Skip to content

Commit 23609ab

Browse files
maraflewing
andauthored
[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 bef50ee. * Fix the references * Update Versions.props * Update Versions.props --------- Co-authored-by: Larry Ewing <[email protected]> * [browser] Extension agnostic lazy assembly loading (#104793) * Fix damage from branch merging --------- Co-authored-by: Larry Ewing <[email protected]>
1 parent f7f4a23 commit 23609ab

File tree

5 files changed

+82
-25
lines changed

5 files changed

+82
-25
lines changed

src/mono/browser/runtime/lazyLoading.ts

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,38 @@ import { AssetEntry } from "./types";
77

88
export async function loadLazyAssembly (assemblyNameToLoad: string): Promise<boolean> {
99
const resources = loaderHelpers.config.resources!;
10-
const originalAssemblyName = assemblyNameToLoad;
1110
const lazyAssemblies = resources.lazyAssembly;
1211
if (!lazyAssemblies) {
1312
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.");
1413
}
1514

15+
let assemblyNameWithoutExtension = assemblyNameToLoad;
16+
if (assemblyNameToLoad.endsWith(".dll"))
17+
assemblyNameWithoutExtension = assemblyNameToLoad.substring(0, assemblyNameToLoad.length - 4);
18+
else if (assemblyNameToLoad.endsWith(".wasm"))
19+
assemblyNameWithoutExtension = assemblyNameToLoad.substring(0, assemblyNameToLoad.length - 5);
20+
21+
const assemblyNameToLoadDll = assemblyNameWithoutExtension + ".dll";
22+
const assemblyNameToLoadWasm = assemblyNameWithoutExtension + ".wasm";
1623
if (loaderHelpers.config.resources!.fingerprinting) {
1724
const map = loaderHelpers.config.resources!.fingerprinting;
1825
for (const fingerprintedName in map) {
1926
const nonFingerprintedName = map[fingerprintedName];
20-
if (nonFingerprintedName == assemblyNameToLoad) {
27+
if (nonFingerprintedName == assemblyNameToLoadDll || nonFingerprintedName == assemblyNameToLoadWasm) {
2128
assemblyNameToLoad = fingerprintedName;
2229
break;
2330
}
2431
}
2532
}
2633

2734
if (!lazyAssemblies[assemblyNameToLoad]) {
28-
throw new Error(`${assemblyNameToLoad} must be marked with 'BlazorWebAssemblyLazyLoad' item group in your project file to allow lazy-loading.`);
35+
if (lazyAssemblies[assemblyNameToLoadDll]) {
36+
assemblyNameToLoad = assemblyNameToLoadDll;
37+
} else if (lazyAssemblies[assemblyNameToLoadWasm]) {
38+
assemblyNameToLoad = assemblyNameToLoadWasm;
39+
} else {
40+
throw new Error(`${assemblyNameToLoad} must be marked with 'BlazorWebAssemblyLazyLoad' item group in your project file to allow lazy-loading.`);
41+
}
2942
}
3043

3144
const dllAsset: AssetEntry = {
@@ -38,7 +51,7 @@ export async function loadLazyAssembly (assemblyNameToLoad: string): Promise<boo
3851
return false;
3952
}
4053

41-
let pdbNameToLoad = changeExtension(originalAssemblyName, ".pdb");
54+
let pdbNameToLoad = assemblyNameWithoutExtension + ".pdb";
4255
let shouldLoadPdb = false;
4356
if (loaderHelpers.config.debugLevel != 0 && loaderHelpers.isDebuggingSupported()) {
4457
shouldLoadPdb = Object.prototype.hasOwnProperty.call(lazyAssemblies, pdbNameToLoad);
@@ -81,12 +94,3 @@ export async function loadLazyAssembly (assemblyNameToLoad: string): Promise<boo
8194
load_lazy_assembly(dll, pdb);
8295
return true;
8396
}
84-
85-
function changeExtension (filename: string, newExtensionWithLeadingDot: string) {
86-
const lastDotIndex = filename.lastIndexOf(".");
87-
if (lastDotIndex < 0) {
88-
throw new Error(`No extension to replace in '${filename}'`);
89-
}
90-
91-
return filename.substring(0, lastDotIndex) + newExtensionWithLeadingDot;
92-
}

src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LazyLoadingTests.cs

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,32 @@ public LazyLoadingTests(ITestOutputHelper output, SharedBuildPerTestClassFixture
2020
{
2121
}
2222

23-
[Fact, TestCategory("no-fingerprinting")]
24-
public async Task LoadLazyAssemblyBeforeItIsNeeded()
23+
public static IEnumerable<object?[]> LoadLazyAssemblyBeforeItIsNeededData()
24+
{
25+
string[] data = ["wasm", "dll", "NoExtension"];
26+
return data.Select(d => new object[] { d, data });
27+
}
28+
29+
[Theory, TestCategory("no-fingerprinting")]
30+
[MemberData(nameof(LoadLazyAssemblyBeforeItIsNeededData))]
31+
public async Task LoadLazyAssemblyBeforeItIsNeeded(string lazyLoadingTestExtension, string[] allLazyLoadingTestExtensions)
2532
{
2633
CopyTestAsset("WasmBasicTestApp", "LazyLoadingTests", "App");
27-
BuildProject("Debug");
34+
BuildProject("Debug", extraArgs: $"-p:LazyLoadingTestExtension={lazyLoadingTestExtension}");
2835

29-
var result = await RunSdkStyleAppForBuild(new(Configuration: "Debug", TestScenario: "LazyLoadingTest"));
36+
// We are running the app and passing all possible lazy extensions to test matrix of all possibilities.
37+
// We don't need to rebuild the application to test how client is trying to load the assembly.
38+
foreach (var clientLazyLoadingTestExtension in allLazyLoadingTestExtensions)
39+
{
40+
var result = await RunSdkStyleAppForBuild(new(
41+
Configuration: "Debug",
42+
TestScenario: "LazyLoadingTest",
43+
BrowserQueryString: new Dictionary<string, string> { ["lazyLoadingTestExtension"] = clientLazyLoadingTestExtension }
44+
));
3045

31-
Assert.True(result.TestOutput.Any(m => m.Contains("FirstName")), "The lazy loading test didn't emit expected message with JSON");
32-
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");
46+
Assert.True(result.TestOutput.Any(m => m.Contains("FirstName")), "The lazy loading test didn't emit expected message with JSON");
47+
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");
48+
}
3349
}
3450

3551
[Fact]

src/mono/wasm/testassets/WasmBasicTestApp/App/WasmBasicTestApp.csproj

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,19 @@
66
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
77
</PropertyGroup>
88

9+
<!-- Lazy loading test various extensions -->
10+
<PropertyGroup>
11+
<LazyAssemblyExtension Condition="'$(LazyLoadingTestExtension)' == 'dll'">.dll</LazyAssemblyExtension>
12+
<LazyAssemblyExtension Condition="'$(LazyLoadingTestExtension)' == 'wasm'">.wasm</LazyAssemblyExtension>
13+
<LazyAssemblyExtension Condition="'$(LazyLoadingTestExtension)' == 'NoExtension'"></LazyAssemblyExtension>
14+
<LazyAssemblyExtension Condition="'$(LazyLoadingTestExtension)' == ''">$(WasmAssemblyExtension)</LazyAssemblyExtension>
15+
</PropertyGroup>
16+
917
<ItemGroup>
1018
<ProjectReference Include="..\Library\Json.csproj" />
1119
</ItemGroup>
1220

1321
<ItemGroup>
14-
<BlazorWebAssemblyLazyLoad Include="Json$(WasmAssemblyExtension)" />
22+
<BlazorWebAssemblyLazyLoad Include="Json$(LazyAssemblyExtension)" />
1523
</ItemGroup>
1624
</Project>

src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,23 @@ try {
138138
break;
139139
case "LazyLoadingTest":
140140
if (params.get("loadRequiredAssembly") !== "false") {
141-
await INTERNAL.loadLazyAssembly(`Json${assemblyExtension}`);
141+
let lazyAssemblyExtension = assemblyExtension;
142+
switch (params.get("lazyLoadingTestExtension")) {
143+
case "wasm":
144+
lazyAssemblyExtension = ".wasm";
145+
break;
146+
case "dll":
147+
lazyAssemblyExtension = ".dll";
148+
break;
149+
case "NoExtension":
150+
lazyAssemblyExtension = "";
151+
break;
152+
default:
153+
lazyAssemblyExtension = assemblyExtension;
154+
break;
155+
}
156+
157+
await INTERNAL.loadLazyAssembly(`Json${lazyAssemblyExtension}`);
142158
}
143159
exports.LazyLoadingTest.Run();
144160
exit(0);

src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,15 @@ public void WriteBootJson(Stream output, string entryAssemblyName)
176176
{
177177
var endpointByAsset = Endpoints.ToDictionary(e => e.GetMetadata("AssetFile"));
178178

179+
var lazyLoadAssembliesWithoutExtension = (LazyLoadedAssemblies ?? Array.Empty<ITaskItem>()).ToDictionary(l =>
180+
{
181+
var extension = Path.GetExtension(l.ItemSpec);
182+
if (extension == ".dll" || extension == Utils.WebcilInWasmExtension)
183+
return Path.GetFileNameWithoutExtension(l.ItemSpec);
184+
185+
return l.ItemSpec;
186+
});
187+
179188
var remainingLazyLoadAssemblies = new List<ITaskItem>(LazyLoadedAssemblies ?? Array.Empty<ITaskItem>());
180189
var resourceData = result.resources;
181190

@@ -194,7 +203,7 @@ public void WriteBootJson(Stream output, string entryAssemblyName)
194203
var resourceName = Path.GetFileName(resource.GetMetadata("OriginalItemSpec"));
195204
var resourceRoute = Path.GetFileName(endpointByAsset[resource.ItemSpec].ItemSpec);
196205

197-
if (TryGetLazyLoadedAssembly(resourceName, out var lazyLoad))
206+
if (TryGetLazyLoadedAssembly(lazyLoadAssembliesWithoutExtension, resourceName, out var lazyLoad))
198207
{
199208
MapFingerprintedAsset(resourceData, resourceRoute, resourceName);
200209
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)
220229
else if (string.Equals("symbol", assetTraitValue, StringComparison.OrdinalIgnoreCase))
221230
{
222231
MapFingerprintedAsset(resourceData, resourceRoute, resourceName);
223-
if (TryGetLazyLoadedAssembly($"{fileName}.dll", out _) || TryGetLazyLoadedAssembly($"{fileName}{Utils.WebcilInWasmExtension}", out _))
232+
if (TryGetLazyLoadedAssembly(lazyLoadAssembliesWithoutExtension, fileName, out _))
224233
{
225234
Log.LogMessage(MessageImportance.Low, "Candidate '{0}' is defined as a lazy loaded symbols file.", resource.ItemSpec);
226235
resourceData.lazyAssembly ??= new ResourceHashesByNameDictionary();
@@ -463,9 +472,13 @@ private void AddToAdditionalResources(ITaskItem resource, Dictionary<string, Add
463472
}
464473
}
465474

466-
private bool TryGetLazyLoadedAssembly(string fileName, out ITaskItem lazyLoadedAssembly)
475+
private static bool TryGetLazyLoadedAssembly(Dictionary<string, ITaskItem> lazyLoadAssembliesNoExtension, string fileName, out ITaskItem lazyLoadedAssembly)
467476
{
468-
return (lazyLoadedAssembly = LazyLoadedAssemblies?.SingleOrDefault(a => a.ItemSpec == fileName)) != null;
477+
var extension = Path.GetExtension(fileName);
478+
if (extension == ".dll" || extension == Utils.WebcilInWasmExtension)
479+
fileName = Path.GetFileNameWithoutExtension(fileName);
480+
481+
return lazyLoadAssembliesNoExtension.TryGetValue(fileName, out lazyLoadedAssembly);
469482
}
470483

471484
private Version? parsedTargetFrameworkVersion;

0 commit comments

Comments
 (0)