Toolbelt.Blazor.WebAssembly.ExtensibleDevServer.ImportMapExtension 10.0.12

dotnet add package Toolbelt.Blazor.WebAssembly.ExtensibleDevServer.ImportMapExtension --version 10.0.12
                    
NuGet\Install-Package Toolbelt.Blazor.WebAssembly.ExtensibleDevServer.ImportMapExtension -Version 10.0.12
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Toolbelt.Blazor.WebAssembly.ExtensibleDevServer.ImportMapExtension" Version="10.0.12">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Toolbelt.Blazor.WebAssembly.ExtensibleDevServer.ImportMapExtension" Version="10.0.12" />
                    
Directory.Packages.props
<PackageReference Include="Toolbelt.Blazor.WebAssembly.ExtensibleDevServer.ImportMapExtension">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Toolbelt.Blazor.WebAssembly.ExtensibleDevServer.ImportMapExtension --version 10.0.12
                    
#r "nuget: Toolbelt.Blazor.WebAssembly.ExtensibleDevServer.ImportMapExtension, 10.0.12"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Toolbelt.Blazor.WebAssembly.ExtensibleDevServer.ImportMapExtension@10.0.12
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Toolbelt.Blazor.WebAssembly.ExtensibleDevServer.ImportMapExtension&version=10.0.12
                    
Install as a Cake Addin
#tool nuget:?package=Toolbelt.Blazor.WebAssembly.ExtensibleDevServer.ImportMapExtension&version=10.0.12
                    
Install as a Cake Tool

Blazor WebAssembly Extensible Dev Server - ImportMap Extension

tests NuGet Package

Edit a collocated JavaScript module of a standalone Blazor WebAssembly app and just reload the page. No rebuild. And run the whole thing under a strict Content Security Policy, with no 'unsafe-inline'.

This package is for .NET 10. If your app targets .NET 11 or later, use Toolbelt.Blazor.WebAssembly.ExtensibleGateway.ImportMapExtension instead.

โš ๏ธ The problems

Both problems below live in wwwroot/index.html of a standalone Blazor WebAssembly project, once you turn on the .NET 10 SDK's HTML asset placeholder rewriting (OverrideHtmlAssetPlaceholders, more on this in How to use). This package fixes both of them.

Editing a .js file breaks the page until you rebuild

With that setting on, the SDK fingerprints your collocated JavaScript modules and writes an import map into wwwroot/index.html that lists them, along with the digest of each one.

<script type="importmap">{
  "imports": { "./App.razor.js": "./App.8lc58hsgxm.razor.js" },
  "integrity": { "./App.8lc58hsgxm.razor.js": "sha256-BIUnR0qePjnxaXNSj5iBv2jD/L2W6pBFf9oqSxkq+P4=" }
}</script>

That import map is only regenerated by a build. The development server, however, answers the fingerprinted URL with whatever is on disk right now. So the moment you edit an imported .js file and reload, the browser fetches the new content under the old name. The digest no longer matches, and your module is blocked.

Failed to find a valid digest in the 'integrity' attribute for resource
'https://localhost:7103/App.8lc58hsgxm.razor.js' with computed SHA-256 integrity
'6armd6IWrzZFPxwY0dx+y82ip/gIOt/KNpjkoGuDyTk='. The resource has been blocked.

dotnet watch does not fix this either. It treats the change as a static asset hot reload and serves the new content without rebuilding. That produces the same mismatch.

The import map is an inline script, so a strict policy refuses it

A policy like script-src 'self' 'wasm-unsafe-eval'; blocks the import map itself, and the app never starts. A nonce is not an option, because the SDK skips a <script type="importmap"> tag that carries any other attribute. A digest is the right answer, but its content changes whenever any asset changes, so nobody can maintain it by hand.

Blazor Web Apps have official guidance for this, because their ImportMap component accepts an integrity or nonce attribute. Standalone Blazor WebAssembly has no such component and no such guidance.

๐Ÿ› ๏ธ What this package does

This package bundles a development server extension and an MSBuild target. The extension rewrites the Blazor development server's response on the fly. The target runs at build and publish time. Together, these two parts solve the problems above in two ways.

Removing integrity from the import map while you develop

The development server extension removes the integrity member from the import map before it sends the page. This lets you edit a JavaScript module and reload the page without a rebuild. Publish never does this. The import map you ship still has its integrity.

Writing the import map's digest into your Content Security Policy

This part replaces the sha256-{importmap} placeholder with the real hash of the import map body. It runs both while you develop and when you publish. While you develop, it computes the hash after the integrity member is removed, so the digest always matches what the browser actually gets. This is what lets you drop 'unsafe-inline' from your script policy.

These two things must live in one package. Removing integrity changes the import map, so it also changes its hash. Whoever removes integrity has to be the one who writes the digest.

๐Ÿš€ How to use

1. Install

This package needs Toolbelt.Blazor.WebAssembly.ExtensibleDevServer during development, in place of the development server that the Blazor WebAssembly template gives you.

dotnet remove package Microsoft.AspNetCore.Components.WebAssembly.DevServer
dotnet add package Toolbelt.Blazor.WebAssembly.ExtensibleDevServer
dotnet add package Toolbelt.Blazor.WebAssembly.ExtensibleDevServer.ImportMapExtension

2. Make sure the SDK generates an import map

Add this to your project file (.csproj).

<PropertyGroup>
  <OverrideHtmlAssetPlaceholders>true</OverrideHtmlAssetPlaceholders>
