Use System.Reflection.Metadata to generate BootConfig (#17156)

* Use System.Reflection.Metadata to generate BootConfig

* Remove reference to Mono.Cecil
* Remove support for auto embedded css \ js
* Remove blazor.webassembly.js
This commit is contained in:
Pranav K 2019-11-19 10:19:57 -08:00 committed by GitHub
parent 92fce0da09
commit 5bdf75f3e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 120 additions and 373 deletions

View File

@ -15,10 +15,6 @@ namespace Microsoft.AspNetCore.Blazor.Build.DevServer.Commands
"The path to a file that lists the paths to given referenced dll files", "The path to a file that lists the paths to given referenced dll files",
CommandOptionType.SingleValue); CommandOptionType.SingleValue);
var embeddedResourcesFile = command.Option("--embedded-resources",
"The path to a file that lists the paths of .NET assemblies that may contain embedded resources (typically, referenced assemblies in their pre-linked states)",
CommandOptionType.SingleValue);
var outputPath = command.Option("--output", var outputPath = command.Option("--output",
"Path to the output file", "Path to the output file",
CommandOptionType.SingleValue); CommandOptionType.SingleValue);
@ -44,14 +40,9 @@ namespace Microsoft.AspNetCore.Blazor.Build.DevServer.Commands
? File.ReadAllLines(referencesFile.Value()) ? File.ReadAllLines(referencesFile.Value())
: Array.Empty<string>(); : Array.Empty<string>();
var embeddedResourcesSources = embeddedResourcesFile.HasValue()
? File.ReadAllLines(embeddedResourcesFile.Value())
: Array.Empty<string>();
BootJsonWriter.WriteFile( BootJsonWriter.WriteFile(
mainAssemblyPath.Value, mainAssemblyPath.Value,
referencesSources, referencesSources,
embeddedResourcesSources,
linkerEnabledFlag.HasValue(), linkerEnabledFlag.HasValue(),
outputPath.Value()); outputPath.Value());
return 0; return 0;

View File

