How to optimize WebAssembly to 1MB?

How to optimize WebAssembly to 1MB?

Optimize WebAssembly to 1MB

Last updated 1/30/2023 10:35 PM
Token
12 min read
Category
Blazor
Tags
.NET Blazor Wasm WebAssembly

Blazor WebAssembly Loading Optimization

Optimizing Blazor WebAssembly loading is aimed at the initial loading of WebAssembly. Since Blazor WebAssembly loads all .NET Core assemblies into the browser on first load, and may reference many third-party DLLs during use, this can lead to slow loading.

Optimization Solutions:

1. Compression

When publishing a Blazor WebAssembly application, the output content is statically compressed during the publish process, reducing the application size and eliminating runtime compression overhead. The following compression algorithms are used:

Obtain the JavaScript Brotli decoder from the google/brotli GitHub repository. The minified decoder file is named decode.min.js and is located in the js folder of the repository.

Modify the wwwroot/index.html file code, add autostart="false" to prevent the default startup from loading assemblies:

<script src="_framework/blazor.webassembly.js" autostart="false"></script>

After the Blazor <script> tag and before the closing </body> tag, add the following JavaScript code <script> block:

<script type="module">
  import { BrotliDecode } from './decode.min.js';
  Blazor.start({
    loadBootResource: function (type, name, defaultUri, integrity) {
    // Note: Compression will not be enabled when using localhost here
      if (type !== 'dotnetjs' && location.hostname !== 'localhost') {
        return (async function () {
          const response = await fetch(defaultUri + '.br', { cache: 'no-cache' });
          if (!response.ok) {
            throw new Error(response.statusText);
          }
          const originalResponseBuffer = await response.arrayBuffer();
          const originalResponseArray = new Int8Array(originalResponseBuffer);
          const decompressedResponseArray = BrotliDecode(originalResponseArray);
          const contentType = type ===
            'dotnetwasm' ? 'application/wasm' : 'application/octet-stream';
          return new Response(decompressedResponseArray,
            { headers: { 'content-type': contentType } });
        })();
      }
    }
  });
</script>

The compression solution will reduce loading time to approximately one-third the size of the compressed DLLs. The effect is shown below:

When using the autostart="false" flag, assemblies are not loaded on startup; loading will be executed in the code block above. By default, .br files are loaded.

2. Lazy Loading Assemblies

Improve Blazor WebAssembly application startup performance by deferring the loading of application assemblies until they are needed. This approach is called "lazy loading."

Break down the Blazor WebAssembly project into finer details and improve the initial loading time by lazy loading assemblies. We'll use a case study to explain lazy loading assemblies.

Create an empty Blazor WebAssembly project: Project name Demand:

Disable HTTPS and use Progressive Web Application:

Create a Razor class library with the project name Demand.Components, then create the project with default options:

Create a Components.razor file and delete unnecessary files. The effect is shown below:

Add the following code to Components.razor:

@inject NavigationManager NavigationManager @page "/components"

<div>
  <h1>Components</h1>
</div>
<button @onclick="Goto">Go to Home</button>
@code { private void Goto() { NavigationManager.NavigateTo("/"); } }

In the Demand project, reference the Demand.Components project.

Modify the App.razor file with the following code:

@using System.Reflection @using
Microsoft.AspNetCore.Components.WebAssembly.Services @*
Note: WebAssembly injects this by default, but Server does not.
In Server, manually inject builder.Services.AddScoped<LazyAssemblyLoader
  >(); *@ @inject LazyAssemblyLoader AssemblyLoader

  <Router
    AppAssembly="@typeof(App).Assembly"
    AdditionalAssemblies="@lazyLoadedAssemblies"
    OnNavigateAsync="@OnNavigateAsync"
  >
    <Found Context="routeData">
      <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
      <FocusOnNavigate RouteData="@routeData" Selector="h1" />
    </Found>
    <NotFound>
      <PageTitle>Not found</PageTitle>
      <LayoutView Layout="@typeof(MainLayout)">
        <p role="alert">Sorry, there's nothing at this address.</p>
      </LayoutView>
    </NotFound>
  </Router>

  @code { private List<Assembly>
    lazyLoadedAssemblies = new(); private async Task
    OnNavigateAsync(NavigationContext args) { try { if (args.Path ==
    "components") { // Customize the assemblies that Demand.Components depends on here var assemblies
    = await AssemblyLoader.LoadAssembliesAsync(new[] { "Demand.Components.dll"
    }); // Add to the route assembly scanning list lazyLoadedAssemblies.AddRange(assemblies); } }
    catch (Exception ex) { } } }</Assembly
  ></LazyAssemblyLoader
>

Handle the assemblies that need to be loaded for specific route components.

Open the Demand project file. In Debug mode, you can add the following ignore list:

<ItemGroup>
    <BlazorWebAssemblyLazyLoad Include="System.Xml.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Xml.XmlSerializer.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Xml.XmlDocument.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Xml.XPath.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Xml.XPath.XDocument.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Xml.XDocument.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Xml.Serialization.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Xml.ReaderWriter.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Xml.Linq.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Windows.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Net.Quic.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.IO.Compression.ZipFile.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Runtime.Numerics.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Collections.Immutable.dll" />
    <BlazorWebAssemblyLazyLoad Include="Microsoft.Win32.Registry.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Web.HttpUtility.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.ValueTuple.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Security.AccessControl.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Net.Mail.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Net.NameResolution.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.IO.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.IO.UnmanagedMemoryStream.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.IO.Pipes.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.IO.Pipes.AccessControl.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.IO.Pipelines.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.IO.FileSystem.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.IO.FileSystem.Watcher.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.IO.FileSystem.Primitives.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.IO.FileSystem.DriveInfo.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.IO.FileSystem.AccessControl.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Data.Common.dll" />
    <BlazorWebAssemblyLazyLoad Include="Microsoft.CSharp.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Console.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Core.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Data.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Data.DataSetExtensions.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Drawing.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Drawing.Primitives.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Diagnostics.TraceSource.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Diagnostics.Tools.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Diagnostics.TextWriterTraceListener.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Diagnostics.StackTrace.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Diagnostics.Process.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Diagnostics.FileVersionInfo.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Diagnostics.DiagnosticSource.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Diagnostics.Debug.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Diagnostics.Contracts.dll" />
    <BlazorWebAssemblyLazyLoad Include="Microsoft.AspNetCore.Authorization.dll" />
    <BlazorWebAssemblyLazyLoad Include="Microsoft.AspNetCore.Components.Forms.dll" />
    <BlazorWebAssemblyLazyLoad Include="Microsoft.AspNetCore.Metadata.dll" />
    <BlazorWebAssemblyLazyLoad Include="Microsoft.Extensions.Configuration.Binder.dll" />
    <BlazorWebAssemblyLazyLoad Include="Microsoft.Extensions.FileProviders.Abstractions.dll" />
    <BlazorWebAssemblyLazyLoad Include="Microsoft.Extensions.FileProviders.Physical.dll" />
    <BlazorWebAssemblyLazyLoad Include="Microsoft.Extensions.Configuration.FileExtensions.dll" />
    <BlazorWebAssemblyLazyLoad Include="Microsoft.Extensions.FileSystemGlobbing.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.IO.MemoryMappedFiles.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.IO.IsolatedStorage.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.IO.Compression.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.IO.Compression.FileSystem.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.IO.Compression.Brotli.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Formats.Tar.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Formats.Asn1.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Net.WebSockets.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Private.DataContractSerialization.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Private.Xml.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Security.Cryptography.dll" />
    <BlazorWebAssemblyLazyLoad Include="Microsoft.VisualBasic.dll" />
    <BlazorWebAssemblyLazyLoad Include="Microsoft.VisualBasic.Core.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Threading.Tasks.Dataflow.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Text.Encoding.CodePages.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Net.WebSockets.Client.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Private.Xml.Linq.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Text.RegularExpressions.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Net.Sockets.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Net.WebClient.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Net.WebProxy.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Net.Ping.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Security.Cryptography.X509Certificates.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Net.WebHeaderCollection.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Security.Cryptography.OpenSsl.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Security.Cryptography.Encoding.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Security.Cryptography.Csp.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Security.Cryptography.Cng.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Security.Claims.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Security.Cryptography.Algorithms.dll" />
    <BlazorWebAssemblyLazyLoad Include="Microsoft.Win32.Primitives.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Net.HttpListener.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.AppContext.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Net.NetworkInformation.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Net.Requests.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Net.Primitives.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Net.Security.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Net.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Net.ServicePoint.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Net.Http.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Globalization.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Globalization.Calendars.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Globalization.Extensions.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Net.Http.Json.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Web.dll" />
    <BlazorWebAssemblyLazyLoad Include="WindowsBase.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Resources.Writer.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Resources.ResourceManager.dll" />
    <BlazorWebAssemblyLazyLoad Include="System.Resources.Reader.dll" />
</ItemGroup>

These are some less commonly used assemblies. If the following error occurs, delete the assemblies that cannot be found and configure on-demand loading,

However, if you use the on-demand loading configuration above, an exception may occur during publishing, as shown in the figure below; the error is caused by Blazor WebAssembly using trimming by default during publishing. Since the following assemblies are not used, they are configured for on-demand loading after trimming, but they have already been trimmed, so the on-demand loading assemblies cannot be found. Just delete the assemblies that report errors; this only occurs during publishing. Debug mode can still use the on-demand loading configuration above, which can make debugging faster:

Next step.

Add the on-demand loading configuration for the specified project. Configure the Demand.Components project:

<ItemGroup>
    <BlazorWebAssemblyLazyLoad Include="Demand.Components.dll" />
</ItemGroup>

Modify the Pages/Index.razor file code:

@page "/" @inject NavigationManager NavigationManager
<h1>Hello, world!</h1>

<button @onclick="Goto">Go to components</button>
@code { private void Goto() { NavigationManager.NavigateTo("/components"); } }

Then start the project, open F12 developer tools, click Application, find Storage, and click Clear site data (assemblies will be cached after the first load):

Click Network, then refresh the page. We can see that Demand.Components.dll is not loaded here, but the assemblies shown here are:

Then click the button on the page:

Now go back to the Network tab in the debug tools. We can see that Demand.Components.dll has been loaded. This assembly is loaded only when we use it, and it will not be reloaded when entering the interface a second time:

Then publish the project (remember the issue mentioned above about assembly loss due to trimming causing on-demand loading to fail; just remove the trimmed assemblies from the on-demand loading configuration):

Then use docker compose to deploy an nginx proxy to view the effect:

Create a docker-compose.yml file and add the following code. In the same directory as docker-compose.yml, create two folders: conf.d and wwwroot:

services:
  nginx:
    image: nginx:stable-alpine
    container_name: nginx
    volumes:
      - ./conf.d:/etc/nginx/conf.d
      - ./wwwroot:/wwwroot
    ports:
      - 811:80

Create webassembly.conf in conf.d and add the following code:

server {
    listen 80;
    server_name http://localhost;

    location / {
        root /wwwroot;
        index index.html;
    }

}

Then, in the directory where docker-compose.yml is located, use docker-compose up -d to start the nginx service.

Open a browser and visit http://127.0.0.1:811/ (do not use localhost, as compression is disabled by default). Then open F12 debug tools, clear storage in the Application tab, open the Network option, refresh the browser, and the loading is complete. Optimized to 2.3MB, compression is enabled, and unused assemblies are trimmed during publishing:

Extreme Optimization to 1MB

Add the following configuration to the Demand project file. This configuration disables some features, such as globalization:

<PublishTrimmed>true</PublishTrimmed>
<InvariantGlobalization>true</InvariantGlobalization>
<BlazorEnableTimeZoneSupport>false</BlazorEnableTimeZoneSupport>
<EventSourceSupport>false</EventSourceSupport>
<HttpActivityPropagationSupport>false</HttpActivityPropagationSupport>
<EnableUnsafeBinaryFormatterSerialization>false</EnableUnsafeBinaryFormatterSerialization>
<MetadataUpdaterSupport>false</MetadataUpdaterSupport>
<UseNativeHttpHandler>true</UseNativeHttpHandler>

Then follow the steps above to publish it and deploy it to nginx.

Check the loading size in the Network tab. We can see it has come down to 1MB. Removing some JS files would make it even smaller, thus significantly solving the loading issue (suggestion from Xiaoye Kun):

Conclusion

If you have a better optimization solution, you can contact me.

Shared by Token.

Blazor discussion group: 452761192

This article is reproduced.

Author: Token

Original title: How to optimize WebAssembly to 1MB?

Original link: https://www.cnblogs.com/hejiale010426/p/17076817.html

Keep Exploring

Related Reading

More Articles
Same category / Same tag 11/6/2024

Why My Blog Website Returned to Blazor

The development of the blog website has gone through many hardships, with nearly 10 versions including MVC, Vue, Go, etc. Now it has returned to Blazor and adopted static SSR, resulting in a significant speed increase and successful launch.

Continue Reading
Same category / Same tag 2/29/2024

Data Display Can Also Be Done Like This in Winform

In the process of developing Winform, data display functionality is often required. Previously, the gridcontrol control was commonly used. Today, through an example, I would like to introduce how to use the table component from Ant Design Blazor for data display in a Winform Blazor Hybrid application.

Continue Reading
Same category / Same tag 2/29/2024

Can the Winform interface also look good?

A few days ago, I introduced using Blazor Hybrid in Winform, and mentioned that with the Blazor UI, our Winform programs can be designed to look better. Next, I will illustrate with an example of drawing in Winform Blazor Hybrid, hoping it helps you.

Continue Reading
Same category / Same tag 1/7/2024

Code Workshop 'Article Title URL Alias Generator' Launched

Code Workshop is a new open-source project by the webmaster that provides web online tools, cross-platform desktop and mobile applications. The webmaster is committed to bringing you a more efficient and convenient experience. Today, the webmaster is proud to launch the 'Article Title URL Alias Generator', helping you easily create URL aliases for article titles, improving SEO and user experience. Come to Code Workshop and explore more practical tools!

Continue Reading