Thanks to visit codestin.com
Credit goes to github.com

Skip to content

fix(runtime-vapor): skip teleport ranges for logical hydration siblings#14832

Merged
edison1105 merged 1 commit into
minorfrom
edison/fix/teleportRange
May 18, 2026
Merged

fix(runtime-vapor): skip teleport ranges for logical hydration siblings#14832
edison1105 merged 1 commit into
minorfrom
edison/fix/teleportRange

Conversation

@edison1105

@edison1105 edison1105 commented May 18, 2026

Copy link
Copy Markdown
Member

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Improved hydration handling for disabled teleports and fragment boundaries to ensure proper DOM state restoration.
  • Tests

    • Added test coverage validating hydration behavior with disabled teleport components.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 18, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 256cc6b4-1c97-4869-8b17-0cb7fe760ba0

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch edison/fix/teleportRange

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

@edison1105 edison1105 added the scope: vapor related to vapor mode label May 18, 2026
@github-actions

Copy link
Copy Markdown

Size Report

Bundles

File Size Gzip Brotli
compiler-dom.global.prod.js 86.5 kB 30.3 kB 26.6 kB
runtime-dom.global.prod.js 113 kB 42.6 kB 38.1 kB
vue.global.prod.js 172 kB 62.4 kB 55.6 kB

Usages

Name Size Gzip Brotli
createApp (CAPI only) 51.5 kB 20.1 kB 18.3 kB
createApp 60.5 kB 23.4 kB 21.3 kB
createApp + vaporInteropPlugin 103 kB 37.3 kB 33.7 kB
createVaporApp 27.6 kB 10.7 kB 9.84 kB
createSSRApp 65 kB 25.2 kB 22.9 kB
createVaporSSRApp 33.8 kB (-9 B) 12.9 kB (-14 B) 11.9 kB (-15 B)
defineCustomElement 67.1 kB 25.4 kB 23.2 kB
defineVaporCustomElement 39.8 kB 14.3 kB 13.1 kB
overall 75.8 kB 28.9 kB 26.2 kB

@pkg-pr-new

pkg-pr-new Bot commented May 18, 2026

Copy link
Copy Markdown

Open in StackBlitz

@vue/compiler-core

pnpm add https://pkg.pr.new/@vue/compiler-core@14832
npm i https://pkg.pr.new/@vue/compiler-core@14832
yarn add https://pkg.pr.new/@vue/[email protected]

@vue/compiler-dom

pnpm add https://pkg.pr.new/@vue/compiler-dom@14832
npm i https://pkg.pr.new/@vue/compiler-dom@14832
yarn add https://pkg.pr.new/@vue/[email protected]

@vue/compiler-sfc

pnpm add https://pkg.pr.new/@vue/compiler-sfc@14832
npm i https://pkg.pr.new/@vue/compiler-sfc@14832
yarn add https://pkg.pr.new/@vue/[email protected]

@vue/compiler-ssr

pnpm add https://pkg.pr.new/@vue/compiler-ssr@14832
npm i https://pkg.pr.new/@vue/compiler-ssr@14832
yarn add https://pkg.pr.new/@vue/[email protected]

@vue/compiler-vapor

pnpm add https://pkg.pr.new/@vue/compiler-vapor@14832
npm i https://pkg.pr.new/@vue/compiler-vapor@14832
yarn add https://pkg.pr.new/@vue/[email protected]

@vue/reactivity

pnpm add https://pkg.pr.new/@vue/reactivity@14832
npm i https://pkg.pr.new/@vue/reactivity@14832
yarn add https://pkg.pr.new/@vue/[email protected]

@vue/runtime-core

pnpm add https://pkg.pr.new/@vue/runtime-core@14832
npm i https://pkg.pr.new/@vue/runtime-core@14832
yarn add https://pkg.pr.new/@vue/[email protected]

@vue/runtime-dom

pnpm add https://pkg.pr.new/@vue/runtime-dom@14832
npm i https://pkg.pr.new/@vue/runtime-dom@14832
yarn add https://pkg.pr.new/@vue/[email protected]

@vue/runtime-vapor

pnpm add https://pkg.pr.new/@vue/runtime-vapor@14832
npm i https://pkg.pr.new/@vue/runtime-vapor@14832
yarn add https://pkg.pr.new/@vue/[email protected]