@ -4,10 +4,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Reflection;
using System.Text.Json; using System.Text.Json;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Mono.Cecil;
namespace Microsoft.AspNetCore.Blazor.Build namespace Microsoft.AspNetCore.Blazor.Build
{ {
@ -16,79 +15,55 @@ namespace Microsoft.AspNetCore.Blazor.Build
public static void WriteFile( public static void WriteFile(
string assemblyPath, string assemblyPath,
string[] assemblyReferences, string[] assemblyReferences,
string[] embeddedResourcesSources,
bool linkerEnabled, bool linkerEnabled,
string outputPath) string outputPath)
{ {
var embeddedContent = EmbeddedResourcesProcessor.ExtractEmbeddedResources(
embeddedResourcesSources, Path.GetDirectoryName(outputPath));
var bootJsonText = GetBootJsonContent( var bootJsonText = GetBootJsonContent(
Path.GetFileName(assemblyPath), AssemblyName.GetAssemblyName(assemblyPath).Name,
GetAssemblyEntryPoint(assemblyPath),
assemblyReferences, assemblyReferences,
embeddedContent,
linkerEnabled); linkerEnabled);
var normalizedOutputPath = Path.GetFullPath(outputPath); var normalizedOutputPath = Path.GetFullPath(outputPath);
Console.WriteLine("Writing boot data to: " + normalizedOutputPath); Console.WriteLine("Writing boot data to: " + normalizedOutputPath);
File.WriteAllText(normalizedOutputPath, bootJsonText); File.WriteAllText(normalizedOutputPath, bootJsonText);
} }
public static string GetBootJsonContent(string assemblyFileName, string entryPoint, string[] assemblyReferences, IEnumerable<EmbeddedResourceInfo> embeddedContent, bool linkerEnabled) public static string GetBootJsonContent(string entryAssembly, string[] assemblyReferences, bool linkerEnabled)
{ {
var data = new BootJsonData( var data = new BootJsonData(
assemblyFileName, entryAssembly,
entryPoint,
assemblyReferences, assemblyReferences,
embeddedContent,
linkerEnabled); linkerEnabled);
return JsonSerializer.Serialize(data, JsonSerializerOptionsProvider.Options); return JsonSerializer.Serialize(data, JsonSerializerOptionsProvider.Options);
} }
private static string GetAssemblyEntryPoint(string assemblyPath)
{
using (var assemblyDefinition = AssemblyDefinition.ReadAssembly(assemblyPath))
{
var entryPoint = assemblyDefinition.EntryPoint;
if (entryPoint == null)
{
throw new ArgumentException($"The assembly at {assemblyPath} has no specified entry point.");
}
return $"{entryPoint.DeclaringType.FullName}::{entryPoint.Name}";
}
}
/// <summary> /// <summary>
/// Defines the structure of a Blazor boot JSON file /// Defines the structure of a Blazor boot JSON file
/// </summary> /// </summary>
class BootJsonData readonly struct BootJsonData
{ {
public string Main { get; } /// <summary>
public string EntryPoint { get; } /// Gets the name of the assembly with the application entry point
public IEnumerable<string> AssemblyReferences { get; } /// </summary>
public IEnumerable<string> CssReferences { get; } public string EntryAssembly { get; }
public IEnumerable<string> JsReferences { get; }
/// <summary>
/// Gets the closure of assemblies to be loaded by Blazor WASM. This includes the application entry assembly.
/// </summary>
public IEnumerable<string> Assemblies { get; }
/// <summary>
/// Gets a value that determines if the linker is enabled.
/// </summary>
public bool LinkerEnabled { get; } public bool LinkerEnabled { get; }
public BootJsonData( public BootJsonData(
string entrypointAssemblyWithExtension, string entryAssembly,
string entryPoint, IEnumerable<string> assemblies,
IEnumerable<string> assemblyReferences,
IEnumerable<EmbeddedResourceInfo> embeddedContent,
bool linkerEnabled) bool linkerEnabled)
{ {
Main = entrypointAssemblyWithExtension; EntryAssembly = entryAssembly;
EntryPoint = entryPoint; Assemblies = assemblies;
AssemblyReferences = assemblyReferences;
LinkerEnabled = linkerEnabled; LinkerEnabled = linkerEnabled;
CssReferences = embeddedContent
.Where(c => c.Kind == EmbeddedResourceKind.Css)
.Select(c => c.RelativePath);
JsReferences = embeddedContent
.Where(c => c.Kind == EmbeddedResourceKind.JavaScript)
.Select(c => c.RelativePath);
} }
} }
} }

View File

@ -1,17 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Blazor.Build
{
internal class EmbeddedResourceInfo
{
public EmbeddedResourceKind Kind { get; }
public string RelativePath { get; }
public EmbeddedResourceInfo(EmbeddedResourceKind kind, string relativePath)
{
Kind = kind;
RelativePath = relativePath;
}
}
}

View File

@ -1,12 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Blazor.Build
{
internal enum EmbeddedResourceKind
{
JavaScript,
Css,
Static
}
}

View File

