From d962f1679960a39bab7756b70d1e0780a1fcb291 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Sun, 21 Jun 2026 13:22:12 +0200 Subject: [PATCH] idempotent lazy --- .../wasm/Wasm.Build.Tests/LazyLoadingTests.cs | 23 +++++++++++++++++++ .../WasmBasicTestApp/App/wwwroot/main.js | 10 +++++++- .../libs/Common/JavaScript/loader/assets.ts | 21 ++++++++++------- 3 files changed, 45 insertions(+), 9 deletions(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/LazyLoadingTests.cs b/src/mono/wasm/Wasm.Build.Tests/LazyLoadingTests.cs index 65011962374221..24d3f0ce5e199e 100644 --- a/src/mono/wasm/Wasm.Build.Tests/LazyLoadingTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/LazyLoadingTests.cs @@ -50,6 +50,29 @@ public async Task LoadLazyAssemblyBeforeItIsNeeded(string lazyLoadingTestExtensi } } + [Fact] + public async Task LoadLazyAssemblyTwiceIsIdempotent() + { + // Regression test for the lazy loader rewriting an asset's virtualPath in place while + // fetching it, which broke the lookup (and dedup) on a subsequent load of the same + // assembly - e.g. when Blazor fires OnNavigate more than once - and surfaced as + // " must be marked with 'BlazorWebAssemblyLazyLoad' item group ...". + Configuration config = Configuration.Debug; + ProjectInfo info = CopyTestAsset(config, false, TestAsset.WasmBasicTestApp, "LazyLoadingTests"); + BuildProject(info, config, new BuildOptions(ExtraMSBuildArgs: "-p:TestLazyLoading=true")); + + RunResult result = await RunForBuildWithDotnetRun(new BrowserRunOptions( + config, + TestScenario: "LazyLoadingTest", + BrowserQueryString: new NameValueCollection { { "loadLazyAssemblyTwice", "true" } } + )); + + Assert.True(result.TestOutput.Any(m => m.Contains("firstJsonLoad=true")), "The first lazy load should report that it loaded the assembly"); + Assert.True(result.TestOutput.Any(m => m.Contains("secondJsonLoad=false")), "The second lazy load of the same assembly should be an idempotent no-op"); + Assert.True(result.TestOutput.Any(m => m.Contains("FirstName")), "The lazy loading test didn't emit expected message with JSON"); + Assert.False(result.ConsoleOutput.Any(m => m.Contains("must be marked with 'BlazorWebAssemblyLazyLoad'")), "Reloading an already-loaded lazy assembly must not throw the 'must be marked' error"); + } + [Fact] public async Task FailOnMissingLazyAssembly() { diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js b/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js index 6a2a75fdd44127..b6450ded78426c 100644 --- a/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js +++ b/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js @@ -270,7 +270,15 @@ try { break; } - await INTERNAL.loadLazyAssembly(`Json${lazyAssemblyExtension}`); + const firstJsonLoad = await INTERNAL.loadLazyAssembly(`Json${lazyAssemblyExtension}`); + testOutput(`firstJsonLoad=${firstJsonLoad}`); + if (params.get("loadLazyAssemblyTwice") === "true") { + // Regression test: loading the same lazy assembly a second time must be an + // idempotent no-op (returns false) and must not throw + // "must be marked with 'BlazorWebAssemblyLazyLoad'". + const secondJsonLoad = await INTERNAL.loadLazyAssembly(`Json${lazyAssemblyExtension}`); + testOutput(`secondJsonLoad=${secondJsonLoad}`); + } exports.LazyLoadingTest.Run(); await INTERNAL.loadLazyAssembly(`LazyLibrary${lazyAssemblyExtension}`); const { LazyLibrary } = await getAssemblyExports("LazyLibrary"); diff --git a/src/native/libs/Common/JavaScript/loader/assets.ts b/src/native/libs/Common/JavaScript/loader/assets.ts index bb8313ad8f64e7..5d09b009855530 100644 --- a/src/native/libs/Common/JavaScript/loader/assets.ts +++ b/src/native/libs/Common/JavaScript/loader/assets.ts @@ -261,6 +261,10 @@ export async function fetchSatelliteAssemblies(culturesToLoad: string[]): Promis await Promise.all(promises); } +function lazyAssetFileName(virtualPath: string): string { + return virtualPath.substring(virtualPath.lastIndexOf("/") + 1); +} + export async function fetchLazyAssembly(assemblyNameToLoad: string): Promise { const lazyAssemblies = loaderConfig.resources?.lazyAssembly; if (!lazyAssemblies) { @@ -273,12 +277,17 @@ export async function fetchLazyAssembly(assemblyNameToLoad: string): Promise