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
<PackageReference Include="Toolbelt.Blazor.WebAssembly.ExtensibleDevServer.ImportMapExtension" Version="10.0.12"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
<PackageVersion Include="Toolbelt.Blazor.WebAssembly.ExtensibleDevServer.ImportMapExtension" Version="10.0.12" />
<PackageReference Include="Toolbelt.Blazor.WebAssembly.ExtensibleDevServer.ImportMapExtension"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
paket add Toolbelt.Blazor.WebAssembly.ExtensibleDevServer.ImportMapExtension --version 10.0.12
#r "nuget: Toolbelt.Blazor.WebAssembly.ExtensibleDevServer.ImportMapExtension, 10.0.12"
#:package Toolbelt.Blazor.WebAssembly.ExtensibleDevServer.ImportMapExtension@10.0.12
#addin nuget:?package=Toolbelt.Blazor.WebAssembly.ExtensibleDevServer.ImportMapExtension&version=10.0.12
#tool nuget:?package=Toolbelt.Blazor.WebAssembly.ExtensibleDevServer.ImportMapExtension&version=10.0.12
Blazor WebAssembly Extensible Dev Server - ImportMap Extension
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
OverrideHtmlAssetPlaceholdersset totrue 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 fordotnet.js - the assemblies,
.wasmand.pdbfiles 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
ImportMapcomponent'sintegrityornonceattribute 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-Onlyand 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.
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.
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