@vue/server-renderer

pnpm add https://pkg.pr.new/@vue/server-renderer@14832
npm i https://pkg.pr.new/@vue/server-renderer@14832
yarn add https://pkg.pr.new/@vue/[email protected]

@vue/shared

pnpm add https://pkg.pr.new/@vue/shared@14832
npm i https://pkg.pr.new/@vue/shared@14832
yarn add https://pkg.pr.new/@vue/[email protected]

vue

pnpm add https://pkg.pr.new/vue@14832
npm i https://pkg.pr.new/vue@14832
yarn add https://pkg.pr.new/[email protected]

@vue/compat

pnpm add https://pkg.pr.new/@vue/compat@14832
npm i https://pkg.pr.new/@vue/compat@14832
yarn add https://pkg.pr.new/@vue/[email protected]

commit: e44fddc

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

Actionable comments posted: 1

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

Inline comments:
In `@packages/runtime-vapor/src/dom/hydration.ts`:
- Around line 235-241: The function nextLogicalSibling uses non-null assertions
on locateEndAnchor results which can be null; update nextLogicalSibling to
defensively handle a null return from locateEndAnchor (both the '[' branch and
the 'teleport start' branch) by checking the result of locateEndAnchor(node)
(and locateEndAnchor(node, 'teleport start','teleport end')) before accessing
.nextSibling and return null (or fallback to node.nextSibling only when safe)
instead of using the bang operator; refer to the nextLogicalSibling,
locateEndAnchor, and isComment symbols to locate the code and mirror the
defensive pattern used by other callers (e.g., using optional chaining or
explicit null checks).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 598588ad-0df0-47f5-ad55-c9768aca1d36

📥 Commits

Reviewing files that changed from the base of the PR and between 74dcb94 and e44fddc.

📒 Files selected for processing (7)
  • packages/runtime-vapor/__tests__/hydration.spec.ts
  • packages/runtime-vapor/src/apiCreateFor.ts
  • packages/runtime-vapor/src/component.ts
  • packages/runtime-vapor/src/components/TransitionGroup.ts
  • packages/runtime-vapor/src/dom/hydration.ts
  • packages/runtime-vapor/src/dom/node.ts
  • packages/runtime-vapor/src/fragment.ts

Comment on lines +235 to 241
export function nextLogicalSibling(node: Node): Node | null {
return isComment(node, '[')
? _next(locateEndAnchor(node)!)
? locateEndAnchor(node)!.nextSibling
: isComment(node, 'teleport start')
? _next(locateEndAnchor(node, 'teleport start', 'teleport end')!)
: _next(node)
? locateEndAnchor(node, 'teleport start', 'teleport end')!.nextSibling
: node.nextSibling
}

@coderabbitai coderabbitai Bot May 18, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Potential null dereference if locateEndAnchor returns null.

locateEndAnchor can return null (line 295) when no matching end anchor exists. The non-null assertions on lines 237 and 239 followed by .nextSibling access will throw if the SSR output is malformed or truncated.

Other callers handle this defensively (e.g., line 409: end || undefined).

