.NET 9 improves JavaScript module importing for Blazor

November 13, 2024 · 3 minute read · Tags: aspnet | blazor | net9

So you’ve set out to build your shiny new web app using Blazor.

Blazor’s component model is more than up to the task and you spend a few weeks happily building out UI to meet your requirements.

But then you run into a requirement to integrate with a JavaScript library.

Curse it! Now you have to figure out how to make calls to a JavaScript library from your Blazor app.

This is exactly the kind of requirement that can get complicated, pretty quickly.

Especially if the JS dependency is packaged up as a JavaScript module.

Thankfully, importing JS modules into your Blazor app has become easier with recent releases, and .NET 9 addresses one of the biggest challenges.

Say we have a small JS module for showing a ’toast’ notification.

// JS/toast.js

export class NotificationHelper {    
	async showToast(message, type = 'info', options = {}) {            
		// details omitted
	}
}

export default new NotificationHelper();

In your Blazor component, if you’re using Blazor WASM or Blazor Server, you can import this module and invoke its methods like this:

@inject IJSRuntime JSRuntime;

<button @onclick="@(()=>ShowNotification("Hello World"))">
    Show Notification
</button>
@code {
    
	async Task ShowNotification(string message)
	{
		var module = await 
			JSRuntime.InvokeAsync<IJSObjectReference>
			("import", "./js/toast.js");
	
			await module.InvokeVoidAsync(
				"default.showToast", message, "success");
	}
    
}

When a user clicks the button this will:

  1. Load the contents of toast.js and import it as a JS module.
  2. invoke the showToast method on the default export from that JS module (NotificationHelper)

But what if you make changes to that JS script and re-deploy your app.

Just as we saw last time with stylesheets, the browser will try to cache the JS module and your users will likely find themselves stuck with an old version.

Cue chaos and confusion!

Thankfully .NET 9 has a solution.

When you switch to the new static asset middleware:

app.MapStaticAssets();

You can also add something called ImportMap to your App.razor page.

<head>    
	<!-- existing code-->
	<ImportMap />
</head>

Run this in the browser, view the page source, and you’ll see that Blazor has identified all the JS modules in your app, and created an import map for them.

<script type="importmap">{
	"imports": {
		// other imports
		"./js/toast.js": "./js/toast.voa2fr9sx9.js",
	}
}</script>

When you publish your app .NET 9 creates a fingerprinted version of your JS modules, with a unique filename based on their contents.

./js/toast.voa2fr9sx9.js in this example.

The import map then ensures any attempt to import these modules is redirected to the fingerprinted version.

You can see this in the browser dev tools (assuming the component is running via Blazor WASM).

image.png

Note how the request for the JS file is using the fingerprinted filename.

With this your users will always get the latest version.

Oh, and its worth noting the new static asset middleware compresses these resources by default too, so the browser gets to download significantly smaller JS and CSS files.

– Jon

Join the Practical ASP.NET Newsletter

Ship better Blazor apps, faster. One practical tip every Tuesday.

I respect your email privacy. Unsubscribe with one click.

    Next up

    How to use .NET 9 to ensure users always get the latest version of your stylesheets
    .NET 9 changes how static files are served, and it solves a long-standing problem
    Preventing double clicks in Blazor components
    Guard against duplicate events in your Blazor app
    Avoiding interactivity with Blazor?
    Sometimes a little HTML and CSS is all you need