Skip to content

[Blazor] Fix WebView blazor.modules.json publish crash via conditional fallback (#67374)#67375

Open
javiercn wants to merge 5 commits into
mainfrom
fix-67374-blazor-modules-framework-asset
Open

[Blazor] Fix WebView blazor.modules.json publish crash via conditional fallback (#67374)#67375
javiercn wants to merge 5 commits into
mainfrom
fix-67374-blazor-modules-framework-asset

Conversation

@javiercn

@javiercn javiercn commented Jun 23, 2026

Copy link
Copy Markdown
Member

Summary

Fixes #67374 — publishing a Blazor Hybrid / WebView app that contributes its own JS library modules crashed with Sequence contains more than one element (a conflict on _framework/blazor.modules.json).

The WebView package ships a fallback _framework/blazor.modules.json (an empty []) so apps without their own JS library modules still serve a manifest. The previous authoring modeled this as a deferred static web asset group, which:

  • required reaching into the consumer to tag/promote the SDK-generated manifest (_TagSdkModulesManifestWithGroup, a SourceId="$(PackageId)" group entry, and AssetKind=All/AssetMode=All promotion), violating the "a group governs only its own package's assets" invariant; and
  • depended on an SDK fix (Static Web Assets: apply deferred asset group resolution during publish sdk#54941) to filter the group at publish — without it, publish crashed.

What changed

Replace the group machinery with conditional materialization (the pattern WebView used before #66412, scoped to just the modules manifest):

  • blazor.modules.json now ships raw under build/ — it is not a flowing static web asset, so it never auto-flows and never collides.
  • StaticWebAssets.Groups.targets materializes the fallback as the consumer's own static web asset only when @(_ExistingBuildJSModules) is empty (the app has no JS modules), during ResolveStaticWebAssetsInputs — i.e. before the build manifest is generated and its conflict check runs.

Result: exactly one asset ever lands on _framework/blazor.modules.json (the app's generated manifest when it has modules, the empty fallback otherwise), so there is no conflict at build or publish. No asset groups, no consumer-manifest tagging/promotion, no SDK dependency.

blazor.webview.js continues to ship as a normal package static web asset under staticwebassets/.

Tests

New test project src/Components/WebView/test/StaticWebAssets/:

  • PackageLayoutTests — crack the built .nupkg and assert the layout/shape (WebView raw build/blazor.modules.json not in the assets manifest; blazor.webview.js as a package asset; plus WebAssembly / App.Internal.Assets / Identity.UI framework/group assets).
  • WebViewBuildBehaviorTests — run isolated consumer build/publish under artifacts/ (with binlog capture), covering package + ProjectReference (P2P) consumers, with and without JS modules, asserting a single served manifest with the expected content.

All 16 tests pass locally against the repo SDK.

Notes

Microsoft.AspNetCore.Components.WebView shipped its fallback blazor.modules.json
via a static web asset group (BlazorWebViewModules=fallback) plus manifest-promotion
targets. At publish, group filtering runs with SkipDeferred=true, so the fallback was
not excluded and GenerateStaticWebAssetEndpointsManifest saw two AssetKind=All assets
on _framework/blazor.modules.json, throwing 'Sequence contains more than one element'
in MAUI Blazor Hybrid apps that also reference a JS-module-contributing RCL.

Model blazor.modules.json as a framework static web asset (like
Microsoft.AspNetCore.Components.WebAssembly ships its JS): BasePath '/', assets under
wwwroot/_framework/, and StaticWebAssetFrameworkPattern '**/*.js;**/*.modules.json'.
The framework pattern is matched against the fingerprinted relative path, so a suffix
glob (*.modules.json) is required for the JS module manifest to be classified as a
Framework asset. The deferred BlazorWebViewModules group + promotion targets are
removed; a minimal StaticWebAssets.Groups.targets keeps JSModuleManifestRelativePath
and CompressionEnabled for consumers.

Add a test project that cracks the built .nupkg files and asserts the static web assets
layout/shape for the WebView, WebAssembly, App.Internal.Assets and Identity.UI packages,
plus end-to-end build/publish tests that reference the locally-built WebView package
from a generated app (and a JS-module RCL) and validate the produced endpoints.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 23, 2026 00:15
@javiercn javiercn requested a review from a team as a code owner June 23, 2026 00:15

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 fixes a publish-time static web assets collision for MAUI Blazor Hybrid apps by changing how Microsoft.AspNetCore.Components.WebView ships its fallback blazor.modules.json: it is now packaged as a framework static web asset (matching the WebAssembly convention) rather than using deferred static web asset groups and SDK manifest promotion, preventing the “two AssetKind=All assets” crash at publish.

Changes:

  • Re-model WebView’s fallback blazor.modules.json as a framework static web asset under wwwroot/_framework/ with StaticWebAssetBasePath=/ and an updated framework pattern.
  • Remove the BlazorWebViewModules deferred-group + manifest-promotion machinery; keep only the consumer build properties needed for JS module manifest path/compression.
  • Add new package-layout and build/publish behavior tests that validate the nupkg shape and ensure publish produces a single _framework/blazor.modules.json endpoint.

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/Components/WebView/WebView/src/wwwroot/_framework/blazor.modules.json Adds the fallback modules manifest in the _framework/ asset location.
src/Components/WebView/WebView/src/StaticWebAssets.Groups.targets Removes deferred group logic; retains only consumer-facing properties used by the SWA SDK.
src/Components/WebView/WebView/src/Microsoft.AspNetCore.Components.WebView.csproj Switches to framework-asset modeling (BasePath=/, updated framework pattern, assets under wwwroot/_framework/).
src/Components/WebView/test/StaticWebAssets/WebViewBuildBehaviorTests.cs New end-to-end build/publish tests validating single-manifest behavior and fallback behavior.
src/Components/WebView/test/StaticWebAssets/StaticWebAssetsTestData.cs Test-time configuration helper sourced from assembly metadata.
src/Components/WebView/test/StaticWebAssets/RequiresBuiltPackagesAttribute.cs Test condition to skip when required locally-built packages aren’t available.
src/Components/WebView/test/StaticWebAssets/PackageLayoutTests.cs Verifies nupkg static web assets layout and absence of removed group machinery.
src/Components/WebView/test/StaticWebAssets/PackageArchive.cs Helper for cracking nupkgs and reading entries/manifests.
src/Components/WebView/test/StaticWebAssets/Microsoft.AspNetCore.Components.WebView.StaticWebAssets.Tests.csproj New test project wiring for the static web assets packaging/build-behavior tests.
src/Components/WebView/test/StaticWebAssets/ConsumerBuild.cs Harness to generate a temp consumer app/RCL, run build/publish, and capture results.

Comment on lines +16 to +28
private PackageArchive(ZipArchive archive, string packageId, string path)
{
_archive = archive;
PackageId = packageId;
Path = path;
EntryNames = archive.Entries.Select(e => e.FullName.Replace('\\', '/')).ToArray();
}

public string PackageId { get; }

public string Path { get; }

public IReadOnlyList<string> EntryNames { get; }

public ConsumerBuild()
{
_root = Path.Combine(Path.GetTempPath(), "wv-swa-tests", Guid.NewGuid().ToString("N"));

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

No Path.GetTempPath() on the tests. Use the bin folder or inject an assembly metadata attribute that points to the artifacts temp dir instead, and of course, "namespace" it. Also use a deterministic monotonically increasing identifier like yyyy-MM-dd-guid if you want.

javiercn and others added 2 commits June 23, 2026 14:44
…ests

The framework-asset modeling of blazor.modules.json/blazor.webview.js broke publish for
in-repo projects that reference the WebView project via ProjectReference (the WebView E2E
test and the Photino sample): the SDK applies StaticWebAssetFrameworkPattern when computing
a referenced project's BUILD static web assets but not its PUBLISH assets, so publish ends up
with both the materialized framework asset (SourceType=Discovered, SourceId=consumer) and the
original (SourceType=Project, SourceId=WebView) at the same _framework/... target path and
fails with 'Conflicting assets with the same target path'.

Add a no-op-for-package-consumers workaround in StaticWebAssets.Groups.targets that drops the
redundant Project-sourced WebView framework assets at publish (the materialized copies are the
ones served), and import the groups targets from the WebView E2E test so it (like the Photino
sample) also gets JSModuleManifestRelativePath. Package consumers receive these assets as
SourceType=Package, so nothing is removed for them.

Test improvements:
- Build-behavior tests now create working folders under artifacts/tmp instead of the system
  temp folder.
- Each build/publish captures a binary log under artifacts/log so CI collects it and failures
  can be diagnosed; the working folder is preserved on failure and removed on success.
- Tests log the dotnet invocation, output and binlog path via ITestOutputHelper.

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

Adds Publish_ProjectReferenceToWebViewWithJsModuleRcl_SucceedsWithSingleModulesManifest,
which references the WebView source project (not the package) plus a JS-module RCL and runs
'dotnet publish'. This is the exact in-repo scenario that regressed in CI: without the
StaticWebAssets.Groups.targets workaround it fails with 'Conflicting assets with the same
target path _framework/blazor.modules.json'. The test asserts publish succeeds, a single
_framework/blazor.modules.json endpoint is produced, and the app's generated manifest (with
the RCL module) supersedes the WebView fallback.

ConsumerBuild gains an isolateNuGetFeeds option so P2P builds inherit the repo NuGet.config
(needed to build the referenced source project) instead of the isolated package feed used by
the PackageReference tests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…otnet/sdk#54941)

Reverts the framework-asset modeling and the in-package publish workaround. With the SDK fix
in dotnet/sdk#54941 (resolved deferred static web asset groups are persisted into the build
manifest and re-applied, unscoped, when the manifest is reloaded at publish), the idiomatic
deferred-group authoring that the WebView package already uses is correct end-to-end:
blazor.modules.json is a Package static web asset in the deferred BlazorWebViewModules group,
resolved to drop the fallback when the app contributes its own JS modules and keep it
otherwise. No framework-asset hack and no package-local workaround are needed, so the WebView
product files are unchanged from main.

This PR now contributes the static web assets packaging + build/publish regression tests:
- Package-layout tests assert the deferred-group shape for WebView (modules.json = Package in
  BlazorWebViewModules=fallback; webview.js = Framework) and the framework/group shapes for
  Components.WebAssembly, App.Internal.Assets and Identity.UI.
- Build/publish behavior tests (package consumer and ProjectReference consumer) assert a single
  _framework/blazor.modules.json endpoint and that the app's manifest supersedes the fallback.
  The publish assertions are skipped until dotnet/sdk#54941 is in the repo SDK (detected via the
  pre-fix 'Sequence contains more than one element' crash) so the suite stays green meanwhile.

Note: this PR depends on dotnet/sdk#54941 flowing into the repo SDK; until then the in-repo
WebView publish path (and the publish tests) require that fix.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@javiercn javiercn changed the title [Blazor] Model WebView blazor.modules.json as a framework asset (#67374) [Blazor] WebView blazor.modules.json deferred-group static web assets tests (depends on dotnet/sdk#54941) Jun 23, 2026
#67374)

The WebView package ships a fallback _framework/blazor.modules.json (empty []) for
apps that contribute no JS library modules. Modeling it as a deferred static web
asset group required tagging/promoting the consumer's SDK-generated manifest and
depended on an SDK fix (dotnet/sdk#54941) to filter the group at publish; without
it, publish crashed with "Sequence contains more than one element" (#67374).

Replace the group authoring with conditional materialization: the package ships
the fallback raw under build/ (not as a flowing static web asset) and materializes
it as the consumer's own asset during ResolveStaticWebAssetsInputs ONLY when the
app has no JS modules of its own. The decision runs before the build manifest /
conflict check, so exactly one asset ever lands on _framework/blazor.modules.json
and there is never a conflict at build or publish. No asset groups, no consumer
manifest tagging, no SDK dependency.

Tests validate the package layout and build/publish behavior (package + P2P
consumers, with and without JS modules) by cracking the built .nupkg and running
isolated consumer builds under artifacts/ with binlog capture.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@javiercn javiercn changed the title [Blazor] WebView blazor.modules.json deferred-group static web assets tests (depends on dotnet/sdk#54941) [Blazor] Fix WebView blazor.modules.json publish crash via conditional fallback (#67374) Jun 23, 2026
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.

BlazorWebView: model blazor.modules.json as a Framework asset (fixes publish-time Sequence contains more than one element in MAUI Blazor Hybrid + RCL)

2 participants