fix(runtime-dom): warn when in-DOM template container contains a shadow root - #15271
fix(runtime-dom): warn when in-DOM template container contains a shadow root#15271ValentinYoushkevich wants to merge 1 commit into
Conversation
…ow root `innerHTML` does not serialize shadow roots, so declarative shadow DOM inside the mount container is absent from the in-DOM template, and clearing the container before mounting discards the host along with its shadow root. The shadow content disappears with no diagnostic. Warn in dev instead. close vuejs#12813
📝 WalkthroughWalkthroughDevelopment builds now detect descendant shadow roots before replacing an element mount container’s in-DOM template content. Vue warns when shadow-root content will be discarded. Integration tests cover implicit and explicit component templates. ChangesShadow-root warning
Estimated code review effort: 2 (Simple) | ~10 minutes Suggested labels: Suggested reviewers: Sequence Diagram(s)sequenceDiagram
participant ElementMount
participant warnShadowRootLoss
participant MountContainer
ElementMount->>warnShadowRootLoss: inspect mount container
warnShadowRootLoss->>MountContainer: scan descendant elements
MountContainer-->>warnShadowRootLoss: return shadow-root presence
warnShadowRootLoss-->>ElementMount: emit warning when found
ElementMount->>MountContainer: replace copied template content
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/vue/__tests__/index.spec.ts (1)
201-210: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd a regression test for the
renderoption.The implementation suppresses this warning when
component.renderis present, but this test covers onlycomponent.template. Add a mount with a render function and an element with an open shadow root to protect the second suppression path.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/vue/__tests__/index.spec.ts` around lines 201 - 210, Add a regression test alongside the existing template-option case that mounts a component with a render function into a container containing an element with an open shadow root, then assert the “shadow root” warning is not emitted. Reuse the existing createApp mount and warning assertion patterns while specifically exercising component.render.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/vue/__tests__/index.spec.ts`:
- Around line 201-210: Add a regression test alongside the existing
template-option case that mounts a component with a render function into a
container containing an element with an open shadow root, then assert the
“shadow root” warning is not emitted. Reuse the existing createApp mount and
warning assertion patterns while specifically exercising component.render.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: e5e7987f-fefa-4272-a963-681acacaee82
📒 Files selected for processing (2)
packages/runtime-dom/src/index.tspackages/vue/__tests__/index.spec.ts
close #12813
Problem
createApp({}).mount(el)silently destroys declarative shadow DOM inside the mount container.packages/runtime-dom/src/index.ts:The HTML parser turns
<template shadowrootmode="open">into a real shadow root and removes the<template>from the light DOM before any script runs.innerHTMLdoes not serialize shadow roots, so the in-DOM template Vue compiles has no trace of it;container.textContent = ''then discards the host node along with its shadow root, and Vue re-creates the host from the compiled template. Net effect: the shadow content disappears and the light content is rendered instead, with no diagnostic of any kind.Reproduced identically on 3.4.38, 3.5.41 and 3.6.0-rc.3.
What this PR does
It does not add declarative shadow DOM support — that needs both
getHTML({ shadowRoots })for serialization and a new renderer step to callattachShadow()for<template shadowrootmode>, which is a design decision rather than a bug fix (see the issue for the analysis).It turns the silent data loss into a diagnosable warning. On the in-DOM-template path only, in dev only:
Details:
__DEV__, tree-shaken in production, same pattern as the neighbouringinjectNativeTagCheck/injectCompilerOptionsCheck.!component.render && !component.templatebranch, i.e. exactly where the loss happens. No cost whentemplate/renderis provided.<slot>and works correctly, so warning there would be a false positive.warnis already imported in the file.Known limitation, matching what the issue says:
el.shadowRootis only exposed formode: 'open', so closed shadow roots cannot be detected.Tests
Two tests in
packages/vue/__tests__/index.spec.ts: one asserting the warning and the resulting shadow-root loss, one asserting no warning when atemplateoption is provided. The DOM state is built withattachShadow+ light text, which is what the parser produces for<div><template shadowrootmode="open">Shadow content</template>Light content</div>.vitest run --project unit-jsdom: 32 files, 397 passed, 1 skipped. The new test fails without the change.Summary by CodeRabbit
Bug Fixes
Tests