From 5bdf75f3e160bc90768526ba07c30e594b08b96d Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 19 Nov 2019 10:19:57 -0800 Subject: [PATCH] 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 --- .../src/Cli/Commands/WriteBootJsonCommand.cs | 9 -- .../Blazor/Build/src/Core/BootJsonWriter.cs | 69 +++----- .../EmbeddedResources/EmbeddedResourceInfo.cs | 17 -- .../EmbeddedResources/EmbeddedResourceKind.cs | 12 -- .../EmbeddedResourcesProcessor.cs | 137 ---------------- .../src/Core/RuntimeDependenciesResolver.cs | 148 ++++++++++-------- .../Microsoft.AspNetCore.Blazor.Build.csproj | 6 +- .../src/targets/Blazor.MonoRuntime.targets | 12 +- .../Blazor/Build/test/BootJsonWriterTest.cs | 40 +---- src/Components/Web.JS/.gitignore | 1 + .../Web.JS/dist/Release/blazor.webassembly.js | 1 - src/Components/Web.JS/src/Boot.WebAssembly.ts | 41 +---- 12 files changed, 120 insertions(+), 373 deletions(-) delete mode 100644 src/Components/Blazor/Build/src/Core/EmbeddedResources/EmbeddedResourceInfo.cs delete mode 100644 src/Components/Blazor/Build/src/Core/EmbeddedResources/EmbeddedResourceKind.cs delete mode 100644 src/Components/Blazor/Build/src/Core/EmbeddedResources/EmbeddedResourcesProcessor.cs delete mode 100644 src/Components/Web.JS/dist/Release/blazor.webassembly.js diff --git a/src/Components/Blazor/Build/src/Cli/Commands/WriteBootJsonCommand.cs b/src/Components/Blazor/Build/src/Cli/Commands/WriteBootJsonCommand.cs index dea217958c..6924bd525c 100644 --- a/src/Components/Blazor/Build/src/Cli/Commands/WriteBootJsonCommand.cs +++ b/src/Components/Blazor/Build/src/Cli/Commands/WriteBootJsonCommand.cs @@ -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", 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", "Path to the output file", CommandOptionType.SingleValue); @@ -44,14 +40,9 @@ namespace Microsoft.AspNetCore.Blazor.Build.DevServer.Commands ? File.ReadAllLines(referencesFile.Value()) : Array.Empty(); - var embeddedResourcesSources = embeddedResourcesFile.HasValue() - ? File.ReadAllLines(embeddedResourcesFile.Value()) - : Array.Empty(); - BootJsonWriter.WriteFile( mainAssemblyPath.Value, referencesSources, - embeddedResourcesSources, linkerEnabledFlag.HasValue(), outputPath.Value()); return 0; diff --git a/src/Components/Blazor/Build/src/Core/BootJsonWriter.cs b/src/Components/Blazor/Build/src/Core/BootJsonWriter.cs index 4d4c114158..a98bbadb20 100644 --- a/src/Components/Blazor/Build/src/Core/BootJsonWriter.cs +++ b/src/Components/Blazor/Build/src/Core/BootJsonWriter.cs @@ -4,10 +4,9 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; +using System.Reflection; using System.Text.Json; using Microsoft.AspNetCore.Components; -using Mono.Cecil; namespace Microsoft.AspNetCore.Blazor.Build { @@ -16,79 +15,55 @@ namespace Microsoft.AspNetCore.Blazor.Build public static void WriteFile( string assemblyPath, string[] assemblyReferences, - string[] embeddedResourcesSources, bool linkerEnabled, string outputPath) { - var embeddedContent = EmbeddedResourcesProcessor.ExtractEmbeddedResources( - embeddedResourcesSources, Path.GetDirectoryName(outputPath)); var bootJsonText = GetBootJsonContent( - Path.GetFileName(assemblyPath), - GetAssemblyEntryPoint(assemblyPath), + AssemblyName.GetAssemblyName(assemblyPath).Name, assemblyReferences, - embeddedContent, linkerEnabled); var normalizedOutputPath = Path.GetFullPath(outputPath); Console.WriteLine("Writing boot data to: " + normalizedOutputPath); File.WriteAllText(normalizedOutputPath, bootJsonText); } - public static string GetBootJsonContent(string assemblyFileName, string entryPoint, string[] assemblyReferences, IEnumerable embeddedContent, bool linkerEnabled) + public static string GetBootJsonContent(string entryAssembly, string[] assemblyReferences, bool linkerEnabled) { var data = new BootJsonData( - assemblyFileName, - entryPoint, + entryAssembly, assemblyReferences, - embeddedContent, linkerEnabled); 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}"; - } - } - /// /// Defines the structure of a Blazor boot JSON file /// - class BootJsonData + readonly struct BootJsonData { - public string Main { get; } - public string EntryPoint { get; } - public IEnumerable AssemblyReferences { get; } - public IEnumerable CssReferences { get; } - public IEnumerable JsReferences { get; } + /// + /// Gets the name of the assembly with the application entry point + /// + public string EntryAssembly { get; } + + /// + /// Gets the closure of assemblies to be loaded by Blazor WASM. This includes the application entry assembly. + /// + public IEnumerable Assemblies { get; } + + /// + /// Gets a value that determines if the linker is enabled. + /// public bool LinkerEnabled { get; } public BootJsonData( - string entrypointAssemblyWithExtension, - string entryPoint, - IEnumerable assemblyReferences, - IEnumerable embeddedContent, + string entryAssembly, + IEnumerable assemblies, bool linkerEnabled) { - Main = entrypointAssemblyWithExtension; - EntryPoint = entryPoint; - AssemblyReferences = assemblyReferences; + EntryAssembly = entryAssembly; + Assemblies = assemblies; 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); } } } diff --git a/src/Components/Blazor/Build/src/Core/EmbeddedResources/EmbeddedResourceInfo.cs b/src/Components/Blazor/Build/src/Core/EmbeddedResources/EmbeddedResourceInfo.cs deleted file mode 100644 index 97331537f2..0000000000 --- a/src/Components/Blazor/Build/src/Core/EmbeddedResources/EmbeddedResourceInfo.cs +++ /dev/null @@ -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; - } - } -} diff --git a/src/Components/Blazor/Build/src/Core/EmbeddedResources/EmbeddedResourceKind.cs b/src/Components/Blazor/Build/src/Core/EmbeddedResources/EmbeddedResourceKind.cs deleted file mode 100644 index caf322ee15..0000000000 --- a/src/Components/Blazor/Build/src/Core/EmbeddedResources/EmbeddedResourceKind.cs +++ /dev/null @@ -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 - } -} diff --git a/src/Components/Blazor/Build/src/Core/EmbeddedResources/EmbeddedResourcesProcessor.cs b/src/Components/Blazor/Build/src/Core/EmbeddedResources/EmbeddedResourcesProcessor.cs deleted file mode 100644 index 21a28597e1..0000000000 --- a/src/Components/Blazor/Build/src/Core/EmbeddedResources/EmbeddedResourcesProcessor.cs +++ /dev/null @@ -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 _knownResourceKindsByNamePrefix = new Dictionary - { - { "blazor:js:", EmbeddedResourceKind.JavaScript }, - { "blazor:css:", EmbeddedResourceKind.Css }, - { "blazor:file:", EmbeddedResourceKind.Static }, - }; - - /// - /// Finds Blazor-specific embedded resources in the specified assemblies, writes them - /// to disk, and returns a description of those resources in dependency order. - /// - /// The paths to assemblies that may contain embedded resources. - /// The path to the directory where output is being written. - /// A description of the embedded resources that were written to disk. - public static IReadOnlyList ExtractEmbeddedResources( - IEnumerable 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 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); - } -} diff --git a/src/Components/Blazor/Build/src/Core/RuntimeDependenciesResolver.cs b/src/Components/Blazor/Build/src/Core/RuntimeDependenciesResolver.cs index 18637753cc..998f65fac5 100644 --- a/src/Components/Blazor/Build/src/Core/RuntimeDependenciesResolver.cs +++ b/src/Components/Blazor/Build/src/Core/RuntimeDependenciesResolver.cs @@ -6,7 +6,9 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; -using Mono.Cecil; +using System.Reflection; +using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; namespace Microsoft.AspNetCore.Blazor.Build { @@ -27,19 +29,15 @@ namespace Microsoft.AspNetCore.Blazor.Build string[] applicationDependencies, string[] monoBclDirectories) { - var assembly = new AssemblyEntry(entryPoint, AssemblyDefinition.ReadAssembly(entryPoint)); + var entryAssembly = new AssemblyEntry(entryPoint, GetAssemblyName(entryPoint)); - var dependencies = applicationDependencies - .Select(a => new AssemblyEntry(a, AssemblyDefinition.ReadAssembly(a))) - .ToArray(); + var dependencies = CreateAssemblyLookup(applicationDependencies); - var bcl = monoBclDirectories - .SelectMany(d => Directory.EnumerateFiles(d, "*.dll").Select(f => Path.Combine(d, f))) - .Select(a => new AssemblyEntry(a, AssemblyDefinition.ReadAssembly(a))) - .ToArray(); + var bcl = CreateAssemblyLookup(monoBclDirectories + .SelectMany(d => Directory.EnumerateFiles(d, "*.dll").Select(f => Path.Combine(d, f)))); var assemblyResolutionContext = new AssemblyResolutionContext( - assembly, + entryAssembly, dependencies, bcl); @@ -47,6 +45,28 @@ namespace Microsoft.AspNetCore.Blazor.Build var paths = assemblyResolutionContext.Results.Select(r => r.Path); return paths.Concat(FindPdbs(paths)); + + static Dictionary CreateAssemblyLookup(IEnumerable assemblyPaths) + { + var dictionary = new Dictionary(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 FindPdbs(IEnumerable dllPaths) @@ -59,46 +79,40 @@ namespace Microsoft.AspNetCore.Blazor.Build public class AssemblyResolutionContext { public AssemblyResolutionContext( - AssemblyEntry assembly, - AssemblyEntry[] dependencies, - AssemblyEntry[] bcl) + AssemblyEntry entryAssembly, + Dictionary dependencies, + Dictionary bcl) { - Assembly = assembly; + EntryAssembly = entryAssembly; Dependencies = dependencies; Bcl = bcl; } - public AssemblyEntry Assembly { get; } - public AssemblyEntry[] Dependencies { get; } - public AssemblyEntry[] Bcl { get; } + public AssemblyEntry EntryAssembly { get; } + public Dictionary Dependencies { get; } + public Dictionary Bcl { get; } public IList Results { get; } = new List(); internal void ResolveAssemblies() { var visitedAssemblies = new HashSet(); - var pendingAssemblies = new Stack(); - pendingAssemblies.Push(Assembly.Definition.Name); + var pendingAssemblies = new Stack(); + pendingAssemblies.Push(EntryAssembly.Name); ResolveAssembliesCore(); void ResolveAssembliesCore() { 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, particularly - // 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) + // 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. + if (Resolve(current) is AssemblyEntry resolved) { Results.Add(resolved); - var references = GetAssemblyReferences(resolved); + var references = GetAssemblyReferences(resolved.Path); foreach (var reference in references) { pendingAssemblies.Push(reference); @@ -108,58 +122,70 @@ namespace Microsoft.AspNetCore.Blazor.Build } } - IEnumerable GetAssemblyReferences(AssemblyEntry current) => - current.Definition.Modules.SelectMany(m => m.AssemblyReferences); - - AssemblyEntry Resolve(AssemblyNameReference current) + AssemblyEntry? Resolve(string assemblyName) { - 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 // 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 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 assembly; } return null; } + + static IReadOnlyList GetAssemblyReferences(string assemblyPath) + { + try + { + using var peReader = new PEReader(File.OpenRead(assemblyPath)); + if (!peReader.HasMetadata) + { + return Array.Empty(); // not a managed assembly + } + + var metadataReader = peReader.GetMetadataReader(); + + var references = new List(); + 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(); // not a managed assembly + } } } [DebuggerDisplay("{ToString(),nq}")] - public class AssemblyEntry + public readonly struct AssemblyEntry { - public AssemblyEntry(string path, AssemblyDefinition definition) + public AssemblyEntry(string path, string name) { Path = path; - Definition = definition; + Name = name; } - public string Path { get; set; } - public AssemblyDefinition Definition { get; set; } + public string Path { get; } + public string Name { get; } - public override string ToString() => Definition.FullName; + public override string ToString() => Name; } } } diff --git a/src/Components/Blazor/Build/src/Microsoft.AspNetCore.Blazor.Build.csproj b/src/Components/Blazor/Build/src/Microsoft.AspNetCore.Blazor.Build.csproj index 35853a4b9e..f3f2e76131 100644 --- a/src/Components/Blazor/Build/src/Microsoft.AspNetCore.Blazor.Build.csproj +++ b/src/Components/Blazor/Build/src/Microsoft.AspNetCore.Blazor.Build.csproj @@ -27,12 +27,8 @@ - - - - - + diff --git a/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.targets b/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.targets index b2de5481db..9b19e06bb5 100644 --- a/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.targets +++ b/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.targets @@ -623,14 +623,12 @@ Inputs="$(BlazorBuildBootJsonInputsCache);@(_BlazorDependencyInput)" Outputs="$(BlazorBootJsonIntermediateOutputPath)"> - <_UnlinkedAppReferencesPaths Include="@(_BlazorDependencyInput)" /> - <_AppReferences Include="@(BlazorItemOutput->WithMetadataValue('Type','Assembly')->WithMetadataValue('PrimaryOutput','')->'%(FileName)%(Extension)')" /> + <_AppReferences Include="@(BlazorItemOutput->WithMetadataValue('Type','Assembly')->'%(FileName)%(Extension)')" /> <_AppReferences Include="@(BlazorItemOutput->WithMetadataValue('Type','Pdb')->'%(FileName)%(Extension)')" Condition="'$(BlazorEnableDebugging)' == 'true'" /> <_LinkerEnabledFlag Condition="'$(_BlazorShouldLinkApplicationAssemblies)' != ''">--linker-enabled <_ReferencesArg Condition="'@(_AppReferences)' != ''">--references "$(BlazorBootJsonReferencesFilePath)" - <_EmbeddedResourcesArg Condition="'@(_UnlinkedAppReferencesPaths)' != ''">--embedded-resources "$(BlazorEmbeddedResourcesConfigFilePath)" - - - + <_BlazorBootJson Include="$(BlazorBootJsonIntermediateOutputPath)" /> diff --git a/src/Components/Blazor/Build/test/BootJsonWriterTest.cs b/src/Components/Blazor/Build/test/BootJsonWriterTest.cs index 3632a082b7..d917a40b06 100644 --- a/src/Components/Blazor/Build/test/BootJsonWriterTest.cs +++ b/src/Components/Blazor/Build/test/BootJsonWriterTest.cs @@ -3,8 +3,6 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using System; -using System.Linq; using Xunit; namespace Microsoft.AspNetCore.Blazor.Build.Test @@ -15,48 +13,16 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test public void ProducesJsonReferencingAssemblyAndDependencies() { // 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( "MyApp.Entrypoint.dll", - "MyNamespace.MyType::MyMethod", assemblyReferences, - Enumerable.Empty(), linkerEnabled: true); // Assert var parsedContent = JsonConvert.DeserializeObject(content); - Assert.Equal("MyApp.Entrypoint.dll", parsedContent["main"].Value()); - Assert.Equal("MyNamespace.MyType::MyMethod", parsedContent["entryPoint"].Value()); - Assert.Equal(assemblyReferences, parsedContent["assemblyReferences"].Values()); - } - - [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(content); - Assert.Equal( - new[] { "css/first.css", "css/second.css" }, - parsedContent["cssReferences"].Values()); - Assert.Equal( - new[] { "javascript/first.js", "javascript/second.js" }, - parsedContent["jsReferences"].Values()); + Assert.Equal("MyApp.Entrypoint.dll", parsedContent["entryAssembly"].Value()); + Assert.Equal(assemblyReferences, parsedContent["assemblies"].Values()); } } } diff --git a/src/Components/Web.JS/.gitignore b/src/Components/Web.JS/.gitignore index 10999e0792..6adb603890 100644 --- a/src/Components/Web.JS/.gitignore +++ b/src/Components/Web.JS/.gitignore @@ -1,2 +1,3 @@ node_modules/ dist/Debug/ +dist/Release/blazor.webassembly.js \ No newline at end of file diff --git a/src/Components/Web.JS/dist/Release/blazor.webassembly.js b/src/Components/Web.JS/dist/Release/blazor.webassembly.js deleted file mode 100644 index 23fa6e24e6..0000000000 --- a/src/Components/Web.JS/dist/Release/blazor.webassembly.js +++ /dev/null @@ -1 +0,0 @@ -!function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=45)}([,,,,,function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),n(25),n(18);var r=n(26),o=n(13),a={},i=!1;function l(e,t,n){var o=a[e];o||(o=a[e]=new r.BrowserRenderer(e)),o.attachRootComponentToLogicalElement(n,t)}t.attachRootComponentToLogicalElement=l,t.attachRootComponentToElement=function(e,t,n){var r=document.querySelector(e);if(!r)throw new Error("Could not find any element matching selector '"+e+"'.");l(n||0,o.toLogicalElement(r,!0),t)},t.renderBatch=function(e,t){var n=a[e];if(!n)throw new Error("There is no browser renderer with ID "+e+".");for(var r=t.arrayRangeReader,o=t.updatedComponents(),l=r.values(o),u=r.count(o),s=t.referenceFrames(),c=r.values(s),f=t.diffReader,d=0;d0&&o[o.length-1])&&(6===a[0]||2===a[0])){i=0;continue}if(3===a[0]&&(!o||a[1]>o[0]&&a[1]0&&!t)throw new Error("New logical elements must start empty, or allowExistingContents must be true");return e[r]=[],e}function l(e,t,n){var a=e;if(e instanceof Comment&&(s(a)&&s(a).length>0))throw new Error("Not implemented: inserting non-empty logical container");if(u(a))throw new Error("Not implemented: moving existing logical children");var i=s(t);if(n0;)e(r,0);var a=r;a.parentNode.removeChild(a)},t.getLogicalParent=u,t.getLogicalSiblingEnd=function(e){return e[a]||null},t.getLogicalChild=function(e,t){return s(e)[t]},t.isSvgElement=function(e){return"http://www.w3.org/2000/svg"===c(e).namespaceURI},t.getLogicalChildrenArray=s,t.permuteLogicalChildren=function(e,t){var n=s(e);t.forEach(function(e){e.moveRangeStart=n[e.fromSiblingIndex],e.moveRangeEnd=function e(t){if(t instanceof Element)return t;var n=f(t);if(n)return n.previousSibling;var r=u(t);return r instanceof Element?r.lastChild:e(r)}(e.moveRangeStart)}),t.forEach(function(t){var r=t.moveToBeforeMarker=document.createComment("marker"),o=n[t.toSiblingIndex+1];o?o.parentNode.insertBefore(r,o):d(r,e)}),t.forEach(function(e){for(var t=e.moveToBeforeMarker,n=t.parentNode,r=e.moveRangeStart,o=e.moveRangeEnd,a=r;a;){var i=a.nextSibling;if(n.insertBefore(a,t),a===o)break;a=i}n.removeChild(t)}),t.forEach(function(e){n[e.toSiblingIndex]=e.moveRangeStart})},t.getClosestDomElement=c},,,,function(e,t,n){"use strict";var r;!function(e){window.DotNet=e;var t=[],n={},r={},o=1,a=null;function i(e){t.push(e)}function l(e,t,n,r){var o=s();if(o.invokeDotNetFromJS){var a=JSON.stringify(r,h),i=o.invokeDotNetFromJS(e,t,n,a);return i?f(i):null}throw new Error("The current dispatcher does not support synchronous calls from JS to .NET. Use invokeMethodAsync instead.")}function u(e,t,r,a){if(e&&r)throw new Error("For instance method calls, assemblyName should be null. Received '"+e+"'.");var i=o++,l=new Promise(function(e,t){n[i]={resolve:e,reject:t}});try{var u=JSON.stringify(a,h);s().beginInvokeDotNetFromJS(i,e,t,r,u)}catch(e){c(i,!1,e)}return l}function s(){if(null!==a)return a;throw new Error("No .NET call dispatcher has been set.")}function c(e,t,r){if(!n.hasOwnProperty(e))throw new Error("There is no pending async call with ID "+e+".");var o=n[e];delete n[e],t?o.resolve(r):o.reject(r)}function f(e){return e?JSON.parse(e,function(e,n){return t.reduce(function(t,n){return n(e,t)},n)}):null}function d(e){return e instanceof Error?e.message+"\n"+e.stack:e?e.toString():"null"}function p(e){if(r.hasOwnProperty(e))return r[e];var t,n=window,o="window";if(e.split(".").forEach(function(e){if(!(e in n))throw new Error("Could not find '"+e+"' in '"+o+"'.");t=n,n=n[e],o+="."+e}),n instanceof Function)return n=n.bind(t),r[e]=n,n;throw new Error("The value '"+o+"' is not a function.")}e.attachDispatcher=function(e){a=e},e.attachReviver=i,e.invokeMethod=function(e,t){for(var n=[],r=2;r0&&o[o.length-1])&&(6===a[0]||2===a[0])){i=0;continue}if(3===a[0]&&(!o||a[1]>o[0]&&a[1]0&&o[o.length-1])&&(6===a[0]||2===a[0])){i=0;continue}if(3===a[0]&&(!o||a[1]>o[0]&&a[1]4)throw new Error("Currently, MonoPlatform supports passing a maximum of 4 arguments from JS to .NET. You tried to pass "+r.length+".");var o=Module.stackSave();try{for(var a=Module.stackAlloc(r.length),l=Module.stackAlloc(4),u=0;u>2,r=Module.HEAPU32[n+1];if(r>g)throw new Error("Cannot read uint64 with high order part "+r+", because the result would exceed Number.MAX_SAFE_INTEGER.");return r*y+Module.HEAPU32[n]},readFloatField:function(e,t){return Module.getValue(e+(t||0),"float")},readObjectField:function(e,t){return Module.getValue(e+(t||0),"i32")},readStringField:function(e,n){var r=Module.getValue(e+(n||0),"i32");return 0===r?null:t.monoPlatform.toJavaScriptString(r)},readStructField:function(e,t){return e+(t||0)}};var E=document.createElement("a");function _(e){return e+12}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(32),o=window.chrome&&navigator.userAgent.indexOf("Edge")<0,a=!1;function i(){return a&&o}t.hasDebuggingEnabled=i,t.attachDebuggerHotkey=function(e){a=e.some(function(e){return/\.pdb$/.test(r.getFileNameFromUrl(e))});var t=navigator.platform.match(/^Mac/i)?"Cmd":"Alt";i()&&console.info("Debugging hotkey: Shift+"+t+"+D (when application has focus)"),document.addEventListener("keydown",function(e){var t;e.shiftKey&&(e.metaKey||e.altKey)&&"KeyD"===e.code&&(a?o?((t=document.createElement("a")).href="_framework/debug?url="+encodeURIComponent(location.href),t.target="_blank",t.rel="noopener noreferrer",t.click()):console.error("Currently, only Edge(Chromium) or Chrome is supported for debugging."):console.error("Cannot start debugging, because the application was not compiled with debugging enabled."))})}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(18),o=function(){function e(e){this.batchAddress=e,this.arrayRangeReader=a,this.arrayBuilderSegmentReader=i,this.diffReader=l,this.editReader=u,this.frameReader=s}return e.prototype.updatedComponents=function(){return r.platform.readStructField(this.batchAddress,0)},e.prototype.referenceFrames=function(){return r.platform.readStructField(this.batchAddress,a.structLength)},e.prototype.disposedComponentIds=function(){return r.platform.readStructField(this.batchAddress,2*a.structLength)},e.prototype.disposedEventHandlerIds=function(){return r.platform.readStructField(this.batchAddress,3*a.structLength)},e.prototype.updatedComponentsEntry=function(e,t){return c(e,t,l.structLength)},e.prototype.referenceFramesEntry=function(e,t){return c(e,t,s.structLength)},e.prototype.disposedComponentIdsEntry=function(e,t){var n=c(e,t,4);return r.platform.readInt32Field(n)},e.prototype.disposedEventHandlerIdsEntry=function(e,t){var n=c(e,t,8);return r.platform.readUint64Field(n)},e}();t.SharedMemoryRenderBatch=o;var a={structLength:8,values:function(e){return r.platform.readObjectField(e,0)},count:function(e){return r.platform.readInt32Field(e,4)}},i={structLength:12,values:function(e){var t=r.platform.readObjectField(e,0),n=r.platform.getObjectFieldsBaseAddress(t);return r.platform.readObjectField(n,0)},offset:function(e){return r.platform.readInt32Field(e,4)},count:function(e){return r.platform.readInt32Field(e,8)}},l={structLength:4+i.structLength,componentId:function(e){return r.platform.readInt32Field(e,0)},edits:function(e){return r.platform.readStructField(e,4)},editsEntry:function(e,t){return c(e,t,u.structLength)}},u={structLength:20,editType:function(e){return r.platform.readInt32Field(e,0)},siblingIndex:function(e){return r.platform.readInt32Field(e,4)},newTreeIndex:function(e){return r.platform.readInt32Field(e,8)},moveToSiblingIndex:function(e){return r.platform.readInt32Field(e,8)},removedAttributeName:function(e){return r.platform.readStringField(e,16)}},s={structLength:36,frameType:function(e){return r.platform.readInt16Field(e,4)},subtreeLength:function(e){return r.platform.readInt32Field(e,8)},elementReferenceCaptureId:function(e){return r.platform.readStringField(e,16)},componentId:function(e){return r.platform.readInt32Field(e,12)},elementName:function(e){return r.platform.readStringField(e,16)},textContent:function(e){return r.platform.readStringField(e,16)},markupContent:function(e){return r.platform.readStringField(e,16)},attributeName:function(e){return r.platform.readStringField(e,16)},attributeValue:function(e){return r.platform.readStringField(e,24)},attributeEventHandlerId:function(e){return r.platform.readUint64Field(e,8)}};function c(e,t,n){return r.platform.getArrayEntryPtr(e,t,n)}}]); \ No newline at end of file diff --git a/src/Components/Web.JS/src/Boot.WebAssembly.ts b/src/Components/Web.JS/src/Boot.WebAssembly.ts index d6f11ba2b2..efecd016e9 100644 --- a/src/Components/Web.JS/src/Boot.WebAssembly.ts +++ b/src/Components/Web.JS/src/Boot.WebAssembly.ts @@ -2,7 +2,6 @@ import '@dotnet/jsinterop'; import './GlobalExports'; import * as Environment from './Environment'; import { monoPlatform } from './Platform/Mono/MonoPlatform'; -import { getAssemblyNameFromUrl } from './Platform/Url'; import { renderBatch } from './Rendering/Renderer'; import { SharedMemoryRenderBatch } from './Rendering/RenderBatch/SharedMemoryRenderBatch'; import { Pointer } from './Platform/Platform'; @@ -39,15 +38,13 @@ async function boot(options?: any): Promise { // Fetch the boot JSON file const bootConfig = await fetchBootConfigAsync(); - const embeddedResourcesPromise = loadEmbeddedResourcesAsync(bootConfig); 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'); } // Determine the URLs of the assemblies we want to load, then begin fetching them all - const loadAssemblyUrls = [bootConfig.main] - .concat(bootConfig.assemblyReferences) + const loadAssemblyUrls = bootConfig.assemblies .map(filename => `_framework/_bin/${filename}`); try { @@ -56,12 +53,8 @@ async function boot(options?: any): Promise { 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 - const mainAssemblyName = getAssemblyNameFromUrl(bootConfig.main); - platform.callEntryPoint(mainAssemblyName); + platform.callEntryPoint(bootConfig.entryAssembly); } async function fetchBootConfigAsync() { @@ -71,36 +64,10 @@ async function fetchBootConfigAsync() { return bootConfigResponse.json() as Promise; } -function loadEmbeddedResourcesAsync(bootConfig: BootJsonData): Promise { - 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 interface BootJsonData { - main: string; - entryPoint: string; - assemblyReferences: string[]; - cssReferences: string[]; - jsReferences: string[]; + entryAssembly: string; + assemblies: string[]; linkerEnabled: boolean; }