@ -1,137 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Mono.Cecil;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Microsoft.AspNetCore.Blazor.Build
{
internal class EmbeddedResourcesProcessor
{
const string ContentSubdirName = "_content";
private readonly static Dictionary<string, EmbeddedResourceKind> _knownResourceKindsByNamePrefix = new Dictionary<string, EmbeddedResourceKind>
{
{ "blazor:js:", EmbeddedResourceKind.JavaScript },
{ "blazor:css:", EmbeddedResourceKind.Css },
{ "blazor:file:", EmbeddedResourceKind.Static },
};
/// <summary>
/// Finds Blazor-specific embedded resources in the specified assemblies, writes them
/// to disk, and returns a description of those resources in dependency order.
/// </summary>
/// <param name="referencedAssemblyPaths">The paths to assemblies that may contain embedded resources.</param>
/// <param name="outputDir">The path to the directory where output is being written.</param>
/// <returns>A description of the embedded resources that were written to disk.</returns>
public static IReadOnlyList<EmbeddedResourceInfo> ExtractEmbeddedResources(
IEnumerable<string> referencedAssemblyPaths, string outputDir)
{
// Clean away any earlier state
var contentDir = Path.Combine(outputDir, ContentSubdirName);
if (Directory.Exists(contentDir))
{
Directory.Delete(contentDir, recursive: true);
}
// First, get an ordered list of AssemblyDefinition instances
var referencedAssemblyDefinitions = referencedAssemblyPaths
.Where(path => !Path.GetFileName(path).StartsWith("System.", StringComparison.Ordinal)) // Skip System.* because they are never going to contain embedded resources that we want
.Select(path => AssemblyDefinition.ReadAssembly(path))
.ToList();
referencedAssemblyDefinitions.Sort(OrderWithReferenceSubjectFirst);
// Now process them in turn
return referencedAssemblyDefinitions
.SelectMany(def => ExtractEmbeddedResourcesFromSingleAssembly(def, outputDir))
.ToList()
.AsReadOnly();
}
private static IEnumerable<EmbeddedResourceInfo> ExtractEmbeddedResourcesFromSingleAssembly(
AssemblyDefinition assemblyDefinition, string outputDirPath)
{
var assemblyName = assemblyDefinition.Name.Name;
foreach (var res in assemblyDefinition.MainModule.Resources)
{
if (TryExtractEmbeddedResource(assemblyName, res, outputDirPath, out var extractedResourceInfo))
{
yield return extractedResourceInfo;
}
}
}
private static bool TryExtractEmbeddedResource(string assemblyName, Resource resource, string outputDirPath, out EmbeddedResourceInfo extractedResourceInfo)
{
if (resource is EmbeddedResource embeddedResource)
{
if (TryInterpretLogicalName(resource.Name, out var kind, out var name))
{
// Prefix the output path with the assembly name to ensure no clashes
// Also be invariant to the OS on which the package was built
name = Path.Combine(ContentSubdirName, assemblyName, EnsureHasPathSeparators(name, Path.DirectorySeparatorChar));
// Write the file content to disk, ensuring we don't try to write outside the output root
var outputPath = Path.GetFullPath(Path.Combine(outputDirPath, name));
if (!outputPath.StartsWith(outputDirPath))
{
throw new InvalidOperationException($"Cannot write embedded resource from assembly '{assemblyName}' to '{outputPath}' because it is outside the expected directory {outputDirPath}");
}
WriteResourceFile(embeddedResource, outputPath);
// The URLs we write into the boot json file need to use web-style directory separators
extractedResourceInfo = new EmbeddedResourceInfo(kind, EnsureHasPathSeparators(name, '/'));
return true;
}
}
extractedResourceInfo = null;
return false;
}
private static void WriteResourceFile(EmbeddedResource resource, string outputPath)
{
Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
using (var outputStream = File.OpenWrite(outputPath))
{
resource.GetResourceStream().CopyTo(outputStream);
}
}
private static string EnsureHasPathSeparators(string name, char desiredSeparatorChar) => name
.Replace('\\', desiredSeparatorChar)
.Replace('/', desiredSeparatorChar);
private static bool TryInterpretLogicalName(string logicalName, out EmbeddedResourceKind kind, out string resolvedName)
{
foreach (var kvp in _knownResourceKindsByNamePrefix)
{
if (logicalName.StartsWith(kvp.Key, StringComparison.Ordinal))
{
kind = kvp.Value;
resolvedName = logicalName.Substring(kvp.Key.Length);
return true;
}
}
kind = default;
resolvedName = default;
return false;
}
// For each assembly B that references A, we want the resources from A to be loaded before
// the references for B (because B's resources might depend on A's resources)
private static int OrderWithReferenceSubjectFirst(AssemblyDefinition a, AssemblyDefinition b)
=> AssemblyHasReference(a, b) ? 1
: AssemblyHasReference(b, a) ? -1
: 0;
private static bool AssemblyHasReference(AssemblyDefinition from, AssemblyDefinition to)
=> from.MainModule.AssemblyReferences
.Select(reference => reference.Name)
.Contains(to.Name.Name, StringComparer.Ordinal);
}
}

View File

