-
Notifications
You must be signed in to change notification settings - Fork 25.2k
[Pre4] Blazor WASM performance profiling #35243
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
Merged
Merged
Changes from 24 commits
Commits
Show all changes
29 commits
Select commit
Hold shift + click to select a range
4b31efd
[Pre4] Blazor WASM performance profiling
guardrex f21854e
Updates
guardrex 895c85a
Updates
guardrex 3a30f36
Updates
guardrex fc1421a
Updates
guardrex 5fc2d59
Updates
guardrex e7f5715
Updates
guardrex f7a0f32
Updates
guardrex 9875831
Updates
guardrex 4d15ead
Updates
guardrex fcb5be4
Updates
guardrex de64081
Updates
guardrex 0fa510a
Updates
guardrex 235427c
Updates
guardrex f602f94
Updates
guardrex e03832e
Updates
guardrex 62ebbfa
Apply suggestions from code review
guardrex 17c049c
Updates
guardrex 808743e
Merge branch 'guardrex/blazor-perf' of https://github.com/dotnet/AspN…
guardrex a40c1a5
Updates
guardrex 45c2d58
Updates
guardrex e6fae23
Updates
guardrex 7aed823
Apply suggestions from code review
guardrex c69cc66
Updates
guardrex f02f26c
Updates
guardrex 0b8ab11
Apply suggestions from code review
guardrex c1042ee
Updates
guardrex cfe55f7
Updates
guardrex 7a3322d
Updates
guardrex File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
--- | ||
title: ASP.NET Core Blazor app download size performance best practices | ||
author: guardrex | ||
description: Tips for reducing app download size in ASP.NET Core Blazor apps and avoiding common performance problems. | ||
monikerRange: '>= aspnetcore-3.1' | ||
ms.author: riande | ||
ms.custom: mvc | ||
ms.date: 05/02/2025 | ||
uid: blazor/performance/app-download-size | ||
--- | ||
# ASP.NET Core Blazor app download size performance best practices | ||
|
||
[!INCLUDE[](~/includes/not-latest-version.md)] | ||
|
||
:::moniker range=">= aspnetcore-6.0" | ||
|
||
## Runtime relinking | ||
|
||
For information on how runtime relinking minimizes an app's download size, see <xref:blazor/tooling/webassembly#runtime-relinking>. | ||
|
||
:::moniker-end | ||
|
||
## Use `System.Text.Json` | ||
|
||
Blazor's JS interop implementation relies on <xref:System.Text.Json>, which is a high-performance JSON serialization library with low memory allocation. Using <xref:System.Text.Json> shouldn't result in additional app payload size over adding one or more alternate JSON libraries. | ||
|
||
For migration guidance, see [How to migrate from `Newtonsoft.Json` to `System.Text.Json`](/dotnet/standard/serialization/system-text-json-migrate-from-newtonsoft-how-to). | ||
|
||
## Intermediate Language (IL) trimming | ||
|
||
*This section only applies to client-side Blazor scenarios.* | ||
|
||
:::moniker range=">= aspnetcore-5.0" | ||
|
||
Trimming unused assemblies from a Blazor WebAssembly app reduces the app's size by removing unused code in the app's binaries. For more information, see <xref:blazor/host-and-deploy/configure-trimmer>. | ||
|
||
:::moniker-end | ||
|
||
:::moniker range="< aspnetcore-5.0" | ||
|
||
[Linking a Blazor WebAssembly app](xref:blazor/host-and-deploy/configure-linker) reduces the app's size by trimming unused code in the app's binaries. The Intermediate Language (IL) Linker is only enabled when building in `Release` configuration. To benefit from this, publish the app for deployment using the [`dotnet publish`](/dotnet/core/tools/dotnet-publish) command with the [-c|--configuration](/dotnet/core/tools/dotnet-publish#options) option set to `Release`: | ||
|
||
```dotnetcli | ||
dotnet publish -c Release | ||
``` | ||
|
||
:::moniker-end | ||
|
||
## Lazy load assemblies | ||
|
||
*This section only applies to client-side Blazor scenarios.* | ||
|
||
Load assemblies at runtime when the assemblies are required by a route. For more information, see <xref:blazor/webassembly-lazy-load-assemblies>. | ||
|
||
## Compression | ||
|
||
*This section only applies to Blazor WebAssembly apps.* | ||
|
||
When a Blazor WebAssembly app is published, the output is statically compressed during publish to reduce the app's size and remove the overhead for runtime compression. Blazor relies on the server to perform content negotiation and serve statically-compressed files. | ||
|
||
After an app is deployed, verify that the app serves compressed files. Inspect the **Network** tab in a browser's [developer tools](https://developer.mozilla.org/docs/Glossary/Developer_Tools) and verify that the files are served with `Content-Encoding: br` (Brotli compression) or `Content-Encoding: gz` (Gzip compression). If the host isn't serving compressed files, follow the instructions in <xref:blazor/host-and-deploy/webassembly/index#compression>. | ||
|
||
## Disable unused features | ||
|
||
*This section only applies to client-side Blazor scenarios.* | ||
|
||
Blazor WebAssembly's runtime includes the following .NET features that can be disabled for a smaller payload size. | ||
|
||
Blazor WebAssembly carries globalization resources required to display values, such as dates and currency, in the user's culture. If the app doesn't require localization, you may configure the app to [support the invariant culture](xref:blazor/globalization-localization#invariant-globalization), which is based on the `en-US` culture. Apply the `<InvariantGlobalization>` MSBuild property with a value of `true` in the app's project file (`.csproj`): | ||
|
||
```xml | ||
<PropertyGroup> | ||
<InvariantGlobalization>true</InvariantGlobalization> | ||
</PropertyGroup> | ||
``` | ||
|
||
:::moniker range=">= aspnetcore-8.0" | ||
|
||
Adopting [invariant globalization](xref:blazor/globalization-localization#invariant-globalization) only results in using non-localized timezone names. To trim timezone code and data from the app, apply the `<InvariantTimezone>` MSBuild property with a value of `true` in the app's project file (`.csproj`): | ||
|
||
```xml | ||
<PropertyGroup> | ||
<InvariantTimezone>true</InvariantTimezone> | ||
</PropertyGroup> | ||
``` | ||
|
||
> [!NOTE] | ||
> [`<BlazorEnableTimeZoneSupport>`](xref:blazor/performance/app-download-size#disable-unused-features) overrides an earlier `<InvariantTimezone>` setting. We recommend removing the `<BlazorEnableTimeZoneSupport>` setting. | ||
|
||
:::moniker-end | ||
|
||
:::moniker range="< aspnetcore-8.0" | ||
|
||
A data file is included to make timezone information correct. If the app doesn't require this feature, consider disabling it by setting the `<BlazorEnableTimeZoneSupport>` MSBuild property to `false` in the app's project file: | ||
|
||
```xml | ||
<PropertyGroup> | ||
<BlazorEnableTimeZoneSupport>false</BlazorEnableTimeZoneSupport> | ||
</PropertyGroup> | ||
``` | ||
|
||
:::moniker-end | ||
|
||
:::moniker range="< aspnetcore-5.0" | ||
|
||
Collation information is included to make APIs such as <xref:System.StringComparison.InvariantCultureIgnoreCase?displayProperty=nameWithType> work correctly. If you're certain that the app doesn't require the collation data, consider disabling it by setting the `BlazorWebAssemblyPreserveCollationData` MSBuild property in the app's project file to `false`: | ||
|
||
```xml | ||
<PropertyGroup> | ||
<BlazorWebAssemblyPreserveCollationData>false</BlazorWebAssemblyPreserveCollationData> | ||
</PropertyGroup> | ||
``` | ||
|
||
guardrex marked this conversation as resolved.
Show resolved
Hide resolved
|
||
:::moniker-end | ||
|
||
## Additional resources | ||
|
||
[Configuring and hosting .NET WebAssembly applications](https://github.com/dotnet/runtime/blob/main/src/mono/wasm/features.md) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
--- | ||
title: ASP.NET Core Blazor performance best practices | ||
author: guardrex | ||
description: Tips for increasing the performance of ASP.NET Core Blazor apps and avoiding common performance problems. | ||
monikerRange: '>= aspnetcore-3.1' | ||
ms.author: riande | ||
ms.custom: mvc | ||
ms.date: 04/16/2025 | ||
uid: blazor/performance/index | ||
--- | ||
# ASP.NET Core Blazor performance best practices | ||
|
||
[!INCLUDE[](~/includes/not-latest-version.md)] | ||
|
||
Blazor is optimized for high performance in most realistic application UI scenarios. However, the best performance depends on developers adopting the correct patterns and features. | ||
|
||
> [!NOTE] | ||
> The code examples in this node of articles adopt [nullable reference types (NRTs) and .NET compiler null-state static analysis](xref:migration/50-to-60#nullable-reference-types-nrts-and-net-compiler-null-state-static-analysis), which are supported in ASP.NET Core in .NET 6 or later. | ||
|
||
:::moniker range=">= aspnetcore-6.0" | ||
|
||
## Ahead-of-time (AOT) compilation | ||
|
||
Ahead-of-time (AOT) compilation compiles a Blazor app's .NET code directly into native WebAssembly for direct execution by the browser. AOT-compiled apps result in larger apps that take longer to download, but AOT-compiled apps usually provide better runtime performance, especially for apps that execute CPU-intensive tasks. For more information, see <xref:blazor/tooling/webassembly#ahead-of-time-aot-compilation>. | ||
|
||
:::moniker-end |
117 changes: 117 additions & 0 deletions
117
aspnetcore/blazor/performance/javascript-interoperability.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
--- | ||
title: ASP.NET Core Blazor JavaScript interoperability (JS interop) performance best practices | ||
author: guardrex | ||
description: Tips for improving JS interop performance in ASP.NET Core Blazor apps and avoiding common performance problems. | ||
monikerRange: '>= aspnetcore-3.1' | ||
ms.author: riande | ||
ms.custom: mvc | ||
ms.date: 04/16/2025 | ||
uid: blazor/performance/js-interop | ||
--- | ||
# ASP.NET Core Blazor JavaScript interoperability (JS interop) performance best practices | ||
|
||
[!INCLUDE[](~/includes/not-latest-version.md)] | ||
|
||
Calls between .NET and JavaScript require additional overhead because: | ||
|
||
* Calls are asynchronous. | ||
* Parameters and return values are JSON-serialized to provide an easy-to-understand conversion mechanism between .NET and JavaScript types. | ||
|
||
Additionally for server-side Blazor apps, these calls are passed across the network. | ||
|
||
## Avoid excessively fine-grained calls | ||
|
||
Since each call involves some overhead, it can be valuable to reduce the number of calls. Consider the following code, which stores a collection of items in the browser's [`localStorage`](https://developer.mozilla.org/docs/Web/API/Window/localStorage): | ||
|
||
```csharp | ||
private async Task StoreAllInLocalStorage(IEnumerable<TodoItem> items) | ||
{ | ||
foreach (var item in items) | ||
{ | ||
await JS.InvokeVoidAsync("localStorage.setItem", item.Id, | ||
JsonSerializer.Serialize(item)); | ||
} | ||
} | ||
``` | ||
|
||
The preceding example makes a separate JS interop call for each item. Instead, the following approach reduces the JS interop to a single call: | ||
|
||
```csharp | ||
private async Task StoreAllInLocalStorage(IEnumerable<TodoItem> items) | ||
{ | ||
await JS.InvokeVoidAsync("storeAllInLocalStorage", items); | ||
} | ||
``` | ||
|
||
The corresponding JavaScript function stores the whole collection of items on the client: | ||
|
||
```javascript | ||
function storeAllInLocalStorage(items) { | ||
items.forEach(item => { | ||
localStorage.setItem(item.id, JSON.stringify(item)); | ||
}); | ||
} | ||
``` | ||
|
||
For Blazor WebAssembly apps, rolling individual JS interop calls into a single call usually only improves performance significantly if the component makes a large number of JS interop calls. | ||
|
||
## Consider the use of synchronous calls | ||
|
||
:::moniker range=">= aspnetcore-5.0" | ||
|
||
### Call JavaScript from .NET | ||
|
||
[!INCLUDE[](~/blazor/includes/js-interop/synchronous-js-interop-call-js.md)] | ||
|
||
### Call .NET from JavaScript | ||
|
||
[!INCLUDE[](~/blazor/includes/js-interop/synchronous-js-interop-call-dotnet.md)] | ||
|
||
:::moniker-end | ||
|
||
:::moniker range="< aspnetcore-5.0" | ||
|
||
[!INCLUDE[](~/blazor/includes/js-interop/synchronous-js-interop-call-js.md)] | ||
|
||
:::moniker-end | ||
|
||
:::moniker range=">= aspnetcore-5.0 < aspnetcore-7.0" | ||
|
||
## Consider the use of unmarshalled calls | ||
|
||
*This section only applies to Blazor WebAssembly apps.* | ||
|
||
When running on Blazor WebAssembly, it's possible to make unmarshalled calls from .NET to JavaScript. These are synchronous calls that don't perform JSON serialization of arguments or return values. All aspects of memory management and translations between .NET and JavaScript representations are left up to the developer. | ||
|
||
> [!WARNING] | ||
> While using <xref:Microsoft.JSInterop.IJSUnmarshalledRuntime> has the least overhead of the JS interop approaches, the JavaScript APIs required to interact with these APIs are currently undocumented and subject to breaking changes in future releases. | ||
|
||
```javascript | ||
function jsInteropCall() { | ||
return BINDING.js_to_mono_obj("Hello world"); | ||
} | ||
``` | ||
|
||
```razor | ||
@inject IJSRuntime JS | ||
|
||
@code { | ||
protected override void OnInitialized() | ||
{ | ||
var unmarshalledJs = (IJSUnmarshalledRuntime)JS; | ||
var value = unmarshalledJs.InvokeUnmarshalled<string>("jsInteropCall"); | ||
} | ||
} | ||
``` | ||
|
||
:::moniker-end | ||
|
||
:::moniker range=">= aspnetcore-7.0" | ||
|
||
## Use JavaScript `[JSImport]`/`[JSExport]` interop | ||
|
||
JavaScript `[JSImport]`/`[JSExport]` interop for Blazor WebAssembly apps offers improved performance and stability over the JS interop API in framework releases prior to ASP.NET Core in .NET 7. | ||
|
||
For more information, see <xref:blazor/js-interop/import-export-interop>. | ||
|
||
:::moniker-end |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.