🛡️ Proposed fix with null guards
 export function nextLogicalSibling(node: Node): Node | null {
-  return isComment(node, '[')
-    ? locateEndAnchor(node)!.nextSibling
-    : isComment(node, 'teleport start')
-      ? locateEndAnchor(node, 'teleport start', 'teleport end')!.nextSibling
-      : node.nextSibling
+  if (isComment(node, '[')) {
+    const end = locateEndAnchor(node)
+    return end ? end.nextSibling : null
+  }
+  if (isComment(node, 'teleport start')) {
+    const end = locateEndAnchor(node, 'teleport start', 'teleport end')
+    return end ? end.nextSibling : null
+  }
+  return node.nextSibling
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function nextLogicalSibling(node: Node): Node | null {
return isComment(node, '[')
? _next(locateEndAnchor(node)!)
? locateEndAnchor(node)!.nextSibling
: isComment(node, 'teleport start')
? _next(locateEndAnchor(node, 'teleport start', 'teleport end')!)
: _next(node)
? locateEndAnchor(node, 'teleport start', 'teleport end')!.nextSibling
: node.nextSibling
}
export function nextLogicalSibling(node: Node): Node | null {
if (isComment(node, '[')) {
const end = locateEndAnchor(node)
return end ? end.nextSibling : null
}
if (isComment(node, 'teleport start')) {
const end = locateEndAnchor(node, 'teleport start', 'teleport end')
return end ? end.nextSibling : null
}
return node.nextSibling
}
🤖 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/runtime-vapor/src/dom/hydration.ts` around lines 235 - 241, The
function nextLogicalSibling uses non-null assertions on locateEndAnchor results
which can be null; update nextLogicalSibling to defensively handle a null return
from locateEndAnchor (both the '[' branch and the 'teleport start' branch) by
checking the result of locateEndAnchor(node) (and locateEndAnchor(node,
'teleport start','teleport end')) before accessing .nextSibling and return null
(or fallback to node.nextSibling only when safe) instead of using the bang
operator; refer to the nextLogicalSibling, locateEndAnchor, and isComment
symbols to locate the code and mirror the defensive pattern used by other
callers (e.g., using optional chaining or explicit null checks).

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.

I don't think this should be guarded here. nextLogicalSibling() preserves the same invariant as the previous locateNextNode() implementation, which already used locateEndAnchor(...)! for Vue-owned structural marker pairs.

A missing fragment / Teleport end marker means the SSR structural range is corrupted or truncated. Returning null from the normal logical stepping helper would silently change boundary cleanup behavior and could hide the broken range. The defensive end || undefined path is specific to cleanup after a node has already been selected for removal, not the general sibling-stepping contract.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@edison1105, that's a clear and well-reasoned distinction — thanks for the explanation. You're right that:

  1. Invariant parity: locateNextNode already used locateEndAnchor(...)!, so nextLogicalSibling preserves the same contract rather than weakening it.
  2. Fail-loud is correct here: A missing ] or teleport end marker means the SSR output is structurally corrupted. Returning null from the stepping helper would silently short-circuit cleanupHydrationTail / finalizeHydrationBoundary loops, potentially leaving orphaned SSR nodes without surfacing the real problem.
  3. Context of the defensive pattern: The end || undefined guard (e.g., in removeFragmentNodes) belongs to the cleanup/removal contract where the node being cleaned up has already been selected, and graceful no-op behavior is appropriate. That's a fundamentally different concern from the general sibling-stepping path.

I'm withdrawing the suggestion — the non-null assertions are intentional and correct.


✏️ Learnings added
Learnt from: edison1105
Repo: vuejs/core PR: 14832
File: packages/runtime-vapor/src/dom/hydration.ts:235-241
Timestamp: 2026-05-18T01:05:23.718Z
Learning: In `packages/runtime-vapor/src/dom/hydration.ts`, the `nextLogicalSibling` function (and its predecessor `locateNextNode`) intentionally uses non-null assertions (`!`) on `locateEndAnchor` results. Vue's SSR renderer always emits matched structural marker pairs (`[`/`]` for fragments, `teleport start`/`teleport end` for teleports). A missing end marker indicates fatal SSR output corruption; returning `null` from the stepping helper would silently short-circuit boundary cleanup loops (`cleanupHydrationTail`, `finalizeHydrationBoundary`) and hide the corruption. The defensive `end || undefined` pattern used elsewhere (e.g., `removeFragmentNodes`) is specific to cleanup/removal paths where a graceful no-op is appropriate, not the general sibling-stepping contract.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: edison1105
Repo: vuejs/core PR: 14786
File: packages/runtime-vapor/src/apiCreateFor.ts:93-95
Timestamp: 2026-05-07T03:25:36.641Z
Learning: In `packages/runtime-vapor` hydration-cursor code paths that use `enterHydrationCursor` (e.g., cursor callers in `apiCreateFor.ts` and related hydration helpers like `createIf`, slots, and dynamic fragments), do not add local try/finally exception-protection solely around cursor entry. If hydration throws, the outer hydration/component cleanup already restores the global hydration state, and there is no supported case where later hydration continues after an inner throw. If exception-safe cursor scoping becomes necessary in the future, implement it as a shared, reusable cursor-scope API (so the behavior is consistent across all cursor users) rather than one-off local try/catch/try/finally patterns in individual files.

@edison1105 edison1105 merged commit ec58870 into minor May 18, 2026
17 checks passed
@edison1105 edison1105 deleted the edison/fix/teleportRange branch May 18, 2026 01:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope: vapor related to vapor mode

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant