Skip to content

[browser] Don't copy framework assets to output during build#126407

Draft
maraf wants to merge 1 commit intomainfrom
maraf/WasmSdkCopyToBin
Draft

[browser] Don't copy framework assets to output during build#126407
maraf wants to merge 1 commit intomainfrom
maraf/WasmSdkCopyToBin

Conversation

@maraf
Copy link
Copy Markdown
Member

@maraf maraf commented Apr 1, 2026

Note

This PR was created with the assistance of GitHub Copilot.

Summary

During build, the WebAssembly SDK copies all .wasm and .js framework assets (~178 files) to bin/wwwroot/_framework/ via CopyToOutputDirectory=PreserveNewest. This is unnecessary because dotnet run uses the static web assets middleware, which serves files directly from their obj/ locations via the manifest (staticwebassets.runtime.json).

Changes

Set CopyToOutputDirectory=Never (was PreserveNewest) for three DefineStaticWebAssets / item-update calls in Microsoft.NET.Sdk.WebAssembly.Browser.targets:

  • Webcil-converted assets (Computed source type) — the .wasm files produced from .dll
  • Framework assets (Framework source type) — dotnet.js, dotnet.native.wasm, runtime JS, etc.
  • Materialized framework assets (post-UpdatePackageStaticWebAssets) — the per-project copies in obj/fx/

Validation

  • ✅ Build succeeds (dotnet build with TargetOS=browser)
  • dotnet run starts WasmAppHost dev server correctly
  • ✅ Playwright headless browser test confirms the WASM app loads and executes (outputs 42)
  • bin/wwwroot/_framework/ drops from ~178 files to 2 (only hot-reload module + dotnet.js from a separate code path)
  • CopyToPublishDirectory was already Never — publish is unaffected

Notes

  • The previous PreserveNewest was added for Blazor WASM hosted scenarios where a server project serves the client's framework files. This scenario needs separate validation.
  • Static web assets middleware resolves files from their source locations (obj/ dirs), so physical copies in bin/ are not needed for dotnet run.

During build, the WebAssembly SDK was copying all .wasm and .js framework
assets to bin/wwwroot/_framework/ via CopyToOutputDirectory=PreserveNewest.
This is unnecessary because dotnet run uses the static web assets middleware,
which serves files directly from their obj/ locations using the manifest.

Change CopyToOutputDirectory from PreserveNewest to Never for:
- Webcil-converted assets (Computed static web assets)
- Materialized framework assets (dotnet.js, dotnet.native.wasm, etc.)

This eliminates ~178 file copies during build while preserving correct
behavior for dotnet run (static web assets middleware) and publish
(CopyToPublishDirectory was already Never).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 1, 2026 14:47
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

🤖 Copilot Code Review — PR #126407

Note

This review was generated by GitHub Copilot.

Holistic Assessment

Motivation: The PR eliminates unnecessary file copies during build. The static web assets middleware serves files directly from obj/ locations via the manifest at dev time, making physical copies to bin/wwwroot/_framework/ redundant. The motivation is sound and well-supported by how the static web assets system works.

Approach: Changing CopyToOutputDirectory from PreserveNewest to Never in three places is the correct and minimal fix. Publish behavior is unaffected — CopyToPublishDirectory was already Never for the first two locations, and publish uses its own separate pipeline with CopyToPublishDirectory="PreserveNewest" set in the publish targets section.

Summary: ⚠️ Needs Changes. The code change itself is correct and well-scoped, but there is a stale comment that now directly contradicts the code behavior, which should be fixed before merge.


Detailed Findings

❌ Documentation — Stale comment contradicts code at lines 427-432

