Summary
We want to support customizing the way Blazor applications are initialized client-side and provide hooks for libraries to initialize before Blazor starts as well as after Blazor has started.
Motivation
- Application developers have problems using Blazor webassembly behind corporate VPN software and enterprise firewalls that incorrectly detect dlls, and have no way around it.
- Library authors can't initialize their libraries at the right time and don't have a way to tell Blazor they are ready other than to force the user to manually start Blazor after their library has initialized.
- Library authors are forced to tell users to add a script tag to their document in addition to reference their RCL.
Goals
- Enable libraries/packages to fully customize how Blazor webassembly loads.
- Enable libraries to initialize themselves before Blazor starts up.
- Enable configuring Blazor settings without requiring additional code changes.
Non-goals
- Create a package manager for ASP.NET Core.
- Resolve dependency conflicts.
- Resolve ordering issues.
Scenarios
- As a developer I want to customize how Blazor webassembly loads.
- As a developer I want to customize the settings for Blazor without updating
index.html or _Host.cshtml.
- As a library author I want to initialize my library before Blazor loads.
Risks
- Libraries could abuse this to bring up large bundles/lots of files, increasing the concerns that Blazor is big and "slow" to load.
- We can't prevent this, however we could discourage this by producing warnings at build time or tools that point out what libraries are contributing the to the size increase (similar to "bundlephobia" in the npm world)
- In general loading many JS files is not that much of a problem in HTTP/2.0, HTTP/3.0 as it is on HTTP/1.1 other than for the "cascade loading" of nested import statements.
- There are things that users can do to prevent this like using
link rel="modulepreload".
- Blazor will produce warnings when a library takes too long to load/initialize.
- Users can choose to collect all the JS files and perform their own bundling and deduplication.
Interaction with other parts of the framework
- Progressive web app service assets manifest
- Blazor boot json
Detailed design
We will support customizing how Blazor applications load completely via support for custom Blazor javascript initializers.
- Applications will be able to pass the url to download the blazor boot manifest from (including inlining it by using a dataurl)
- The manifest will contain a new initializers section containing a list of files with a list of files to import via javascript import statements.
- We will not offer any guarantees on the import order.
- Customers need to figure out dependencies via
import statements within their initializers.
- We will invoke all the imports in parallel and won't be responsible for resolving conflicts.
- We will check for an
initialize function in the module exports.
- In case we find it, we will invoke it passing the blazor options, which allows configuring the rest of the boot process.
SDK
- We will detect a new file type rz.js (should we consider this extension to be razor.js and cshtml.js)
- This is our convention which users could override.
- We will produce a new file
<<project-name>>.modules.json containing a list of modules in alphabetical order (for deterministically generating the file).
- Blazor WASM will inline the contents of the file on the blazor.boot.json manifest and disable its generation.
- We will have to modify blazor.boot.json to add the list of generated files.
We will produce an "experimental" nuget package that customizes the build output from Blazor to produce a "bundle" or update the dlls and includes a javascript initializer that is able to understand this format and load blazor in a complete custom way. We will give this package for customers to try out and see if it solves issues with their corporate firewalls VPNs.
Runtime
- Blazor webassembly will take in the manifest url as an input to the loading process.
- Blazor will load the list of initalizers and use an
import statement to load each one of them.
- We won't wait for all of them to be done loading or executing and will produce warnings if they take too long to load or too long to execute.
- We want to encourage developers to not degrade the Blazor loading experience in their libraries.
- Blazor will search each imported module for two symbols
onBlazorStarting and onBlazorStarted and will invoke them at the right time if they are present, however we won't require them to be present, so that if a library is just initializing itself or wants to make itself ready before blazor starts, it just works.
Drawbacks
This might introduce slow startup times if libraries take a long time to start. We can mitigate this in several ways:
- We won't allow a library initializer to prevent Blazor from starting.
- We will log to the console if a library takes too long to load.
Considered alternatives
We considered creating an alternative integrated way to customize the Blazor loads, however we can't predict that any alternative loading scheme that we produce fixes all issues related to loading Blazor behind a corporate firewall or VPN. This solution enables libraries to emerge with different strategies to solve these problems.
Open questions
- Should this just be a "Razor" feature?
- Since this is implemented for RCLs it might make sense that we have a task that produces an "app import bundle" and just have Blazor disable that feature. This means that things like MVC can also benefit from this for loading RCLs with JS (like fluent) even if their components are not used.
- Should libraries like "these" compose?
- For altering how blazor loads, I believe the answer is no.
- Module load URls are based on the path to the file, will we have to adapt these based on the final location or base url of the document?
Detailed design
You can add a file $(PackageId).lib.module.json
We will automatically discover those files as library initializers and do one or two things:
-
On Blazor webassembly they'll get included into the blazor.boot.json manifest.
-
On non webassembly builds, we generate a file App.modules.json and do the following
- Blazor server will write the file to the html doc the first time a component is emitted on to the page.
- Blazor desktop will request the file and continue the initialization from there.
-
MVC and Razor pages applications won't do anything by default with the manifest, and it is up to developers to provide a small snippet to perform the loading whenver and however they see fit.
For blazor, there will be a beforeBlazorStarts and an afterBlazorStarts callbacks that can be used to setup state either before and just after the application starts.
Detailed scenarios:
As a developer I want to customize how Blazor webassembly loads.
I can write a js initializer that exports a beforeBlazorStarts callback receiving the options and the blazor.boot.json data)
export function beforeBlazorStarts(options, bootJson){
const bundleIntegrity = bootJson.extensions.myExtension['bundle.bdl'];
const bundle = await fetch('bundle.bdl', {integrity: bundleIntegrity);
options.bootResourceLoader = myCustomLoader;
function myCustomLoader(...)
{
// Some logic
return bundle[resource];
}
}
As a developer I want to customize the settings for Blazor without updating index.html or _Host.cshtml
I can write an initializer to modify the options:
export function beforeBlazorStarts(options, bootJson){
options.logLevel = LogLevel.Debug;
}
As a library author I want to initialize my library before Blazor loads, potentially preventing blazor from starting
One case for this is when we are handling OAuth callbacks as part of an auth flow and want to prevent Blazor from loading on to the callback url on a hidden iframe.
export function beforeBlazorStarts(...,...){
// Do initialization stuff
if(shouldStopBlazorFromStarting){
return "Do not start blazor, I am about to redirect and close the window/frame";
}
}
Sample library initializer
MyPackage.lib.module.js
export function beforeBlazorStarts(options, bootManifest)
{
options.bootResourceLoader = custom
}
export function afterBlazorStarted()
{
}
Summary
We want to support customizing the way Blazor applications are initialized client-side and provide hooks for libraries to initialize before Blazor starts as well as after Blazor has started.
Motivation
Goals
Non-goals
Scenarios
index.htmlor_Host.cshtml.Risks
link rel="modulepreload".Interaction with other parts of the framework
Detailed design
We will support customizing how Blazor applications load completely via support for custom Blazor javascript initializers.
importstatements within their initializers.initializefunction in the module exports.SDK
<<project-name>>.modules.jsoncontaining a list of modules inalphabetical order(for deterministically generating the file).We will produce an "experimental" nuget package that customizes the build output from Blazor to produce a "bundle" or update the dlls and includes a javascript initializer that is able to understand this format and load blazor in a complete custom way. We will give this package for customers to try out and see if it solves issues with their corporate firewalls VPNs.
Runtime
importstatement to load each one of them.onBlazorStartingandonBlazorStartedand will invoke them at the right time if they are present, however we won't require them to be present, so that if a library is just initializing itself or wants to make itself ready before blazor starts, it just works.Drawbacks
This might introduce slow startup times if libraries take a long time to start. We can mitigate this in several ways:
Considered alternatives
We considered creating an alternative integrated way to customize the Blazor loads, however we can't predict that any alternative loading scheme that we produce fixes all issues related to loading Blazor behind a corporate firewall or VPN. This solution enables libraries to emerge with different strategies to solve these problems.
Open questions
Detailed design
You can add a file $(PackageId).lib.module.json
We will automatically discover those files as library initializers and do one or two things:
On Blazor webassembly they'll get included into the blazor.boot.json manifest.
On non webassembly builds, we generate a file App.modules.json and do the following
MVC and Razor pages applications won't do anything by default with the manifest, and it is up to developers to provide a small snippet to perform the loading whenver and however they see fit.
For blazor, there will be a beforeBlazorStarts and an afterBlazorStarts callbacks that can be used to setup state either before and just after the application starts.
Detailed scenarios:
As a developer I want to customize how Blazor webassembly loads.
I can write a js initializer that exports a
beforeBlazorStartscallback receiving the options and the blazor.boot.json data)As a developer I want to customize the settings for Blazor without updating index.html or _Host.cshtml
I can write an initializer to modify the options:
As a library author I want to initialize my library before Blazor loads, potentially preventing blazor from starting
One case for this is when we are handling OAuth callbacks as part of an auth flow and want to prevent Blazor from loading on to the callback url on a hidden iframe.
Sample library initializer
MyPackage.lib.module.js