Skip to content

[Wasm] Prototype middleware-free standalone hot reload - #56010

Draft
lewing wants to merge 1 commit into
mainfrom
lewing-middleware-free-wasm-hot-reload
Draft

[Wasm] Prototype middleware-free standalone hot reload#56010
lewing wants to merge 1 commit into
mainfrom
lewing-middleware-free-wasm-hot-reload

Conversation

@lewing

@lewing lewing commented Aug 29, 2026

Copy link
Copy Markdown
Member

Why

Modern standalone Blazor WebAssembly is served by the NativeAOT-compatible Gateway, which cannot dynamically load Microsoft.AspNetCore.Watch.BrowserRefresh. As a result, the hosting-startup middleware never injects the browser-refresh script and dotnet watch reports that no browser is connected.

This prototypes a .NET 11+ path that does not require Gateway middleware, HTML response rewriting, or dynamic assembly loading. It is an alternative and architectural follow-up to dotnet/aspnetcore#68900 and dotnet/aspnetcore#67611.

Approach

  • Add the browser-refresh WebSocket endpoints and public RSA key to the automatically launched URL fragment. Fragments are not sent to the application server, and the browser-generated shared secret never enters the URL.
  • Consolidate Marek's [wasm] HotReload script consolidation #54142 WebSocket client into Microsoft.DotNet.HotReload.WebAssembly.Browser.lib.module.js, replacing the proposed middleware configuration endpoint.
  • Restrict bootstrap WebSockets to the application host or loopback, remove the fragment before routing, and preserve browser history state.
  • Apply managed deltas directly over the WebSocket and retain successful batches in watcher-scoped sessionStorage so manual and static-file reloads can replay them without /_framework/blazor-hotreload.
  • Keep middleware behavior for older TFMs, hosted/non-WASM apps, initializer-disabled builds, and configurations where dotnet watch does not automatically launch the browser.

For the new path this removes dependencies on DOTNET_STARTUP_HOOKS, ASPNETCORE_HOSTINGSTARTUPASSEMBLIES, HTML script injection, aspnetcore-browser-refresh.js, browser-refresh-config, blazor-hotreload, and clear-browser-cache. The dotnet-watch WebSocket refresh server remains.

Validation

  • Full ./build.sh build.
  • Focused dotnet-watch.Tests: 18 passed.
  • Focused Microsoft.DotNet.HotReload.Client.Tests: 3 passed.
  • Reproduced the original failure before the change: a loaded Chromium page produced No browser is connected after a Razor edit.
  • Exercised the rebuilt SDK with real headless Chromium and standalone Gateway: no middleware environment variables were supplied, the browser connected, a Razor delta changed the live <h1>, a manual page reload reconnected as a second browser, and the updated DOM was preserved by replay.

Known limitations and follow-ups

  • Updates produced while no browser is connected are not retained. Completing that requires watch/IDE-owned per-connection history along the direction of WIP: WASM redesign #49021 and [hot-reload] WASM changes are dropped when there is no browser running when the changes are applied #53485.
  • VS and VS Code must emit the same bootstrap fragment before they can use this path.
  • Manual browser-launch configurations intentionally retain middleware compatibility, so NativeAOT Gateway is not yet middleware-free in that mode.
  • Arbitrary cross-host WebSocket endpoints are rejected until there is a bootstrap design with an external trust anchor.
  • Direct stylesheet cache busting works, but nested @import and scoped CSS behavior needs more validation without Clear-Site-Data.
  • The checked-in integration test uses the protocol test browser; the full Chromium DOM transition is currently a manual validation.

Use a trusted launch URL fragment to bootstrap the net11+ standalone Blazor WebAssembly browser client directly to dotnet-watch, while preserving middleware fallback and page-reload delta replay.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 2 pipeline(s).
1 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant