From 23609ab264bac3d2a241d602488bd1d6889a7361 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 24 Jul 2024 18:15:15 +0200 Subject: [PATCH] [release/9.0-preview7] [browser] Extension agnostic lazy assembly loading (#105298) * [browser] Use StaticWebAssets fingerprinting in Wasm SDK (#103755) * Build and publish integration * Make fingerpring work at runtime for assemblies * Make fingerpring work at runtime for icu * Remove version fingerprint check * Check core assembly extension * Typescript nits * JSModules and SatelliteAssemblies * DEBUG require newer SDK for testing * Fix fingerprint for new publish assets * Lazy loading and FP mapping boot json * WBT file on disk checks * WBT file on disk checks * WBT file on disk checks * WBT testmain no fingerprint * WBT revert debug message * AOT * WBT fix ordering * Fingerprinting without webcil * Fix GenerateWasmBootJson when FP is off * NoFingerprint WBT variant * DEBUG try to run WBT without fingerprinting * WBT make entry comparison order agnostic * WBT smoke tests for no-fingerprinting * Update sendtohelix-browser.targets * Remove debug log * Fix typo * Fix regex matching * Remove test for dotnet.js FP since we don't support that anymore * Fix check for System.Private.CoreLib * FP for dotnet.globalization.js * Fingerprinting pdbs * WBT fix file check * Fingerprint segmentation-rules.json * Fix loading pdb for fingerprinted lazy assembly * Ensure lazy pdb is loaded * Remove non-WasmSDK tests from non-FP category * Revert drop for dotnet.js finterprinting * Compute non-Fingerprinted virtualPath for pdb and resource as well * Make debugger working with fingerprinted assemblies and pdbs * DEBUG latest SDK for WBT * DEBUG fix wbt installation * Add WorkloadBuildTasks to WasmBuild.sln * Fix WBT * Revert escaping URL in debugger * Fix lazy loading test and message emit in release config * Fixes for MT after merge * Skip WBT without workloads and without fingerprinting * Turn off fingerprinting when targeting downlevel versions * Git ignore *.d.ts.sha256 * Fix * Update source-build-reference-packages to latest * Revert "Update source-build-reference-packages to latest" This reverts commit bef50ee7016c84c26bf6bd5c26671282509ca6f3. * Fix the references * Update Versions.props * Update Versions.props --------- Co-authored-by: Larry Ewing * [browser] Extension agnostic lazy assembly loading (#104793) * Fix damage from branch merging --------- Co-authored-by: Larry Ewing --- src/mono/browser/runtime/lazyLoading.ts | 30 +++++++++++-------- .../TestAppScenarios/LazyLoadingTests.cs | 28 +++++++++++++---- .../App/WasmBasicTestApp.csproj | 10 ++++++- .../WasmBasicTestApp/App/wwwroot/main.js | 18 ++++++++++- .../GenerateWasmBootJson.cs | 21 ++++++++++--- 5 files changed, 82 insertions(+), 25 deletions(-) diff --git a/src/mono/browser/runtime/lazyLoading.ts b/src/mono/browser/runtime/lazyLoading.ts index 3d075b3005a8e..9787c18ac33ec 100644 --- a/src/mono/browser/runtime/lazyLoading.ts +++ b/src/mono/browser/runtime/lazyLoading.ts @@ -7,17 +7,24 @@ import { AssetEntry } from "./types"; export async function loadLazyAssembly (assemblyNameToLoad: string): Promise { const resources = loaderHelpers.config.resources!; - const originalAssemblyName = assemblyNameToLoad; const lazyAssemblies = resources.lazyAssembly; if (!lazyAssemblies) { throw new Error("No assemblies have been marked as lazy-loadable. Use the 'BlazorWebAssemblyLazyLoad' item group in your project file to enable lazy loading an assembly."); } + let assemblyNameWithoutExtension = assemblyNameToLoad; + if (assemblyNameToLoad.endsWith(".dll")) + assemblyNameWithoutExtension = assemblyNameToLoad.substring(0, assemblyNameToLoad.length - 4); + else if (assemblyNameToLoad.endsWith(".wasm")) + assemblyNameWithoutExtension = assemblyNameToLoad.substring(0, assemblyNameToLoad.length - 5); + + const assemblyNameToLoadDll = assemblyNameWithoutExtension + ".dll"; + const assemblyNameToLoadWasm = assemblyNameWithoutExtension + ".wasm"; if (loaderHelpers.config.resources!.fingerprinting) { const map = loaderHelpers.config.resources!.fingerprinting; for (const fingerprintedName in map) { const nonFingerprintedName = map[fingerprintedName]; - if (nonFingerprintedName == assemblyNameToLoad) { + if (nonFingerprintedName == assemblyNameToLoadDll || nonFingerprintedName == assemblyNameToLoadWasm) { assemblyNameToLoad = fingerprintedName; break; } @@ -25,7 +32,13 @@ export async function loadLazyAssembly (assemblyNameToLoad: string): Promise LoadLazyAssemblyBeforeItIsNeededData() + { + string[] data = ["wasm", "dll", "NoExtension"]; + return data.Select(d => new object[] { d, data }); + } + + [Theory, TestCategory("no-fingerprinting")] + [MemberData(nameof(LoadLazyAssemblyBeforeItIsNeededData))] + public async Task LoadLazyAssemblyBeforeItIsNeeded(string lazyLoadingTestExtension, string[] allLazyLoadingTestExtensions) { CopyTestAsset("WasmBasicTestApp", "LazyLoadingTests", "App"); - BuildProject("Debug"); + BuildProject("Debug", extraArgs: $"-p:LazyLoadingTestExtension={lazyLoadingTestExtension}"); - var result = await RunSdkStyleAppForBuild(new(Configuration: "Debug", TestScenario: "LazyLoadingTest")); + // We are running the app and passing all possible lazy extensions to test matrix of all possibilities. + // We don't need to rebuild the application to test how client is trying to load the assembly. + foreach (var clientLazyLoadingTestExtension in allLazyLoadingTestExtensions) + { + var result = await RunSdkStyleAppForBuild(new( + Configuration: "Debug", + TestScenario: "LazyLoadingTest", + BrowserQueryString: new Dictionary { ["lazyLoadingTestExtension"] = clientLazyLoadingTestExtension } + )); - Assert.True(result.TestOutput.Any(m => m.Contains("FirstName")), "The lazy loading test didn't emit expected message with JSON"); - Assert.True(result.ConsoleOutput.Any(m => m.Contains("Attempting to download") && m.Contains("_framework/Json.") && m.Contains(".pdb")), "The lazy loading test didn't load PDB"); + Assert.True(result.TestOutput.Any(m => m.Contains("FirstName")), "The lazy loading test didn't emit expected message with JSON"); + Assert.True(result.ConsoleOutput.Any(m => m.Contains("Attempting to download") && m.Contains("_framework/Json.") && m.Contains(".pdb")), "The lazy loading test didn't load PDB"); + } } [Fact] diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/App/WasmBasicTestApp.csproj b/src/mono/wasm/testassets/WasmBasicTestApp/App/WasmBasicTestApp.csproj index 04469138d9803..a62c057733b4f 100644 --- a/src/mono/wasm/testassets/WasmBasicTestApp/App/WasmBasicTestApp.csproj +++ b/src/mono/wasm/testassets/WasmBasicTestApp/App/WasmBasicTestApp.csproj @@ -6,11 +6,19 @@ true + + + .dll + .wasm + + $(WasmAssemblyExtension) + + - + diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js b/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js index 85fd83270b273..7fccb3c8e473d 100644 --- a/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js +++ b/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js @@ -138,7 +138,23 @@ try { break; case "LazyLoadingTest": if (params.get("loadRequiredAssembly") !== "false") { - await INTERNAL.loadLazyAssembly(`Json${assemblyExtension}`); + let lazyAssemblyExtension = assemblyExtension; + switch (params.get("lazyLoadingTestExtension")) { + case "wasm": + lazyAssemblyExtension = ".wasm"; + break; + case "dll": + lazyAssemblyExtension = ".dll"; + break; + case "NoExtension": + lazyAssemblyExtension = ""; + break; + default: + lazyAssemblyExtension = assemblyExtension; + break; + } + + await INTERNAL.loadLazyAssembly(`Json${lazyAssemblyExtension}`); } exports.LazyLoadingTest.Run(); exit(0); diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs index 63cee84d412a8..748671aeec898 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs @@ -176,6 +176,15 @@ public void WriteBootJson(Stream output, string entryAssemblyName) { var endpointByAsset = Endpoints.ToDictionary(e => e.GetMetadata("AssetFile")); + var lazyLoadAssembliesWithoutExtension = (LazyLoadedAssemblies ?? Array.Empty()).ToDictionary(l => + { + var extension = Path.GetExtension(l.ItemSpec); + if (extension == ".dll" || extension == Utils.WebcilInWasmExtension) + return Path.GetFileNameWithoutExtension(l.ItemSpec); + + return l.ItemSpec; + }); + var remainingLazyLoadAssemblies = new List(LazyLoadedAssemblies ?? Array.Empty()); var resourceData = result.resources; @@ -194,7 +203,7 @@ public void WriteBootJson(Stream output, string entryAssemblyName) var resourceName = Path.GetFileName(resource.GetMetadata("OriginalItemSpec")); var resourceRoute = Path.GetFileName(endpointByAsset[resource.ItemSpec].ItemSpec); - if (TryGetLazyLoadedAssembly(resourceName, out var lazyLoad)) + if (TryGetLazyLoadedAssembly(lazyLoadAssembliesWithoutExtension, resourceName, out var lazyLoad)) { MapFingerprintedAsset(resourceData, resourceRoute, resourceName); Log.LogMessage(MessageImportance.Low, "Candidate '{0}' is defined as a lazy loaded assembly.", resource.ItemSpec); @@ -220,7 +229,7 @@ public void WriteBootJson(Stream output, string entryAssemblyName) else if (string.Equals("symbol", assetTraitValue, StringComparison.OrdinalIgnoreCase)) { MapFingerprintedAsset(resourceData, resourceRoute, resourceName); - if (TryGetLazyLoadedAssembly($"{fileName}.dll", out _) || TryGetLazyLoadedAssembly($"{fileName}{Utils.WebcilInWasmExtension}", out _)) + if (TryGetLazyLoadedAssembly(lazyLoadAssembliesWithoutExtension, fileName, out _)) { Log.LogMessage(MessageImportance.Low, "Candidate '{0}' is defined as a lazy loaded symbols file.", resource.ItemSpec); resourceData.lazyAssembly ??= new ResourceHashesByNameDictionary(); @@ -463,9 +472,13 @@ private void AddToAdditionalResources(ITaskItem resource, Dictionary lazyLoadAssembliesNoExtension, string fileName, out ITaskItem lazyLoadedAssembly) { - return (lazyLoadedAssembly = LazyLoadedAssemblies?.SingleOrDefault(a => a.ItemSpec == fileName)) != null; + var extension = Path.GetExtension(fileName); + if (extension == ".dll" || extension == Utils.WebcilInWasmExtension) + fileName = Path.GetFileNameWithoutExtension(fileName); + + return lazyLoadAssembliesNoExtension.TryGetValue(fileName, out lazyLoadedAssembly); } private Version? parsedTargetFrameworkVersion;