Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JS initializers 8.0 #30920

Merged
merged 3 commits into from
Nov 3, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 73 additions & 5 deletions aspnetcore/blazor/fundamentals/startup.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,27 +104,95 @@ To define a JS initializer, add a JS module to the project named `{NAME}.lib.mod

The module exports either or both of the following conventional functions:

:::moniker-end

:::moniker range=">= aspnetcore-8.0"

<!-- UPDATE 8.0 Remove NOTE -->

> [!NOTE]
> JS initializers for Blazor Web Apps is an upcoming feature that will appear with the release of .NET 8 in mid-November.

For Blazor Web Apps:

* `beforeWebStart(options)`: Called before the Blazor Web App starts. For example, `beforeStart` is used to customize the loading process, logging level, and other options. Receives the Blazor options (`options`).
guardrex marked this conversation as resolved.
Show resolved Hide resolved
* `afterWebStarted(blazor)`: Called after the Blazor Web App is ready to receive calls from JS. For example, `afterWebStarted` is used to initialize libraries by making JS interop calls and registering custom elements. The Blazor instance is passed to `afterWebStarted` as an argument (`blazor`).
guardrex marked this conversation as resolved.
Show resolved Hide resolved
guardrex marked this conversation as resolved.
Show resolved Hide resolved
* `beforeServerStart(options, extensions)`: Called before each interactive Server runtime is started. Receives SignalR circuit start options (`options`) and any extensions (`extensions`) added during publishing.
guardrex marked this conversation as resolved.
Show resolved Hide resolved
* `afterServerStarted(blazor)`: Called after each interactive Server runtime is started.
guardrex marked this conversation as resolved.
Show resolved Hide resolved
* `beforeWebAssemblyStart(options, extensions)`: Called before each interactive WebAssembly runtime is started. Receives the Blazor options (`options`) and any extensions (`extensions`) added during publishing. For example, options can specify the use of a custom [boot resource loader](xref:blazor/fundamentals/startup#load-client-side-boot-resources).
guardrex marked this conversation as resolved.
Show resolved Hide resolved
* `afterWebAssemblyStarted(blazor)`: Called after each interactive WebAssembly runtime is started.
guardrex marked this conversation as resolved.
Show resolved Hide resolved

> [!NOTE]
> Legacy JS initializers (`beforeStart`, `afterStarted`) are ***not*** invoked by default in a Blazor Web App. You can enable the legacy initializers to run with the `enableClassicInitializers` option. However, legacy initializer execution is unpredictable.
>
> ```html
> <script>
> Blazor.start({ enableClassicInitializers: true });
> </script>
> ```

For Blazor Server, Blazor WebAssembly, and Blazor Hybrid apps:

:::moniker-end

:::moniker range=">= aspnetcore-6.0"

* `beforeStart(options, extensions)`: Called before Blazor starts. For example, `beforeStart` is used to customize the loading process, logging level, and other options specific to the hosting model.
* Client-side, `beforeStart` receives the Blazor options (`options` in this section's examples) and any extensions (`extensions` in this section's examples) added during publishing. For example, options can specify the use of a custom [boot resource loader](xref:blazor/fundamentals/startup#load-client-side-boot-resources).
* Server-side, `beforeStart` receives SignalR circuit start options (`options` in this section's examples).
* Client-side, `beforeStart` receives the Blazor options (`options`) and any extensions (`extensions`) added during publishing. For example, options can specify the use of a custom [boot resource loader](xref:blazor/fundamentals/startup#load-client-side-boot-resources).
* Server-side, `beforeStart` receives SignalR circuit start options (`options`).
* In a [`BlazorWebView`](/mobile-blazor-bindings/walkthroughs/hybrid-hello-world#mainrazor-native-ui-page), no options are passed.
* `afterStarted`: Called after Blazor is ready to receive calls from JS. For example, `afterStarted` is used to initialize libraries by making JS interop calls and registering custom elements. The Blazor instance is passed to `afterStarted` as an argument (`blazor` in this section's example).
* `afterStarted(blazor)`: Called after Blazor is ready to receive calls from JS. For example, `afterStarted` is used to initialize libraries by making JS interop calls and registering custom elements. The Blazor instance is passed to `afterStarted` as an argument (`blazor`).

For the file name:

* If the JS initializers are consumed as a static asset in the project, use the format `{ASSEMBLY NAME}.lib.module.js`, where the `{ASSEMBLY NAME}` placeholder is the app's assembly name. For example, name the file `BlazorSample.lib.module.js` for a project with an assembly name of `BlazorSample`. Place the file in the app's `wwwroot` folder.
* If the JS initializers are consumed from an RCL, use the format `{LIBRARY NAME/PACKAGE ID}.lib.module.js`, where the `{LIBRARY NAME/PACKAGE ID}` placeholder is the project's library name or package identifier. For example, name the file `RazorClassLibrary1.lib.module.js` for an RCL with a package identifier of `RazorClassLibrary1`. Place the file in the library's `wwwroot` folder.

:::moniker-end

:::moniker range=">= aspnetcore-8.0"

<!-- UPDATE 8.0 Remove NOTE -->

> [!NOTE]
> JS initializers for Blazor Web Apps is an upcoming feature that will appear with the release of .NET 8 in mid-November.

For Blazor Web Apps:

The following example demonstrates JS initializers that load custom scripts before and after the Blazor Web App has started by appending them to the `<head>` in `beforeWebStart` and `afterWebStarted`:

```javascript
export function beforeWebStart() {
var customScript = document.createElement('script');
customScript.setAttribute('src', 'beforeStartScripts.js');
document.head.appendChild(customScript);
}

export function afterWebStarted() {
var customScript = document.createElement('script');
customScript.setAttribute('src', 'afterStartedScripts.js');
document.head.appendChild(customScript);
}
```

The preceding `beforeWebStart` example only guarantees that the custom script loads before Blazor starts. It doesn't guarantee that awaited promises in the script complete their execution before Blazor starts.

For Blazor Server, Blazor WebAssembly, and Blazor Hybrid apps:

:::moniker-end

:::moniker range=">= aspnetcore-6.0"

The following example demonstrates JS initializers that load custom scripts before and after Blazor has started by appending them to the `<head>` in `beforeStart` and `afterStarted`:

```javascript
export function beforeStart(options, extensions) {
export function beforeStart() {
guardrex marked this conversation as resolved.
Show resolved Hide resolved
var customScript = document.createElement('script');
customScript.setAttribute('src', 'beforeStartScripts.js');
document.head.appendChild(customScript);
}

export function afterStarted(blazor) {
guardrex marked this conversation as resolved.
Show resolved Hide resolved
export function afterStarted() {
guardrex marked this conversation as resolved.
Show resolved Hide resolved
var customScript = document.createElement('script');
customScript.setAttribute('src', 'afterStartedScripts.js');
document.head.appendChild(customScript);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,54 @@ When the package is referenced, it generates a bundle of the Blazor files during

The NuGet package leverages [JavaScript (JS) initializers](xref:blazor/js-interop/index#javascript-initializers) to automatically bootstrap a Blazor WebAssembly app from the bundle instead of using individual DLL files. JS initializers are used to change the Blazor [boot resource loader](xref:blazor/fundamentals/startup#load-boot-resources) and use the bundle.

:::moniker range=">= aspnetcore-8.0"

To create a JS initializer, add a JS file with the name `{NAME}.lib.module.js` to the `wwwroot` folder of the package project, where the `{NAME}` placeholder is the package identifier. For example, the file for the Microsoft package is named `Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.lib.module.js`. The exported functions `beforeWebAssemblyStart` and `afterWebAssemblyStarted` handle loading.

The JS initializers:

* Detect if the Publish Extension is available by checking for `extensions.multipart`, which is the extension name (`ExtensionName`) provided in the [Create an MSBuild task to customize the list of published files and define new extensions](#create-an-msbuild-task-to-customize-the-list-of-published-files-and-define-new-extensions) section.
* Download the bundle and parse the contents into a resources map using generated object URLs.
* Update the [boot resource loader (`options.loadBootResource`)](xref:blazor/fundamentals/startup#load-boot-resources) with a custom function that resolves the resources using the object URLs.
* After the app has started, revoke the object URLs to release memory in the `afterWebAssemblyStarted` function.

`Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle/wwwroot/Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.lib.module.js`:

```javascript
const resources = new Map();

export async function beforeWebAssemblyStart(options, extensions) {
if (!extensions || !extensions.multipart) {
return;
}

try {
const integrity = extensions.multipart['app.bundle'];
const bundleResponse =
await fetch('app.bundle', { integrity: integrity, cache: 'no-cache' });
const bundleFromData = await bundleResponse.formData();
for (let value of bundleFromData.values()) {
resources.set(value, URL.createObjectURL(value));
}
options.loadBootResource = function (type, name, defaultUri, integrity) {
return resources.get(name) ?? null;
}
} catch (error) {
console.log(error);
}
}

export async function afterWebAssemblyStarted(blazor) {
for (const [_, url] of resources) {
URL.revokeObjectURL(url);
}
}
```

:::moniker-end

:::moniker range="< aspnetcore-8.0"

To create a JS initializer, add a JS file with the name `{NAME}.lib.module.js` to the `wwwroot` folder of the package project, where the `{NAME}` placeholder is the package identifier. For example, the file for the Microsoft package is named `Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.lib.module.js`. The exported functions `beforeStart` and `afterStarted` handle loading.

The JS initializers:
Expand Down Expand Up @@ -348,6 +396,8 @@ export async function afterStarted(blazor) {
}
```

:::moniker-end

## Serve the bundle from the host server app

Due to security restrictions, ASP.NET Core doesn't serve the `app.bundle` file by default. A request processing helper is required to serve the file when it's requested by clients.
Expand Down