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:
- Add code which calls a JavaScript function which updates the progress
- Make sure our application uses the modified blazor.webassembly.js
- 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
- GitHub project, source code and GitHub Action can be found here
- See jsMind.Blazor Demo application
- See Corona Dashboard Nederland (this one uses the same idea, but different code)