Skip to content

Fix: Apply Blazor WebAssembly JS Initializer configurations (loadBootResource, environment) before runtime initialization#65696

Open
muharremtekin wants to merge 11 commits intodotnet:mainfrom
muharremtekin:fix/issue-54358-js-initializers-timing
Open

Fix: Apply Blazor WebAssembly JS Initializer configurations (loadBootResource, environment) before runtime initialization#65696
muharremtekin wants to merge 11 commits intodotnet:mainfrom
muharremtekin:fix/issue-54358-js-initializers-timing

Conversation

@muharremtekin
Copy link
Copy Markdown

Fixes #54358

Description

This PR fixes a bug where configurations set inside beforeStart JS Initializer callbacks (such as loadBootResource, environment, and configureRuntime) were ignored in Blazor WebAssembly because the .NET runtime was being initialized before these callbacks had a chance to apply their modifications.

Root Cause

In MonoPlatform.ts, the calls to dotnet.withResourceLoader(), dotnet.withApplicationEnvironment(), etc., were being made synchronously inside createRuntimeInstance(), capturing the options object before the onConfigLoaded callback was fired. Since JS Initializer callbacks are invoked via fetchAndInvokeInitializers inside onConfigLoaded, any modifications they made to options were lost.

The Fix

Moved the dotnet.with*() configuration calls from createRuntimeInstance() into the onConfigLoaded callback, ensuring they are executed after fetchAndInvokeInitializers has run and applied user configurations.

Is this safe? Yes. According to the dotnet/runtime source code (src/mono/browser/runtime/loader/config.ts), onConfigLoaded is explicitly awaited by the runtime exactly for this scenario:

"scripts need to be loaded before onConfigLoaded because Blazor calls beforeStart export in onConfigLoaded"

This guarantees that WebAssembly resource downloads and initialization will wait for these configurations to be applied.

Testing

To ensure this behavior is correct and doesn't regress:

  1. Unit Tests (Jest)

    • Exported prepareRuntimeConfig as @internal.
    • Added MonoPlatform.InitializerTiming.test.ts to strictly verify the chronological execution order:
      • fetchAndInvokeInitializers must fire before withResourceLoader.
      • fetchAndInvokeInitializers must fire before withApplicationEnvironment.
      • Direct Blazor.start(options) properties are still respected.
  2. E2E Integration Tests (Selenium)

    • Modified the existing JS Initializer E2E tests (JsInitializersTest.cs and BlazorWebJsInitializersTest.cs) to strictly assert that the custom loadBootResource mock (from BasicTestApp.lib.module.js) successfully intercepts and logs network requests, proving the configuration was passed successfully to the runtime.

Risk Assessment

Low. This only delays the reading of options properties until onConfigLoaded, which is the correct lifecycle hook intended by the runtime. All 40 Web.JS test suites and E2E tests pass.

@muharremtekin muharremtekin requested a review from a team as a code owner March 7, 2026 13:05
@github-actions github-actions Bot added the area-blazor Includes: Blazor, Razor Components label Mar 7, 2026
@dotnet-policy-service dotnet-policy-service Bot added the community-contribution Indicates that the PR has been added by a community member label Mar 7, 2026
@muharremtekin
Copy link
Copy Markdown
Author

@dotnet-policy-service agree

Cast fetchAndInvokeInitializers mock to 'any' to prevent strict TypeScript
type checking on mock return values after import paths were updated to use
relative paths (../src/...) instead of module aliases.
@lewing lewing requested a review from pavelsavara March 7, 2026 17:12
@javiercn
Copy link
Copy Markdown
Member

javiercn commented Mar 9, 2026

Thanks for your contribution.

I remember looking into this in the past. I believe there is a smaller/simpler fix. If I recall correctly the issue was were trying to initialize the runtime before calling the initializers (beware I took a really quick look, maybe your fix is the same).

@pavelsavara
Copy link
Copy Markdown
Member

cc @maraf

@muharremtekin
Copy link
Copy Markdown
Author

@javiercn Yes I think it's the same thing, I just added lots of tests to ensure it fixed correctly.

onConfigLoadedCallback?.(loadedConfig);

jsInitializer = await fetchAndInvokeInitializers(options, loadedConfig);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@maraf why we are doing fetchAndInvokeInitializers here and not only in runtime ?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initializers is a blazor feature, not a wasm feature

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know wasm has it too, it got added there too when we did a refactor a couple years later. The blazor feature predates the wasm feature #34798

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, why we didn't finish the refactoring and unify it ?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we can't. This is used by libraries and we shouldn't break them, and I believe there were some things that could be done in the Blazor version that couldn't be done in the wasm version.

To be very clear, we can't just remove this feature for blazor. it's used on server, ssr, webview and (wasm) where wasm also added its own hooks. And it's the pattern libraries have to bring their JS. This should keep working for backwards compatibility.

@dotnet-policy-service
Copy link
Copy Markdown
Contributor

Looks like this PR hasn't been active for some time and the codebase could have been changed in the meantime.
To make sure no conflicting changes have occurred, please rerun validation before merging. You can do this by leaving an /azp run comment here (requires commit rights), or by simply closing and reopening.

@dotnet-policy-service dotnet-policy-service Bot added the pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun label Mar 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-blazor Includes: Blazor, Razor Components community-contribution Indicates that the PR has been added by a community member pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unable to use JS Initializer for custom boot resource loader

3 participants