The XML comment block at lines 427-432 explicitly describes the old behavior that this PR removes:

    <!-- Materialized framework assets must be visible to referencing projects (e.g. Blazor WASM
         hosted scenarios where the server project serves the client's framework files).
         UpdatePackageStaticWebAssets defaults AssetMode to CurrentProject and CopyToOutputDirectory
         to Never. Override both: AssetMode=All so assets flow through project references, and
         CopyToOutputDirectory=PreserveNewest so they are copied from the intermediate materialized
         path (obj/fx/{SourceId}/) to bin/wwwroot/_framework/ at build time. -->

The comment says CopyToOutputDirectory=PreserveNewest but the code now sets CopyToOutputDirectory="Never". This is misleading for anyone reading this code in the future. Additionally, since only AssetMode is being overridden now (the CopyToOutputDirectory="Never" matches the default from UpdatePackageStaticWebAssets), the comment's claim that "Override both" is no longer accurate.

Suggested fix — update the comment to reflect the new behavior, e.g.:

    <!-- Materialized framework assets must be visible to referencing projects (e.g. Blazor WASM
         hosted scenarios where the server project serves the client's framework files).
         UpdatePackageStaticWebAssets defaults AssetMode to CurrentProject. Override AssetMode=All
         so assets flow through project references. CopyToOutputDirectory stays Never because the
         static web assets middleware serves files directly from obj/ locations during development. -->

✅ Correctness — Build-time behavior preserved via middleware

The static web assets middleware serves files from their obj/ locations using the manifest during dotnet run. Removing the physical copy to bin/wwwroot/_framework/ doesn't break dev-time serving. The boot config file at line 597 still uses CopyToOutputDirectory="PreserveNewest", which is correct — it's the manifest entry point that the middleware needs.

✅ Publish behavior — Unaffected

The publish pipeline is handled separately (lines ~887-925) with CopyToPublishDirectory="PreserveNewest" and AssetKind="Publish". The three locations modified in this PR already had CopyToPublishDirectory="Never" (lines 388, 407) or handle publish through the separate publish targets, so publish output is unaffected.

💡 Scope — Consistent change across all three asset registration sites

The change touches the three correct locations where build-time WASM assets were configured for copying:

  1. Line 387 — Webcil-converted assets (Computed)
  2. Line 406 — Framework candidate assets (Framework)
  3. Line 435 — Materialized framework assets (post-UpdatePackageStaticWebAssets)

All three are consistently set to Never, which is the right thing.

Generated by Code Review for issue #126407 ·

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR changes the WebAssembly SDK build targets to stop copying runtime/framework static web assets into bin/wwwroot/_framework during build, relying instead on the static web assets manifest/middleware to serve them from their source locations (typically under obj/ or the runtime pack).

Changes:

  • Set CopyToOutputDirectory="Never" for build-time static web assets emitted from webcil conversion (SourceType="Computed").
  • Set CopyToOutputDirectory="Never" for build-time runtime pack assets registered as SourceType="Framework".
  • Set CopyToOutputDirectory="Never" for the post-UpdatePackageStaticWebAssets “materialized” framework assets (per-project obj/fx/{SourceId}/ copies).

Comment on lines 427 to 432
<!-- Materialized framework assets must be visible to referencing projects (e.g. Blazor WASM
hosted scenarios where the server project serves the client's framework files).
UpdatePackageStaticWebAssets defaults AssetMode to CurrentProject and CopyToOutputDirectory
to Never. Override both: AssetMode=All so assets flow through project references, and
CopyToOutputDirectory=PreserveNewest so they are copied from the intermediate materialized
path (obj/fx/{SourceId}/) to bin/wwwroot/_framework/ at build time. -->
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment above this ItemGroup still says CopyToOutputDirectory is overridden to PreserveNewest to copy materialized framework assets into bin/wwwroot/_framework at build time, but the code now sets CopyToOutputDirectory="Never". Please update the comment to reflect the new behavior/rationale (and avoid referencing build-time copying if that is no longer intended).

Copilot uses AI. Check for mistakes.
@maraf maraf changed the title Stop copying WASM framework assets to bin/wwwroot/_framework during build [browser] Don't copy framework assets to output during build Apr 1, 2026
@maraf maraf added arch-wasm WebAssembly architecture os-browser Browser variant of arch-wasm labels Apr 1, 2026
@maraf maraf added this to the 11.0.0 milestone Apr 1, 2026
@dotnet-policy-service
Copy link
Copy Markdown
Contributor

Tagging subscribers to 'arch-wasm': @lewing, @pavelsavara
See info in area-owners.md if you want to be subscribed.

Copy link
Copy Markdown
Member Author

maraf commented Apr 1, 2026

Note

This comment was generated with the assistance of GitHub Copilot.

Blazor WASM Hosted Scenario Analysis

Testing revealed that the existing MultiClientHostedBuild test in src/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests.cs explicitly asserts that framework files are physically present in bin/<config>/<tfm>/wwwroot/_framework/:

// Lines 169-179
string client1Framework = Path.Combine(client1Dir, "bin", config.ToString(),
    DefaultTargetFrameworkForBlazor, "wwwroot", "_framework");

Assert.True(Directory.Exists(client1Framework), ...);
Assert.Contains(client1Files, f => Path.GetFileName(f).StartsWith("dotnet.") && f.EndsWith(".js"));

This test creates two Blazor WASM client projects hosted by a single server and verifies that each client gets its own physical framework files — validating the Framework SourceType materialization path that gives each client unique per-project Identity for shared runtime pack files (dotnet.native.js, ICU data, etc.).

Impact Summary

Scenario Status
dotnet build (standalone WASM) ✅ Works — assets no longer copied to bin/wwwroot/_framework/
dotnet run (standalone WASM via WasmAppHost) ✅ Works — static web assets middleware serves from obj/ via manifest
dotnet run (Blazor WASM hosted) ✅ Should work — UseBlazorFrameworkFiles() uses static web assets middleware
MultiClientHostedBuild test Will fail — asserts physical files in bin/wwwroot/_framework/
Non-publish deployment (xcopy from bin/) ⚠️ Would break if anyone depends on files being in bin/ at build time

Next Steps

Options to consider:

  1. Update the test assertions to check obj/ locations (via manifest) instead of bin/wwwroot/_framework/
  2. Make the behavior opt-in via a property (e.g. <WasmCopyFrameworkFilesToOutput>false</WasmCopyFrameworkFilesToOutput>)
  3. Only suppress copy for non-hosted scenarios where no other project references the WASM client

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

Labels

arch-wasm WebAssembly architecture area-Build-mono os-browser Browser variant of arch-wasm

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants