Skip to content

fix(runtime-dom): warn when in-DOM template container contains a shadow root - #15271

Open
ValentinYoushkevich wants to merge 1 commit into
vuejs:mainfrom
ValentinYoushkevich:fix/12813-warn-shadow-root-loss
Open

fix(runtime-dom): warn when in-DOM template container contains a shadow root#15271
ValentinYoushkevich wants to merge 1 commit into
vuejs:mainfrom
ValentinYoushkevich:fix/12813-warn-shadow-root-loss

Conversation

@ValentinYoushkevich

@ValentinYoushkevich ValentinYoushkevich commented Aug 11, 2026

Copy link
Copy Markdown

close #12813

Problem

createApp({}).mount(el) silently destroys declarative shadow DOM inside the mount container.

packages/runtime-dom/src/index.ts:

component.template = container.innerHTML
...
// clear content before mounting
if (container.nodeType === 1) {
  container.textContent = ''
}

The HTML parser turns <template shadowrootmode="open"> into a real shadow root and removes the <template> from the light DOM before any script runs. innerHTML does 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 call attachShadow() 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:

[Vue warn]: Mount container contains <div> with a shadow root. Shadow roots are
not serialized by `innerHTML`, so they are not part of the in-DOM template and
are discarded when the container is cleared before mounting. Use the `template`
or `render` option instead of relying on the in-DOM template, or mount into a
container that has no shadow roots inside it.

Details:

  • Guarded by __DEV__, tree-shaken in production, same pattern as the neighbouring injectNativeTagCheck / injectCompilerOptionsCheck.
  • Runs only inside the !component.render && !component.template branch, i.e. exactly where the loss happens. No cost when template/render is provided.
  • Checks descendants only, not the container itself: a container that is itself a shadow host projects its light children through <slot> and works correctly, so warning there would be a false positive.
  • Breaks on the first match, so one message rather than one per host.
  • No new imports; warn is already imported in the file.

Known limitation, matching what the issue says: el.shadowRoot is only exposed for mode: '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 a template option is provided. The DOM state is built with attachShadow + 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

    • Development builds now warn when in-DOM templates contain shadow roots.
    • Shadow-root content is excluded from the template and removed when the mount container is cleared.
    • Explicit templates avoid warnings about existing shadow roots.
  • Tests

    • Added coverage for shadow-root handling during application mounting.

…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
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Development 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.

Changes

Shadow-root warning

Layer / File(s) Summary
Mount detection and warning
packages/runtime-dom/src/index.ts
Element mounts scan descendant elements for shadow roots before copying template content. Development builds emit one warning when shadow-root content will be lost.
Compiler and runtime integration tests
packages/vue/__tests__/index.spec.ts
Tests verify warning emission, shadow-root removal, and warning suppression when an explicit component template is used.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested labels: 🔩 p2-edge-case

Suggested reviewers: edison1105

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
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The change detects and warns about shadow-root loss but does not preserve declarative shadow DOM content as required by issue #12813. Preserve existing shadow-root content during mounting, or document and implement the required behavior that prevents its loss.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the runtime-dom change and the warning added for in-DOM template containers with shadow roots.
Out of Scope Changes check ✅ Passed The runtime change and integration tests directly address shadow-root detection and warning behavior described in issue #12813.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/vue/__tests__/index.spec.ts (1)

201-210: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a regression test for the render option.

The implementation suppresses this warning when component.render is present, but this test covers only component.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

📥 Commits

Reviewing files that changed from the base of the PR and between a2b40db and 4dbdcda.

📒 Files selected for processing (2)
  • packages/runtime-dom/src/index.ts
  • packages/vue/__tests__/index.spec.ts

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.

Vue breaks Declarative Shadow DOM

1 participant