@ -6,7 +6,9 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Mono.Cecil; using System.Reflection;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
namespace Microsoft.AspNetCore.Blazor.Build namespace Microsoft.AspNetCore.Blazor.Build
{ {
@ -27,19 +29,15 @@ namespace Microsoft.AspNetCore.Blazor.Build
string[] applicationDependencies, string[] applicationDependencies,
string[] monoBclDirectories) string[] monoBclDirectories)
{ {
var assembly = new AssemblyEntry(entryPoint, AssemblyDefinition.ReadAssembly(entryPoint)); var entryAssembly = new AssemblyEntry(entryPoint, GetAssemblyName(entryPoint));
var dependencies = applicationDependencies var dependencies = CreateAssemblyLookup(applicationDependencies);
.Select(a => new AssemblyEntry(a, AssemblyDefinition.ReadAssembly(a)))
.ToArray();
var bcl = monoBclDirectories var bcl = CreateAssemblyLookup(monoBclDirectories
.SelectMany(d => Directory.EnumerateFiles(d, "*.dll").Select(f => Path.Combine(d, f))) .SelectMany(d => Directory.EnumerateFiles(d, "*.dll").Select(f => Path.Combine(d, f))));
.Select(a => new AssemblyEntry(a, AssemblyDefinition.ReadAssembly(a)))
.ToArray();
var assemblyResolutionContext = new AssemblyResolutionContext( var assemblyResolutionContext = new AssemblyResolutionContext(
assembly, entryAssembly,
dependencies, dependencies,
bcl); bcl);
@ -47,6 +45,28 @@ namespace Microsoft.AspNetCore.Blazor.Build
var paths = assemblyResolutionContext.Results.Select(r => r.Path); var paths = assemblyResolutionContext.Results.Select(r => r.Path);
return paths.Concat(FindPdbs(paths)); return paths.Concat(FindPdbs(paths));
static Dictionary<string, AssemblyEntry> CreateAssemblyLookup(IEnumerable<string> assemblyPaths)
{
var dictionary = new Dictionary<string, AssemblyEntry>(StringComparer.Ordinal);
foreach (var path in assemblyPaths)
{
var assemblyName = AssemblyName.GetAssemblyName(path).Name;
if (dictionary.TryGetValue(assemblyName, out var previous))
{
throw new InvalidOperationException($"Multiple assemblies found with the same assembly name '{assemblyName}':" +
Environment.NewLine + string.Join(Environment.NewLine, previous, path));
}
dictionary[assemblyName] = new AssemblyEntry(path, assemblyName);
}
return dictionary;
}
}
private static string GetAssemblyName(string assemblyPath)
{
return AssemblyName.GetAssemblyName(assemblyPath).Name;
} }
private static IEnumerable<string> FindPdbs(IEnumerable<string> dllPaths) private static IEnumerable<string> FindPdbs(IEnumerable<string> dllPaths)
@ -59,46 +79,40 @@ namespace Microsoft.AspNetCore.Blazor.Build
public class AssemblyResolutionContext public class AssemblyResolutionContext
{ {
public AssemblyResolutionContext( public AssemblyResolutionContext(
AssemblyEntry assembly, AssemblyEntry entryAssembly,
AssemblyEntry[] dependencies, Dictionary<string, AssemblyEntry> dependencies,
AssemblyEntry[] bcl) Dictionary<string, AssemblyEntry> bcl)
{ {
Assembly = assembly; EntryAssembly = entryAssembly;
Dependencies = dependencies; Dependencies = dependencies;
Bcl = bcl; Bcl = bcl;
} }
public AssemblyEntry Assembly { get; } public AssemblyEntry EntryAssembly { get; }
public AssemblyEntry[] Dependencies { get; } public Dictionary<string, AssemblyEntry> Dependencies { get; }
public AssemblyEntry[] Bcl { get; } public Dictionary<string, AssemblyEntry> Bcl { get; }
public IList<AssemblyEntry> Results { get; } = new List<AssemblyEntry>(); public IList<AssemblyEntry> Results { get; } = new List<AssemblyEntry>();
internal void ResolveAssemblies() internal void ResolveAssemblies()
{ {
var visitedAssemblies = new HashSet<string>(); var visitedAssemblies = new HashSet<string>();
var pendingAssemblies = new Stack<AssemblyNameReference>(); var pendingAssemblies = new Stack<string>();
pendingAssemblies.Push(Assembly.Definition.Name); pendingAssemblies.Push(EntryAssembly.Name);
ResolveAssembliesCore(); ResolveAssembliesCore();
void ResolveAssembliesCore() void ResolveAssembliesCore()
{ {
while (pendingAssemblies.TryPop(out var current)) while (pendingAssemblies.TryPop(out var current))
{ {
if (!visitedAssemblies.Contains(current.Name)) if (visitedAssemblies.Add(current))
{ {
visitedAssemblies.Add(current.Name); // Not all references will be resolvable within the Mono BCL.
// Skipping unresolved assemblies here is equivalent to passing "--skip-unresolved true" to the Mono linker.
// Not all references will be resolvable within the Mono BCL, particularly if (Resolve(current) is AssemblyEntry resolved)
// when building for server-side Blazor as you will be running on CoreCLR
// and therefore may depend on System.* BCL assemblies that aren't present
// in Mono WebAssembly. Skipping unresolved assemblies here is equivalent
// to passing "--skip-unresolved true" to the Mono linker.
var resolved = Resolve(current);
if (resolved != null)
{ {
Results.Add(resolved); Results.Add(resolved);
var references = GetAssemblyReferences(resolved); var references = GetAssemblyReferences(resolved.Path);
foreach (var reference in references) foreach (var reference in references)
{ {
pendingAssemblies.Push(reference); pendingAssemblies.Push(reference);
@ -108,58 +122,70 @@ namespace Microsoft.AspNetCore.Blazor.Build
} }
} }
IEnumerable<AssemblyNameReference> GetAssemblyReferences(AssemblyEntry current) => AssemblyEntry? Resolve(string assemblyName)
current.Definition.Modules.SelectMany(m => m.AssemblyReferences);
AssemblyEntry Resolve(AssemblyNameReference current)
{ {
if (Assembly.Definition.Name.Name == current.Name) if (EntryAssembly.Name == assemblyName)
{ {
return Assembly; return EntryAssembly;
} }
var referencedAssemblyCandidate = FindCandidate(current, Dependencies);
var bclAssemblyCandidate = FindCandidate(current, Bcl);
// Resolution logic. For right now, we will prefer the mono BCL version of a given // Resolution logic. For right now, we will prefer the mono BCL version of a given
// assembly if there is a candidate assembly and an equivalent mono assembly. // assembly if there is a candidate assembly and an equivalent mono assembly.
if (bclAssemblyCandidate != null) if (Bcl.TryGetValue(assemblyName, out var assembly) ||
Dependencies.TryGetValue(assemblyName, out assembly))
{ {
return bclAssemblyCandidate; return assembly;
}
return referencedAssemblyCandidate;
}
AssemblyEntry FindCandidate(AssemblyNameReference current, AssemblyEntry[] candidates)
{
// Do simple name match. Assume no duplicates.
foreach (var candidate in candidates)
{
if (current.Name == candidate.Definition.Name.Name)
{
return candidate;
}
} }
return null; return null;
} }
static IReadOnlyList<string> GetAssemblyReferences(string assemblyPath)
{
try
{
using var peReader = new PEReader(File.OpenRead(assemblyPath));
if (!peReader.HasMetadata)
{
return Array.Empty<string>(); // not a managed assembly
}
var metadataReader = peReader.GetMetadataReader();
var references = new List<string>();
foreach (var handle in metadataReader.AssemblyReferences)
{
var reference = metadataReader.GetAssemblyReference(handle);
var referenceName = metadataReader.GetString(reference.Name);
references.Add(referenceName);
}
return references;
}
catch (BadImageFormatException)
{
// not a PE file, or invalid metadata
}
return Array.Empty<string>(); // not a managed assembly
}
} }
} }
[DebuggerDisplay("{ToString(),nq}")] [DebuggerDisplay("{ToString(),nq}")]
public class AssemblyEntry public readonly struct AssemblyEntry
{ {
public AssemblyEntry(string path, AssemblyDefinition definition) public AssemblyEntry(string path, string name)
{ {
Path = path; Path = path;
Definition = definition; Name = name;
} }
public string Path { get; set; } public string Path { get; }
public AssemblyDefinition Definition { get; set; } public string Name { get; }
public override string ToString() => Definition.FullName; public override string ToString() => Name;
} }
} }
} }

View File

@ -27,12 +27,8 @@
<ItemGroup> <ItemGroup>
<ProjectReference Condition="'$(BuildNodeJS)' != 'false' and '$(BuildingInsideVisualStudio)' != 'true'" Include="$(RepoRoot)src\Components\Web.JS\Microsoft.AspNetCore.Components.Web.JS.npmproj" ReferenceOutputAssembly="false" /> <ProjectReference Condition="'$(BuildNodeJS)' != 'false' and '$(BuildingInsideVisualStudio)' != 'true'" Include="$(RepoRoot)src\Components\Web.JS\Microsoft.AspNetCore.Components.Web.JS.npmproj" ReferenceOutputAssembly="false" />
<Reference Include="Microsoft.AspNetCore.Components" />
<Reference Include="Microsoft.Extensions.CommandLineUtils.Sources" /> <Reference Include="Microsoft.Extensions.CommandLineUtils.Sources" />
<Reference Include="Microsoft.Extensions.FileProviders.Composite" /> <Reference Include="System.Reflection.Metadata" />
<Reference Include="Microsoft.Extensions.FileProviders.Physical" />
<Reference Include="Mono.Cecil" />
<Reference Include="System.CodeDom" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -623,14 +623,12 @@
Inputs="$(BlazorBuildBootJsonInputsCache);@(_BlazorDependencyInput)" Inputs="$(BlazorBuildBootJsonInputsCache);@(_BlazorDependencyInput)"
Outputs="$(BlazorBootJsonIntermediateOutputPath)"> Outputs="$(BlazorBootJsonIntermediateOutputPath)">
<ItemGroup> <ItemGroup>
<_UnlinkedAppReferencesPaths Include="@(_BlazorDependencyInput)" /> <_AppReferences Include="@(BlazorItemOutput->WithMetadataValue('Type','Assembly')->'%(FileName)%(Extension)')" />
<_AppReferences Include="@(BlazorItemOutput->WithMetadataValue('Type','Assembly')->WithMetadataValue('PrimaryOutput','')->'%(FileName)%(Extension)')" />
<_AppReferences Include="@(BlazorItemOutput->WithMetadataValue('Type','Pdb')->'%(FileName)%(Extension)')" Condition="'$(BlazorEnableDebugging)' == 'true'" /> <_AppReferences Include="@(BlazorItemOutput->WithMetadataValue('Type','Pdb')->'%(FileName)%(Extension)')" Condition="'$(BlazorEnableDebugging)' == 'true'" />
</ItemGroup> </ItemGroup>
<PropertyGroup> <PropertyGroup>
<_LinkerEnabledFlag Condition="'$(_BlazorShouldLinkApplicationAssemblies)' != ''">--linker-enabled</_LinkerEnabledFlag> <_LinkerEnabledFlag Condition="'$(_BlazorShouldLinkApplicationAssemblies)' != ''">--linker-enabled</_LinkerEnabledFlag>
<_ReferencesArg Condition="'@(_AppReferences)' != ''">--references &quot;$(BlazorBootJsonReferencesFilePath)&quot;</_ReferencesArg> <_ReferencesArg Condition="'@(_AppReferences)' != ''">--references &quot;$(BlazorBootJsonReferencesFilePath)&quot;</_ReferencesArg>
<_EmbeddedResourcesArg Condition="'@(_UnlinkedAppReferencesPaths)' != ''">--embedded-resources &quot;$(BlazorEmbeddedResourcesConfigFilePath)&quot;</_EmbeddedResourcesArg>
</PropertyGroup> </PropertyGroup>
<WriteLinesToFile <WriteLinesToFile
@ -638,13 +636,7 @@
Lines="@(_AppReferences)" Lines="@(_AppReferences)"
Overwrite="true" /> Overwrite="true" />
<WriteLinesToFile <Exec Command="$(BlazorBuildExe) write-boot-json &quot;@(IntermediateAssembly)&quot; $(_ReferencesArg) $(_LinkerEnabledFlag) --output &quot;$(BlazorBootJsonIntermediateOutputPath)&quot;" />
Condition="'@(_UnlinkedAppReferencesPaths)' != ''"
File="$(BlazorEmbeddedResourcesConfigFilePath)"
Lines="@(_UnlinkedAppReferencesPaths)"
Overwrite="true" />
<Exec Command="$(BlazorBuildExe) write-boot-json &quot;@(IntermediateAssembly)&quot; $(_ReferencesArg) $(_EmbeddedResourcesArg) $(_LinkerEnabledFlag) --output &quot;$(BlazorBootJsonIntermediateOutputPath)&quot;" />
<ItemGroup Condition="Exists('$(BlazorBootJsonIntermediateOutputPath)')"> <ItemGroup Condition="Exists('$(BlazorBootJsonIntermediateOutputPath)')">
<_BlazorBootJson Include="$(BlazorBootJsonIntermediateOutputPath)" /> <_BlazorBootJson Include="$(BlazorBootJsonIntermediateOutputPath)" />

View File

@ -3,8 +3,6 @@
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using System;
using System.Linq;
using Xunit; using Xunit;
namespace Microsoft.AspNetCore.Blazor.Build.Test namespace Microsoft.AspNetCore.Blazor.Build.Test
@ -15,48 +13,16 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
public void ProducesJsonReferencingAssemblyAndDependencies() public void ProducesJsonReferencingAssemblyAndDependencies()
{ {
// Arrange/Act // Arrange/Act
var assemblyReferences = new string[] { "System.Abc.dll", "MyApp.ClassLib.dll", }; var assemblyReferences = new string[] { "MyApp.EntryPoint.dll", "System.Abc.dll", "MyApp.ClassLib.dll", };
var content = BootJsonWriter.GetBootJsonContent( var content = BootJsonWriter.GetBootJsonContent(
"MyApp.Entrypoint.dll", "MyApp.Entrypoint.dll",
"MyNamespace.MyType::MyMethod",
assemblyReferences, assemblyReferences,
Enumerable.Empty<EmbeddedResourceInfo>(),
linkerEnabled: true); linkerEnabled: true);
// Assert // Assert
var parsedContent = JsonConvert.DeserializeObject<JObject>(content); var parsedContent = JsonConvert.DeserializeObject<JObject>(content);
Assert.Equal("MyApp.Entrypoint.dll", parsedContent["main"].Value<string>()); Assert.Equal("MyApp.Entrypoint.dll", parsedContent["entryAssembly"].Value<string>());
Assert.Equal("MyNamespace.MyType::MyMethod", parsedContent["entryPoint"].Value<string>()); Assert.Equal(assemblyReferences, parsedContent["assemblies"].Values<string>());
Assert.Equal(assemblyReferences, parsedContent["assemblyReferences"].Values<string>());
}
[Fact]
public void IncludesReferencesToEmbeddedContent()
{
// Arrange/Act
var embeddedContent = new[]
{
new EmbeddedResourceInfo(EmbeddedResourceKind.Static, "my/static/file"),
new EmbeddedResourceInfo(EmbeddedResourceKind.Css, "css/first.css"),
new EmbeddedResourceInfo(EmbeddedResourceKind.JavaScript, "javascript/first.js"),
new EmbeddedResourceInfo(EmbeddedResourceKind.Css, "css/second.css"),
new EmbeddedResourceInfo(EmbeddedResourceKind.JavaScript, "javascript/second.js"),
};
var content = BootJsonWriter.GetBootJsonContent(
"MyApp.Entrypoint",
"MyNamespace.MyType::MyMethod",
assemblyReferences: new[] { "Something.dll" },
embeddedContent: embeddedContent,
linkerEnabled: true);
// Assert
var parsedContent = JsonConvert.DeserializeObject<JObject>(content);
Assert.Equal(
new[] { "css/first.css", "css/second.css" },
parsedContent["cssReferences"].Values<string>());
Assert.Equal(
new[] { "javascript/first.js", "javascript/second.js" },
parsedContent["jsReferences"].Values<string>());
} }
} }
} }

View File

@ -1,2 +1,3 @@
node_modules/ node_modules/
dist/Debug/ dist/Debug/
dist/Release/blazor.webassembly.js

File diff suppressed because one or more lines are too long

View File

@ -2,7 +2,6 @@ import '@dotnet/jsinterop';
import './GlobalExports'; import './GlobalExports';
import * as Environment from './Environment'; import * as Environment from './Environment';
import { monoPlatform } from './Platform/Mono/MonoPlatform'; import { monoPlatform } from './Platform/Mono/MonoPlatform';
import { getAssemblyNameFromUrl } from './Platform/Url';
import { renderBatch } from './Rendering/Renderer'; import { renderBatch } from './Rendering/Renderer';
import { SharedMemoryRenderBatch } from './Rendering/RenderBatch/SharedMemoryRenderBatch'; import { SharedMemoryRenderBatch } from './Rendering/RenderBatch/SharedMemoryRenderBatch';
import { Pointer } from './Platform/Platform'; import { Pointer } from './Platform/Platform';
@ -39,15 +38,13 @@ async function boot(options?: any): Promise<void> {
// Fetch the boot JSON file // Fetch the boot JSON file
const bootConfig = await fetchBootConfigAsync(); const bootConfig = await fetchBootConfigAsync();
const embeddedResourcesPromise = loadEmbeddedResourcesAsync(bootConfig);
if (!bootConfig.linkerEnabled) { if (!bootConfig.linkerEnabled) {
console.info('Blazor is running in dev mode without IL stripping. To make the bundle size significantly smaller, publish the application or see https://go.microsoft.com/fwlink/?linkid=870414'); console.info('Blazor is running in dev mode without IL stripping. To make the bundle size significantly smaller, publish the application or see https://go.microsoft.com/fwlink/?linkid=870414');
} }
// Determine the URLs of the assemblies we want to load, then begin fetching them all // Determine the URLs of the assemblies we want to load, then begin fetching them all
const loadAssemblyUrls = [bootConfig.main] const loadAssemblyUrls = bootConfig.assemblies
.concat(bootConfig.assemblyReferences)
.map(filename => `_framework/_bin/${filename}`); .map(filename => `_framework/_bin/${filename}`);
try { try {
@ -56,12 +53,8 @@ async function boot(options?: any): Promise<void> {
throw new Error(`Failed to start platform. Reason: ${ex}`); throw new Error(`Failed to start platform. Reason: ${ex}`);
} }
// Before we start running .NET code, be sure embedded content resources are all loaded
await embeddedResourcesPromise;
// Start up the application // Start up the application
const mainAssemblyName = getAssemblyNameFromUrl(bootConfig.main); platform.callEntryPoint(bootConfig.entryAssembly);
platform.callEntryPoint(mainAssemblyName);
} }
async function fetchBootConfigAsync() { async function fetchBootConfigAsync() {
@ -71,36 +64,10 @@ async function fetchBootConfigAsync() {
return bootConfigResponse.json() as Promise<BootJsonData>; return bootConfigResponse.json() as Promise<BootJsonData>;
} }
function loadEmbeddedResourcesAsync(bootConfig: BootJsonData): Promise<any> {
const cssLoadingPromises = bootConfig.cssReferences.map(cssReference => {
const linkElement = document.createElement('link');
linkElement.rel = 'stylesheet';
linkElement.href = cssReference;
return loadResourceFromElement(linkElement);
});
const jsLoadingPromises = bootConfig.jsReferences.map(jsReference => {
const scriptElement = document.createElement('script');
scriptElement.src = jsReference;
return loadResourceFromElement(scriptElement);
});
return Promise.all(cssLoadingPromises.concat(jsLoadingPromises));
}
function loadResourceFromElement(element: HTMLElement) {
return new Promise((resolve, reject) => {
element.onload = resolve;
element.onerror = reject;
document.head!.appendChild(element);
});
}
// Keep in sync with BootJsonData in Microsoft.AspNetCore.Blazor.Build // Keep in sync with BootJsonData in Microsoft.AspNetCore.Blazor.Build
interface BootJsonData { interface BootJsonData {
main: string; entryAssembly: string;
entryPoint: string; assemblies: string[];
assemblyReferences: string[];
cssReferences: string[];
jsReferences: string[];
linkerEnabled: boolean; linkerEnabled: boolean;
} }