Blog

Show a loading progress indicator for a Blazor WebAssembly Application

This blogpost describes a possible way to show a progress-bar indicator when a Blazor WebAssembly Application is loading resources.

“Loading…”

By default when a Blazor WebAssembly is started, you see “Loading…” in the browser. In the background all the required resources (JavaScript files) and dll’s from your application are loaded. Once all files have been loaded, the application will start and will be running and displayed in the browser.

In case you have a big Blazor WebAssembly application (or a slower) connection, you just see this “Loading…” for several seconds without any feedback on the progress. This article describes a possible way to fix this.

blazor.webassembly.js

The JavaScript file which is responsible for booting and loading the required resources is the blazor.webassembly.js. However, the problem is that this file is minified and it’s not possible to hook into a a loading file event. So the only way of adding a file-load hook is by modifying this file.

WebAssemblyResourceLoader

The main logic which takes care of loading resources is the WebAssemblyResourceLoader. In this TypeScript file there is a method named loadResources which loads all the resources based on the list defined in the blazor.boot.json file.

The code looks like this (excerpt):

export class WebAssemblyResourceLoader {
  // ...

  loadResources(resources: ResourceList, url: (name: string) => string, resourceType: WebAssemblyBootResourceType): LoadingResource[] {
    return Object.keys(resources)
      .map(name => this.loadResource(name, url(name), resources[name], resourceType));
  }

  loadResource(name: string, url: string, contentHash: string, resourceType: WebAssemblyBootResourceType): LoadingResource {
    const response = this.cacheIfUsed
      ? this.loadResourceWithCaching(this.cacheIfUsed, name, url, contentHash, resourceType)
      : this.loadResourceWithoutCaching(name, url, contentHash, resourceType);

    return { name, url, response };
  }

  // ...

This code will be also present in the minified version from the blazor.webassembly.js:

This minified code looks like this:

e.prototype.loadResources=function(e,t,n){var r=this;return Object.keys(e).map((function(o){return r.loadResource(o,t(o),e[o],n)}))}

Modifications

So when we can replace this with our own code, we can add a hook to report the load progress.

This can be achieved in three steps:

  1. Add code which calls a JavaScript function which updates the progress
  2. Make sure our application uses the modified blazor.webassembly.js
  3. Add a JavaScript function in our application to update a progress-bar.

1. Replace the code

In the blazor.webassembly.js, replace the string

return r.loadResource(o,t(o),e[o],n)

with

var p = r.loadResource(o,t(o),e[o],n); p.response.then((x) => { if (typeof window.loadResourceCallback === “function”) { window.loadResourceCallback(Object.keys(e).length, o, x);}}); return p;

The new code will add a “then” on the promise (“p.response”) to call a function “window.loadResourceCallback” with the total number of requested resources (dll’s), the filename, and the current response (which contains the url from the resource which was downloaded).

Replacing this code can be done manually, or during the build/publish steps in a pipeline (Azure Pipelines or GitHub Actions).

Because my example Blazor WebAssembly is hosted on GitHub (see this blog), I decided to do the replacement using the GitHub deploy action.

2. GitHub Action

A new step is added to replace a string in a file, for this I used gha-find-replace. The steps looks like this:

 - name: Patch blazor.webassembly.js
      uses: jacobtomlinson/gha-find-replace@master
      with:
        find: "return r.loadResource\\(o,t\\(o\\),e\\[o\\],n\\)"
        replace: "var p = r.loadResource(o,t(o),e[o],n); p.response.then((x) => { if (typeof window.loadResourceCallback === 'function') { window.loadResourceCallback(Object.keys(e).length, o, x);}}); return p;"
        include: "blazor.webassembly.js"

3. Updating the progress

Now add some html to add a bootstrap progress bar and add a JavaScript callback function to update that progress bar:

<app>
    <div class="container-fluid">
        <div class="row">
            <div class="col-2"></div>
            <div class="col-8">
                <p></p>
                Loading Blazor WebAssembly...
                <br />
                <div class="progress">
                    <div id="progressbar" class="progress-bar progress-bar-striped active" role="progressbar" style="width:0"></div>
                </div>
            </div>
            <div class="col-2"></div>
        </div>
    </div>
</app>

<script type="text/javascript">
    var i = 0;
    window.loadResourceCallback = (total, name, response) => {
        if (name.endsWith('.dll')) {
            i++;
            const value = parseInt((i * 100.0) / total);
            const pct = value + '%';

            const progressbar = document.getElementById('progressbar');
            progressbar.style.width = pct;
            console.log(i + '/' + total + ' (' + pct + ') ' + name);
        }
    }
</script>

The JavaScript code above will act on load callbacks from filenames which end with dll.

Example

The example below is a screen-capture using slow network settings and gives an impression on how the loading progress works.

Todo

Currently it only registers when a dll is loaded, the dotnet.wasm (which is ~700KB) is not yet tracked, however there should also be way to detect when this file has loaded.

Any updates or comments are welcome!

References