Skip to content

[browser] Make WASM framework asset CopyToOutputDirectory configurable#127285

Closed
maraf wants to merge 18 commits intodotnet:mainfrom
maraf:maraf/WasmSdkCopyToBin
Closed

[browser] Make WASM framework asset CopyToOutputDirectory configurable#127285
maraf wants to merge 18 commits intodotnet:mainfrom
maraf:maraf/WasmSdkCopyToBin

Conversation

@maraf
Copy link
Copy Markdown
Member

@maraf maraf commented Apr 22, 2026

Note

This PR was created with the assistance of GitHub Copilot.

Summary

Fixes the library-tests regression from #126407 tracked in #127257.

#126407 switched the WASM framework / webcil static web asset CopyToOutputDirectory from PreserveNewest to Never, which is correct for dotnet run (static web assets middleware serves files out of obj/fx/). But library tests in this repo run the app directly from bin/ without the middleware, so the framework files are missing and loading dotnet.*.js fails.

Changes

  • Microsoft.NET.Sdk.WebAssembly.Browser.targets: introduce a private MSBuild property _WasmFrameworkCopyToOutputDirectory (default Never) and route the three CopyToOutputDirectory values through it (Computed/webcil assets, Framework assets, and the post-UpdatePackageStaticWebAssets item update).
  • eng/testing/tests.browser.targets: set _WasmFrameworkCopyToOutputDirectory to PreserveNewest so library tests copy framework assets to the output directory as before.

Default behavior for regular dotnet run / Blazor WASM scenarios is unchanged.

maraf and others added 18 commits April 1, 2026 14:46
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>
With CopyToOutputDirectory=Never, framework assets are no longer copied
to bin/wwwroot/_framework/ during build. They are served from obj/
locations via the static web assets middleware during dotnet run.

Only assert bundle file layout for publish, where files are still
physically copied to the output. Build-time tests that run the app
(via dotnet run or xharness) still validate the app works correctly -
they just don't check for files in bin/ that are intentionally no
longer there.

The Blazor-specific AssertBundle already had this guard
WasmTemplateTestsBase path needed updating.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
With CopyToOutputDirectory=Never, framework files are no longer copied
to bin/_framework/ during build. Update MultiClientHostedBuildAndPublish
to assert framework files exist in obj/<config>/<tfm>/fx/<ProjectName>/
_framework/ for build, matching the materialization path used by
UpdatePackageStaticWebAssets. Publish assertions remain unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Revert the IsPublish guard that skipped AssertWasmSdkBundle for builds.
Instead, add AssertBuildBundle that validates framework files are in their
correct obj/ subdirectories with CopyToOutputDirectory=Never:

- dotnet.js (boot config) in obj/{config}/{tfm}/
- dotnet.runtime.js, maps, ICU in obj/{config}/{tfm}/fx/{name}/_framework/
- dotnet.native.* in fx/_framework/ (non-native) or wasm/for-build/ (native)
- webcil assemblies in obj/{config}/{tfm}/webcil/
- framework files NOT in bin/_framework/

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Gate webcil directory assertion on UseWebcil; when disabled, assemblies
  are materialized as DLLs in fx/_framework/ instead of webcil/.
- Update SatelliteAssembliesFromPackageReference to check obj/ paths
  (webcil/{locale}/ or fx/{name}/_framework/{locale}/) instead of
  bin/_framework/ which is no longer populated.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Update targets comment to reflect CopyToOutputDirectory=Never behavior
- Use named arguments for isUsingWorkloads/isNativeBuild parameters
- Remove unused nativeDirLabel variable

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Restore dotnet.native.wasm check in the build branch of
MultiClientHostedBuildAndPublish to match the publish branch,
ensuring both clients have native assets materialized independently.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…aded worker

- Remove accidentally committed WasmAppHost symlink pointing to local
  Codespace build artifacts path
- Add dotnet.native.worker.mjs validation in AssertBuildBundle for
  multi-threaded build scenarios to match publish-path coverage

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
With framework assets no longer copied to bin/_framework/ during build,
update the three remaining failing tests to look for files in the obj
subdirectories where they actually live:

- Blazor BuildPublishTests.DefaultTemplate_WithResources_Publish:
  probe satellite assemblies under obj/{config}/{tfm}/webcil/{locale}/
  (webcil) or obj/{config}/{tfm}/fx/{name}/_framework/{locale}/ (non-webcil)
  for the build-time assertion.

- BuildPublishTests.BuildThenPublishWithAOT: stat framework files for the
  first build from obj/{config}/{tfm}/fx/{name}/_framework/ (JS, source
  maps) and obj/{config}/{tfm}/wasm/for-build/ (native outputs). Boot
  config is read from obj/{config}/{tfm}/dotnet.js via a new optional
  bootConfigDir parameter on GetFilesTable used by fingerprint resolution.

- ModuleConfigTests.SymbolMapFileEmitted(isPublish=false): search for
  dotnet.native*.js.symbols in obj/wasm/for-build/ (native rebuild) and
  in obj/fx/*/_framework/ as a fallback.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Addresses review feedback: centralizes the obj/.../fx/<source-id>/_framework/
discovery logic in WasmSdkBasedProjectProvider.GetMaterializedFrameworkDir
and uses it from AssertBuildBundle, BuildPublishTests and Blazor/MiscTests
so the per-project fx subdir name is discovered instead of hardcoded.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Apply GetMaterializedFrameworkDir helper to the remaining three
  locations flagged by the review bot: Blazor/BuildPublishTests
  GetBuildSatelliteBaseDir, SatelliteLoadingTests, and ModuleConfigTests
  (the last still inlines the extra 'wasm/for-build' search path but
  deduplicates the fx-base Path.Combine).
- Explain why dotnet.native.worker.mjs is not compared against the
  runtime pack in AssertBuildBundle (parity with AssertBundle's publish
  path comment).
- Log when AssertWasmSdkBundle skips AssertBuildBundle because
  NonDefaultFrameworkDir is set, so the coverage gap is visible in
  test output.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…n obj/fx

With CopyToOutputDirectory=Never, the materialized framework dir under
obj/{config}/{tfm}/fx/{source-id}/_framework/ contains dotnet.runtime.js
under its canonical (non-fingerprinted) name during build. Fingerprinting
is applied later when publishing to bin. GetFilesTable rewrites the entry
from the boot config (which already reflects the publish layout), yielding
a fingerprinted path that does not exist in obj/fx and causes CompareStat
to report the file as missing.

Override the dotnet.runtime.js entry to the unfingerprinted obj/fx path
for the build-phase stat, mirroring the existing native-file override.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Run the server via 'dotnet run --no-build' and use Playwright's
APIRequestContext to verify that each referenced client's framework
files are served from the obj/ materialized directory at the
/<client>/_framework/* base path. This guards the hosted scenario
from PR dotnet#126407 review feedback (build-only, server statically serves
client assets).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… asset copy

Introduce a private MSBuild property _WasmFrameworkCopyToOutputDirectory that
switches CopyToOutputDirectory between 'Never' (default) and 'PreserveNewest'
for WASM framework/webcil static web assets in Microsoft.NET.Sdk.WebAssembly.Browser.targets.

Enable it with 'PreserveNewest' in eng/testing/tests.browser.targets so library
tests that run from bin/ without the static web assets middleware can still find
the framework files. Fixes the regression from dotnet#126407 tracked in dotnet#127257.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 22, 2026 16:00
@maraf maraf added arch-wasm WebAssembly architecture os-browser Browser variant of arch-wasm labels Apr 22, 2026
@maraf maraf added this to the 11.0.0 milestone Apr 22, 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 22, 2026

Replaced by a cleaner branch off main.

@maraf maraf closed this Apr 22, 2026
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 makes build-time copying of WASM framework/static web assets configurable (defaulting to not copying), while restoring the previous “copy to bin/” behavior for in-tree browser/library tests that run without the static web assets middleware.

Changes:

  • Introduces _WasmFrameworkCopyToOutputDirectory in the WASM Browser SDK targets and routes framework/webcil CopyToOutputDirectory behavior through it (default Never).
  • Sets _WasmFrameworkCopyToOutputDirectory=PreserveNewest in eng/testing/tests.browser.targets to fix in-tree test runs that execute directly from bin/.
  • Updates WASM build tests to validate the new default build layout (framework assets in obj/ materialization) and adjusts helpers to locate boot config appropriately.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/mono/wasm/Wasm.Build.Tests/WasmSdkBasedProjectProvider.cs Adds helpers/assertions for build-time (non-publish) asset layout under obj/ and updates bundle assertions accordingly.
src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTestsBase.cs Extends GetFilesTable wrapper to pass an optional boot-config directory.
src/mono/wasm/Wasm.Build.Tests/SatelliteLoadingTests.cs Updates satellite assembly location checks to use obj/ (webcil or materialized fx) instead of bin/_framework.
src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs Adds bootConfigDir parameter to locate boot config when fingerprinting is enabled.
src/mono/wasm/Wasm.Build.Tests/ModuleConfigTests.cs Adjusts symbol-map file lookup to search obj/ build locations when build-time copying is disabled.
src/mono/wasm/Wasm.Build.Tests/BuildPublishTests.cs Updates build-vs-publish file stat paths for the new default layout (but currently has a correctness issue—see PR comment).
src/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests.cs Updates hosted build assertions to check obj/ materialized framework files and adds a runtime verification via dotnet run --no-build + Playwright fetch.
src/mono/wasm/Wasm.Build.Tests/Blazor/BuildPublishTests.cs Adjusts resource DLL assertions to use build-time obj/ locations.
src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets Adds _WasmFrameworkCopyToOutputDirectory (default Never) and uses it for relevant framework/webcil static web assets.
eng/testing/tests.browser.targets Forces framework assets to copy to output for in-tree browser tests by setting _WasmFrameworkCopyToOutputDirectory=PreserveNewest (should be conditional).


<!-- Library tests run directly from bin/ without the static web assets middleware,
so copy framework files to the output directory. See https://github.com/dotnet/runtime/issues/127257. -->
<_WasmFrameworkCopyToOutputDirectory>PreserveNewest</_WasmFrameworkCopyToOutputDirectory>
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

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

This overrides _WasmFrameworkCopyToOutputDirectory unconditionally. To keep the file overrideable (and consistent with the surrounding properties in this PropertyGroup), consider setting it only when it’s not already set (e.g., add a Condition checking for empty).

Suggested change
<_WasmFrameworkCopyToOutputDirectory>PreserveNewest</_WasmFrameworkCopyToOutputDirectory>
<_WasmFrameworkCopyToOutputDirectory Condition="'$(_WasmFrameworkCopyToOutputDirectory)' == ''">PreserveNewest</_WasmFrameworkCopyToOutputDirectory>

Copilot uses AI. Check for mistakes.
Comment on lines +69 to +73
BuildPaths buildObjPaths = paths with { BinFrameworkDir = fxFrameworkDir };
IDictionary<string, (string fullPath, bool unchanged)> pathsDict =
GetFilesTable(info.ProjectName, aot, paths, unchanged: false);
GetFilesTable(info.ProjectName, aot, buildObjPaths, unchanged: false, bootConfigDir: paths.ObjDir);

// dotnet.native.* are produced by the native rebuild into obj/wasm/for-build/ using
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

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

In this test, pathsDict is repurposed later for publish output paths, but then reused again for the “second build” StatFiles(pathsDict) / CompareStat(...) checks. After the publish step, pathsDict points at publish paths, so the second-build stat/compare is no longer validating build outputs. Consider keeping separate dictionaries for build vs publish (or recomputing pathsDict/BuildPaths for the second build) before calling StatFiles/CompareStat for the build phase.

Copilot uses AI. Check for mistakes.
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.

3 participants