</PropertyGroup>

Keep the empty import map tag that the template gives you in wwwroot/index.html.

<script type="importmap"></script>

3. Write your policy with the placeholder

Add this near the top of the <head> content in wwwroot/index.html.

<meta http-equiv="Content-Security-Policy" content="
    base-uri 'self';
    default-src 'self';
    img-src 'self' data:;
    object-src 'none';
    script-src 'self' 'wasm-unsafe-eval' 'sha256-{importmap}';
    style-src 'self';
    connect-src 'self';
    upgrade-insecure-requests;" />

That is all. Run the app, edit an imported .js file, reload, and it works. Publish, and {importmap} is replaced with the real digest.

'wasm-unsafe-eval' is needed by the .NET WebAssembly runtime itself, and is not something this package can remove.

๐Ÿ“จ Delivering the policy as a response header

A <meta> policy cannot express frame-ancestors, report-to, report-uri or sandbox, so many deployments send a Content-Security-Policy header instead. Ask for the digest in a file. Feed that file to whatever configures your host.

dotnet publish -p:ImportMapCspHashOutputFile=obj/csp-hash.txt
sha256-G0ljhAw8W4oTdNcwOzeTZpKKR+KCC1VGyODygTruFm4=

The digest is also available to later MSBuild targets as $(ImportMapCspHash).

โš™๏ธ Settings

Property Default What it does
ImportMapCspPlaceholder {importmap} The token to look for.
ImportMapCspEnabled true Set to false to stop writing the digest, and to stop every check in When something goes wrong.
ImportMapStripIntegrity true Set to false to leave the integrity member alone while you develop.
ImportMapCspHashOutputFile (none) Where to save the digests computed during publish.

sha384 and sha512 work too. Write 'sha384-{importmap}' and that is what you get.

๐Ÿ“‹ Requirements

  • .NET 10 (SDK 10.0.400 or later)
  • A standalone Blazor WebAssembly app with OverrideHtmlAssetPlaceholders set to true
  • Toolbelt.Blazor.WebAssembly.ExtensibleDevServer, for the development half

๐Ÿšซ What it does not cover

This package fixes the problem of editing a .js file. It does not fix every integrity problem. Two other things also carry a digest of their own. This package does not touch either one.

  • the <link rel="preload"> element the SDK writes for dotnet.js
  • the assemblies, .wasm and .pdb files the Blazor runtime fetches

Neither is something you edit by hand. Both are regenerated together with the page, so they stay in step. A build that produces an inconsistent set of them is a problem for the SDK, not for this package.

These things are also out of scope.

  • Blazor Web Apps. Use the ImportMap component's integrity or nonce attribute and follow the official guidance instead.
  • Your own inline scripts and inline styles. This package only knows about the import map.
  • Writing a policy that fits your app. Start it in Content-Security-Policy-Report-Only and watch the browser console.

Turning off integrity during development can hide a real integrity problem until you publish. In practice you lose nothing. The error you see today is only a false alarm about your own edit.

๐Ÿ†˜ When something goes wrong

A placeholder that is never replaced leaves you with an app that no browser can start. This package reports that instead of shipping it.

Code What it means
IMCSP001 Your HTML asks for a digest, but the project does not have OverrideHtmlAssetPlaceholders set to true, so no import map is ever generated.
IMCSP002 The step of the SDK that generates the import map was not found. Check your SDK version. If it is 10.0.400 or later, please open an issue.
IMCSP003 A published HTML file still has the placeholder in it. Please open an issue.
IMCSP004 A warning. Toolbelt.Blazor.WebAssembly.ExtensibleDevServer is not installed, so nothing will fill the placeholder in while you develop.
IMCSP010 The document asks for a digest but has no import map.
IMCSP011 The document has more than one import map, so there is no single digest to write.
IMCSP012 The placeholder has no sha256-, sha384- or sha512- in front of it.

๐Ÿงช Working on this package

dotnet test packs the package once, publishes the sample app with it, and checks what a static host would serve. It also runs the sample app the way you would, edits a module, reloads, and checks that a real browser accepts both the module and the policy, and that the same app fails when either half is turned off.

Everything that builds, publishes or runs the sample app does so inside a disposable Linux container, so you need Docker running. Each one mounts your working tree read only, copies what a build reads out of it, and builds in that copy, so nothing a test does can reach your files. The container also has a NuGet package cache of its own, which is what lets the tests pack the same version over and over without the package cache on your machine handing an older copy of it back.

The browser runs on your machine rather than in that container. Playwright downloads a Chromium of its own the first time the tests run and finds it again afterwards, so nothing has to be installed for this, and whichever browsers you happen to have are left alone.

MSBuild keeps task assemblies loaded in its worker processes. If a rebuild fails with "access to the path is denied", run dotnet build-server shutdown.

๐Ÿ“„ License and 3rd Party Notices

Mozilla Public License 2.0. See LICENSE.

There are no supported framework assets in this package.

Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
10.0.12 36 9/9/2026
10.0.11 74 9/1/2026

v.10.0.12
- Improve: Aligned the version number with the Toolbelt.Blazor.WebAssembly.ExtensibleDevServer package. This release contains no functional changes.

To see all the change logs, please visit the following URL.
- https://github.com/jsakamoto/Toolbelt.Blazor.WebAssembly.ExtensibleDevServer.ImportMapExtension/blob/main/RELEASE-NOTES.txt