Skip to content

fix(runtime-core): resolve $el for dev root comment fragment - #15313

Open
oedumoreira wants to merge 1 commit into
vuejs:mainfrom
oedumoreira:fix/12680-el-dev-root-fragment
Open

fix(runtime-core): resolve $el for dev root comment fragment#15313
oedumoreira wants to merge 1 commit into
vuejs:mainfrom
oedumoreira:fix/12680-el-dev-root-fragment

Conversation

@oedumoreira

@oedumoreira oedumoreira commented Aug 18, 2026

Copy link
Copy Markdown

close #12680

In dev mode, Vue preserves HTML comments written in a template. When a component's root is a single element preceded by a leading comment, e.g.:

<!-- a comment -->
<div>real element</div>

...the compiler wraps the root in a fragment flagged DEV_ROOT_FRAGMENT, since there are technically two root-level nodes.

instance.$el (and any template ref pointing at the component) simply read instance.vnode.el, which for this
fragment is the fragment's anchor node — silently resolves to a useless anchor nodeinstead of the real rendered element, in dev only.

Fix

$el now resolves through to the real sing— the same helper renderComponentRootalready uses to resolve attrs/scopeId fallthrough for this exact DEV_ROOT_FRAGMENT case. Only active in __DEV__; production builds strip comments from templates so this scenario doesn't exist there, and the change is a no-op for non-fragment roots.

Test plan

- Added a regression test in packages/runtime-core/__tests__/componentPublicInstance.spec.ts that mounts a
component whose root is [comment, <div>] sserts $el is the real element node (notthe anchor). Verified it fails without the fix and passes with it.
- pnpm test-unit passes locally for runtime-core, runtime-dom, and vue.

close #12680

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **Bug Fixes**
  - Fixed component element references in development mode when a component renders a single element wrapped in a fragment.
  - Component `$el` now correctly points to the actual root element instead of an internal development wrapper.
  - Production behavior remains unchanged.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

In dev mode, a leading comment before a component's single root
element causes the compiler to wrap the root in a fragment flagged
DEV_ROOT_FRAGMENT. $el (and template refs pointing at the component)
resolved to the fragment's anchor node instead of the real rendered
element, silently breaking any code relying on $el being an actual
DOM element (measuring, focusing, third-party DOM integrations,
etc).

Resolve $el to the real single root via filterSingleRoot, the same
utility already used to resolve attrs/scopeId fallthrough for this
exact scenario in renderComponentRoot. Production builds are
unaffected since dev comments are stripped from templates outside
of dev.

close vuejs#12680
@coderabbitai

coderabbitai Bot commented Aug 18, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 665143d3-d039-4853-ba4e-09b6fc9ddfea

📥 Commits

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

📒 Files selected for processing (2)
  • packages/runtime-core/__tests__/componentPublicInstance.spec.ts
  • packages/runtime-core/src/componentPublicInstance.ts

Included review availability: Your plan includes up to 10 reviews per rolling hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

Development-mode $el resolution now returns the actual element for components whose root is a comment plus a single-element fragment. A regression test covers the div root. Production behavior remains unchanged.

Changes

Development root fragment element resolution

Layer / File(s) Summary
Resolve development root element
packages/runtime-core/src/componentPublicInstance.ts
$el detects DEV_ROOT_FRAGMENT roots and resolves their single real root element in development mode.
Verify component $el
packages/runtime-core/__tests__/componentPublicInstance.spec.ts
The regression test renders a comment and one element, then verifies that $el is the expected div node.

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

Merge Risk: ⚪ Minimal · up to a6ebc

This localized change makes component references resolve to the actual root element for development-only comment fragments; no actionable merge-blocking risk remains after normal checks and review.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the fix to resolve component $el for development root comment fragments.
✨ 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.

@edison1105 edison1105 added the 🍰 p2-nice-to-have Priority 2: this is not breaking anything but nice to have it addressed. label Aug 19, 2026

@edison1105 edison1105 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for working on this. The overall direction looks correct: DEV_ROOT_FRAGMENT is the right way to distinguish a comment-only pseudo-fragment from a real multi-root component, and resolving this in the public $el getter preserves the renderer's internal fragment-anchor semantics.

However, the current resolver stops at component boundaries. For example:

<!-- Inner.vue -->
<!-- comment -->
<div />
<!-- Outer.vue -->
<Inner />

Outer.subTree is the Inner component VNode, so getDevRootFragmentEl(Outer) falls back to Outer.vnode.el. That value follows the normal component host-element chain to Inner.vnode.el, which is still the DEV_ROOT_FRAGMENT anchor. As a result, Outer.$el remains an anchor instead of the <div>. The same issue remains when both Outer and Inner have root comments: resolving the outer fragment returns the Inner VNode's .el, which is still the inner fragment anchor.

Could we make the public-root resolver recursively treat both DEV_ROOT_FRAGMENT and single-root component VNodes as transparent layers, following vnode.component.subTree where applicable? Please also add a regression test for an outer component rendering an inner component whose root is comment + div; covering the case where both layers are DEV_ROOT_FRAGMENT would be useful as well.

One minor test-fidelity point: the compiler emits PatchFlags.STABLE_FRAGMENT | PatchFlags.DEV_ROOT_FRAGMENT, so the hand-written test should preferably use that combination rather than DEV_ROOT_FRAGMENT alone.

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

Labels

🍰 p2-nice-to-have Priority 2: this is not breaking anything but nice to have it addressed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

$el of templateRef points to comment VNode instead of first real child of component

2 participants