Skip to content

feat(teleport): use moveBefore to preserve node state when possible#14345

Open
edison1105 wants to merge 10 commits intominorfrom
edison/feat/teleportUseMoveBefore
Open

feat(teleport): use moveBefore to preserve node state when possible#14345
edison1105 wants to merge 10 commits intominorfrom
edison/feat/teleportUseMoveBefore

Conversation

@edison1105
Copy link
Member

@edison1105 edison1105 commented Jan 21, 2026

Summary by CodeRabbit

  • Bug Fixes

    • Improved Teleport to preserve element state, including video playback position and playing status, when dynamically moving elements between DOM locations.
  • Tests

    • Added end-to-end tests validating that Teleport maintains video playback state during DOM relocations.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 21, 2026

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.

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

📝 Walkthrough

Walkthrough

This PR implements state preservation for DOM nodes during Teleport operations by introducing a preserveState flag throughout the rendering pipeline. It adds a moveNode utility function that leverages the native moveBefore DOM API when available, updates both Vapor and runtime-core Teleport implementations to use this flag on subsequent mounts, and includes comprehensive end-to-end tests for both Vue and Vapor frameworks.

Changes

Cohort / File(s) Summary
Configuration & Ignore
.gitignore
Added .claude ignore rule
Build & E2E Config
packages-private/vapor-e2e-test/vite.config.ts, packages-private/vapor-e2e-test/index.html
Registered new teleport entry in Vite config; added navigation link to teleport test page
Vapor Teleport E2E Test
packages-private/vapor-e2e-test/__tests__/teleport.spec.ts, packages-private/vapor-e2e-test/teleport/*
New E2E test suite with server setup, Puppeteer integration, and video playback state verification across teleport moves; Vue app component demonstrating teleport with video and test helpers
Core Renderer API
packages/runtime-core/src/renderer.ts, packages/runtime-dom/src/index.ts
Extended insertion and movement APIs with optional preserveState?: boolean parameter; propagated flag through rendering flow for fragments and nested operations
DOM Operations
packages/runtime-dom/src/nodeOps.ts
Added moveNode utility preferring moveBefore API; updated insert method to accept preserveState and delegate to moveNode when flag is true
Teleport Implementation
packages/runtime-core/src/components/Teleport.ts
Updated moveTeleport to pass preserveState = true flag to child moves
Vapor Block & Teleport
packages/runtime-vapor/src/block.ts, packages/runtime-vapor/src/components/Teleport.ts
Added preserveState parameter to move function; updated Teleport to differentiate first mount (insert) from subsequent moves (move with preserveState); integrated moveNode for state-preserving relocations
Vue E2E Tests
packages/vue/__tests__/e2e/teleport.html, packages/vue/__tests__/e2e/teleport.spec.ts
New E2E test with static HTML scaffold and Puppeteer-based spec validating video playback continuity across teleport operations with moveBefore fallback handling

Sequence Diagram(s)

sequenceDiagram
    participant VueComponent as Vue Component
    participant Teleport as Teleport
    participant Renderer as Renderer
    participant DOM as DOM

    rect rgba(100, 200, 255, 0.5)
    note over VueComponent,DOM: First Mount
    VueComponent->>Teleport: mount(target)
    Teleport->>Renderer: insert(video, target)
    Renderer->>DOM: insertBefore(video)
    note right of Teleport: isMounted = true
    end

    rect rgba(200, 150, 255, 0.5)
    note over VueComponent,DOM: Subsequent Move (preserveState=true)
    VueComponent->>Teleport: mount(newTarget)
    Teleport->>Renderer: move(video, newTarget, null, true)
    Renderer->>DOM: moveBefore(video, null)
    note right of DOM: Video state preserved
    end

    rect rgba(255, 200, 100, 0.5)
    note over VueComponent,DOM: Fallback (moveBefore unavailable)
    Teleport->>Renderer: move(video, oldTarget, null, true)
    Renderer->>DOM: insertBefore(video)
    note right of DOM: State preserved via insertBefore
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

scope: teleport

Poem

🐰 Hop, skip, and teleport away—
State preserved, come what may!
Videos dance from place to place,
With moveBefore's swift embrace.

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main purpose of the changeset: implementing moveBefore functionality in Teleport to preserve node state during moves.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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

@github-actions
Copy link

github-actions bot commented Jan 21, 2026

Size Report

Bundles

File Size Gzip Brotli
compiler-dom.global.prod.js 85.5 kB 30 kB 26.4 kB
runtime-dom.global.prod.js 109 kB (+164 B) 40.8 kB (+54 B) 36.7 kB (+21 B)
vue.global.prod.js 167 kB (+164 B) 60.8 kB (+51 B) 54.2 kB (+33 B)

Usages

Name Size Gzip Brotli
createApp (CAPI only) 48.4 kB (+156 B) 18.9 kB (+50 B) 17.4 kB (+60 B)
createApp 57.4 kB (+156 B) 22.1 kB (+47 B) 20.2 kB (+58 B)
createApp + vaporInteropPlugin 78.7 kB (+158 B) 29.1 kB (+51 B) 26.4 kB (+23 B)
createVaporApp 27.9 kB 10.7 kB 9.83 kB
createSSRApp 61.8 kB (+156 B) 23.9 kB (+55 B) 21.8 kB (+56 B)
defineCustomElement 63.5 kB (+156 B) 23.9 kB (+48 B) 21.8 kB (+52 B)
overall 72.2 kB (+156 B) 27.3 kB (+51 B) 24.9 kB (+24 B)

@pkg-pr-new
Copy link

pkg-pr-new bot commented Jan 21, 2026

Open in StackBlitz

@vue/compiler-core

pnpm add https://pkg.pr.new/@vue/compiler-core@14345
npm i https://pkg.pr.new/@vue/compiler-core@14345
yarn add https://pkg.pr.new/@vue/compiler-core@14345.tgz

@vue/compiler-dom

pnpm add https://pkg.pr.new/@vue/compiler-dom@14345
npm i https://pkg.pr.new/@vue/compiler-dom@14345
yarn add https://pkg.pr.new/@vue/compiler-dom@14345.tgz

@vue/compiler-sfc

pnpm add https://pkg.pr.new/@vue/compiler-sfc@14345
npm i https://pkg.pr.new/@vue/compiler-sfc@14345
yarn add https://pkg.pr.new/@vue/compiler-sfc@14345.tgz

@vue/compiler-ssr

pnpm add https://pkg.pr.new/@vue/compiler-ssr@14345
npm i https://pkg.pr.new/@vue/compiler-ssr@14345
yarn add https://pkg.pr.new/@vue/compiler-ssr@14345.tgz

@vue/compiler-vapor

pnpm add https://pkg.pr.new/@vue/compiler-vapor@14345
npm i https://pkg.pr.new/@vue/compiler-vapor@14345
yarn add https://pkg.pr.new/@vue/compiler-vapor@14345.tgz

@vue/reactivity

pnpm add https://pkg.pr.new/@vue/reactivity@14345
npm i https://pkg.pr.new/@vue/reactivity@14345
yarn add https://pkg.pr.new/@vue/reactivity@14345.tgz

@vue/runtime-core

pnpm add https://pkg.pr.new/@vue/runtime-core@14345
npm i https://pkg.pr.new/@vue/runtime-core@14345
yarn add https://pkg.pr.new/@vue/runtime-core@14345.tgz

@vue/runtime-dom

pnpm add https://pkg.pr.new/@vue/runtime-dom@14345
npm i https://pkg.pr.new/@vue/runtime-dom@14345
yarn add https://pkg.pr.new/@vue/runtime-dom@14345.tgz

@vue/runtime-vapor

pnpm add https://pkg.pr.new/@vue/runtime-vapor@14345
npm i https://pkg.pr.new/@vue/runtime-vapor@14345
yarn add https://pkg.pr.new/@vue/runtime-vapor@14345.tgz

@vue/server-renderer

pnpm add https://pkg.pr.new/@vue/server-renderer@14345
npm i https://pkg.pr.new/@vue/server-renderer@14345
yarn add https://pkg.pr.new/@vue/server-renderer@14345.tgz

@vue/shared

pnpm add https://pkg.pr.new/@vue/shared@14345
npm i https://pkg.pr.new/@vue/shared@14345
yarn add https://pkg.pr.new/@vue/shared@14345.tgz

vue

pnpm add https://pkg.pr.new/vue@14345
npm i https://pkg.pr.new/vue@14345
yarn add https://pkg.pr.new/vue@14345.tgz

@vue/compat

pnpm add https://pkg.pr.new/@vue/compat@14345
npm i https://pkg.pr.new/@vue/compat@14345
yarn add https://pkg.pr.new/@vue/compat@14345.tgz

commit: 27cea69

@edison1105 edison1105 added version: minor scope: vapor related to vapor mode labels Jan 21, 2026
@edison1105 edison1105 changed the title feat(vapor): use moveBefore for teleport to preserve node state feat(teleport): use moveBefore for teleport to preserve node state Jan 21, 2026
@edison1105 edison1105 changed the title feat(teleport): use moveBefore for teleport to preserve node state feat(teleport): use moveBefore to preserve node state Jan 21, 2026
@edison1105 edison1105 changed the title feat(teleport): use moveBefore to preserve node state feat(teleport): use moveBefore to preserve node state when possiable Jan 22, 2026
@edison1105 edison1105 changed the title feat(teleport): use moveBefore to preserve node state when possiable feat(teleport): use moveBefore to preserve node state when possible Jan 22, 2026
@edison1105 edison1105 marked this pull request as ready for review January 22, 2026 01:40
@edison1105 edison1105 removed the scope: vapor related to vapor mode label Jan 22, 2026
@netlify
Copy link

netlify bot commented Jan 22, 2026

Deploy Preview for vue-sfc-playground failed. Why did it fail? →

Name Link
🔨 Latest commit 11bfdcf
🔍 Latest deploy log https://app.netlify.com/projects/vue-sfc-playground/deploys/697184f11e82b0000837d99e

@netlify
Copy link

netlify bot commented Jan 22, 2026

Deploy Preview for vue-sfc-playground failed. Why did it fail? →

Name Link
🔨 Latest commit 9a0eed6
🔍 Latest deploy log https://app.netlify.com/projects/vue-sfc-playground/deploys/69718510035ac10008b21168

@edison1105 edison1105 moved this to In Progress in Next Minor Jan 22, 2026
@edison1105
Copy link
Member Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Jan 23, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@packages/runtime-vapor/src/components/Teleport.ts`:
- Line 63: The Teleport fragment's isMounted flag is set true after first mount
but not reset in remove(), causing subsequent re-adds to call move() instead of
insert(); update the Teleport/TeleportFragment remove() method to set
this.isMounted = false when the fragment is removed so the next mount path uses
insert() (also apply the same reset to the analogous remove() implementation
around the 253-280 region).
- Around line 161-179: The move() call in Teleport.ts is passing undefined for
the moveType which defaults to MoveType.LEAVE and triggers leave transitions;
change the call in the isMounted branch to pass MoveType.REORDER instead of
undefined to prevent unintended leave behavior (i.e., replace the third-to-last
undefined argument with MoveType.REORDER), and add an import for MoveType from
'@vue/runtime-dom'; you can find this near the existing move/insert usage and
where applyTransitionHooks() is called.

In `@packages/vue/__tests__/e2e/teleport.spec.ts`:
- Around line 1-7: The test file imports beforeAll but uses afterAll without
importing it; update the import from 'vitest' to include afterAll (i.e., add
afterAll to the named imports alongside beforeAll) so the afterAll hook used
later in this file is explicitly imported rather than relying on globals.
🧹 Nitpick comments (2)
packages-private/vapor-e2e-test/teleport/index.html (1)

1-2: Missing DOCTYPE declaration.

HTML files should include a <!DOCTYPE html> declaration for proper standards-mode rendering. While this may work for e2e tests, it's good practice to include it for consistency with browser behavior.

Suggested fix
+<!DOCTYPE html>
 <script type="module" src="./main.ts"></script>
 <div id="app"></div>
packages-private/vapor-e2e-test/__tests__/teleport.spec.ts (1)

13-20: Consider dynamic port allocation to avoid conflicts in parallel test runs.

The hardcoded port 8197 could cause test failures if multiple test suites run in parallel or if the port is already in use. Consider using port 0 to let the OS assign an available port, then retrieve the actual port from the server.

♻️ Suggested improvement
 describe('vapor teleport', () => {
   let server: any
-  const port = '8197'
+  let port: number

   beforeAll(() => {
-    server = connect()
+    const app = connect()
       .use(sirv(path.resolve(import.meta.dirname, '../dist')))
-      .listen(port)
+    server = app.listen(0)
+    port = (server.address() as any).port
     process.on('SIGTERM', () => server && server.close())
   })

@edison1105
Copy link
Member Author

/ecosystem-ci run

@vue-bot
Copy link
Contributor

vue-bot commented Jan 23, 2026

📝 Ran ecosystem CI: Open

suite result latest scheduled
radix-vue success success
nuxt failure success
quasar success success
pinia success success
primevue success success
vueuse failure success
vue-simple-compiler success success
language-tools success success
router failure failure
vuetify failure success
vitepress success success
vue-macros success success
test-utils failure success
vite-plugin-vue success success
vant success success
vue-i18n failure success

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

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

2 participants