Skip to content

Allow import of Blazor and DotNet JS objects from Blazor WASM startup script #48974

Closed as not planned
@mvromer

Description

@mvromer

Summary

As part of trying out ideas to enable multiple Blazor WebAssembly apps to co-exist on the same page (a la micro frontends as described in #38128), one thing that would be helpful is getting access to the Blazor and DotNet JavaScript objects setup as part the Blazor WASM startup script (blazor.webassembly.js) through a means other than the global window object. Ideally these objects should be retrievable when the Blazor WASM startup script is loaded dynamically, such as when lazy loading a Blazor WASM application in cases like (but not limited to) Blazor-based micro frontends.

Motivation and goals

As I mentioned in my comments here and here, my main motivation for supporting Blazor-based MFEs is to enable teams I work with who are more comfortable with Blazor to continue developing Blazor-based frontend modules that plug into an app shell at runtime in the browser to form one cohesive app portal experience. At the same time, I want to enable teams to build modules using JS-based technologies in those situations where it's more beneficial, such as when the module needs to interop heavily with JS-based components (e.g., Mapbox for maps, Highcharts for time series strip charts, Tableau for dashboards, and Cesium for geo-spatial visualization).

While I understand #38128 has been pushed out of the .NET 8 timeline, I wanted to continue investigating on my end things my teams can do in the short term to make Blazor-based MFEs more feasible. As a proof of concept, I made a set of patches to the Blazor WASM startup script (available on GitHub here), and I've been able to get some basic Blazor-based MFEs working (with a demo app published on GitHub here).

One constraint I see with my current approach is that I need to ensure that I only load one Blazor WASM app at a time, and by "load" I mean import the app's Blazor WASM startup script. The reason is I need to grab a reference to the Blazor and DotNet objects that correspond to a particular MFE so that I can properly manage its lifecycle when I mount and unmount that MFE. I believe the one-at-a-time limitation exists due to a possible race condition I call out in my demo app here, but the basic idea is that if I try to load more than one startup script at the same time, I could possibly end up in a situation where one MFE's script will overwrite window.Blazor and window.DotNet with its runtime objects before my code has had a chance to capture the corresponding references for the other MFE.

If there were a way to get these references other than through the global namespace, my belief is that this potential race condition can get mitigated.

In scope

Given a URL to a Blazor-based MFE's startup script, I would ideally like to dynamically load it from JavaScript like so and, in the process, get the Blazor and DotNet objects created when the startup script is executed by the browser:

const blazorStartup = await import(blazorStartupScriptUrl);
const mfeBlazor = blazorStartup.Blazor;
const mfeDotNet = blazorStartup.DotNet;

// Do some other MFE init, followed by...
await mfeBlazor.start(/* arguments typically passed to Blazor.start */);

I think the ideal way of exposing this would be if the Blazor startup script were a true JS module that exported the Blazor and DotNet objects. This would allow JS code to resolve those via a dynamic import using the import() syntax.

Out of scope

I'm really only considering Blazor WASM apps at this point. Other uses of Blazor really aren't in scope with this request (and I'm not sure how relevant they would be here).

Risks / unknowns

One possible challenge of switching the startup script to a JS module is that <script> tags would need to specify the type="module" attribute to ensure the script is executed as a JS module. This would likely be a breaking change for existing Blazor WASM apps.

Just as a straw man, a couple mitigations may include:

  • Provide a separate build of the script (e.g., blazor.webassembly.esm.js) that is basically the same script with exports for the Blazor and DotNet objects included.
  • Make the existing blazor.webassembly.js script simply a shim that dynamically imports the ESM variant. It sounds like based on the last paragraph on dynamic imports described here on MDN, a dynamic import of an ES module should be possible in "classic script" environments within a browser. I'm hand waving a bit here, but that should help avoid breaking existing Blazor WASM applications.

Examples

See the snippet above to get a basic idea of how I would roughly expect this import mechanism to work. For a more complete context of where I would ideally like to use this capability, the bootstrap function in my demo app offers a decent example here. In that example, I'm reading the Blazor and DotNet objects from the window object, but getting them from the module object resolved from the dynamic import of the Blazor startup script would be the ideal outcome.

Metadata

Metadata

Assignees

No one assigned

    Labels

    ✔️ Resolution: DuplicateResolved as a duplicate of another issuePillar: Technical DebtStatus: Resolvedarea-blazorIncludes: Blazor, Razor Componentsdesign-proposalThis issue represents a design proposal for a different issue, linked in the descriptionenhancementThis issue represents an ask for new feature or an enhancement to an existing one

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions