Skip to content

refactor(compiler-vapor): introduce a dedicated KEY IR node + generator path#14413

Merged
edison1105 merged 5 commits intovuejs:minorfrom
zhiyuanzmj:key
Feb 6, 2026
Merged

refactor(compiler-vapor): introduce a dedicated KEY IR node + generator path#14413
edison1105 merged 5 commits intovuejs:minorfrom
zhiyuanzmj:key

Conversation

@zhiyuanzmj
Copy link
Member

@zhiyuanzmj zhiyuanzmj commented Feb 4, 2026

Summary by CodeRabbit

  • New Features

    • Enhanced support for handling keyed dynamic children in templates, including v-for and nested element scenarios.
    • Added key support across various template contexts (v-if, v-else-if, components, slots, and elements).
  • Tests

    • Comprehensive test coverage for key insertion and positioning scenarios (prepend, append, middle positions).
    • New test suite for dynamic and static key handling across multiple template patterns.
  • Refactor

    • Simplified internal key code generation mechanism.
    • Updated IR structure to better represent keyed blocks.

Copilot AI review requested due to automatic review settings February 4, 2026 14:01
@coderabbitai
Copy link

coderabbitai bot commented Feb 4, 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.

  • 🔍 Trigger a full review
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 4, 2026

Open in StackBlitz

@vue/compiler-core

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

@vue/compiler-dom

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

@vue/compiler-sfc

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

@vue/compiler-ssr

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

@vue/compiler-vapor

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

@vue/reactivity

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

@vue/runtime-core

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

@vue/runtime-dom

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

@vue/runtime-vapor

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

@vue/server-renderer

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

@vue/shared

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

vue

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

@vue/compat

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

commit: b38d3b2

@github-actions
Copy link

github-actions bot commented Feb 4, 2026

Size Report

Bundles

File Size Gzip Brotli
compiler-dom.global.prod.js 86.4 kB 30.2 kB 26.6 kB
runtime-dom.global.prod.js 109 kB 41.4 kB 37.2 kB
vue.global.prod.js 169 kB 61.2 kB 54.5 kB

Usages

Name Size Gzip Brotli
createApp (CAPI only) 49.5 kB 19.4 kB 17.7 kB
createApp 58.5 kB 22.7 kB 20.7 kB
createApp + vaporInteropPlugin 81.5 kB 30.3 kB 27.4 kB
createVaporApp 28.7 kB 11.1 kB 10.2 kB
createSSRApp 62.8 kB 24.4 kB 22.2 kB
createVaporSSRApp 32.1 kB 12.4 kB 11.4 kB
defineCustomElement 64.5 kB 24.6 kB 22.3 kB
defineVaporCustomElement 39 kB 14.4 kB 13.2 kB
overall 73.3 kB 28 kB 25.4 kB

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR refactors how :key is handled in compiler-vapor by introducing a dedicated KEY IR node + generator path, instead of encoding “keyed blocks” on BlockIRNode.

Changes:

  • Introduces transformKey to emit a new IRNodeTypes.KEY operation and a new genKey codegen implementation.
  • Updates template-wrapping logic so :key can be reserved/split correctly (notably for v-for wrapping).
  • Removes the previous keyed-block mechanism from transformElement, BlockIRNode, and root codegen, and updates/relocates related tests & snapshots.

Reviewed changes

Copilot reviewed 21 out of 21 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
packages/compiler-vapor/src/transforms/vFor.ts Ensures wrapTemplate also reserves key when processing v-for.
packages/compiler-vapor/src/transforms/utils.ts Extends wrapTemplate to treat v-bind:key as reservable when dirs includes key.
packages/compiler-vapor/src/transforms/transformKey.ts New transform that turns dynamic key bindings into a KEY IR operation.
packages/compiler-vapor/src/transforms/transformElement.ts Removes prior keyed-block marking logic from element transform.
packages/compiler-vapor/src/ir/index.ts Adds IRNodeTypes.KEY + KeyIRNode; removes keyed-block fields from BlockIRNode.
packages/compiler-vapor/src/index.ts Exports the new transformKey.
packages/compiler-vapor/src/generators/operation.ts Routes IRNodeTypes.KEY to the new genKey generator.
packages/compiler-vapor/src/generators/key.ts New generator emitting createKeyedFragment calls for KEY ops.
packages/compiler-vapor/src/generators/block.ts Removes old keyed-fragment wrapping helpers/logic from genBlock.
packages/compiler-vapor/src/generate.ts Removes root-level keyed-block special casing; relies on KEY ops.
packages/compiler-vapor/src/compile.ts Adds transformKey into the base transform preset.
packages/compiler-vapor/tests/transforms/vOnce.spec.ts Adds coverage for v-once combined with :key.
packages/compiler-vapor/tests/transforms/transformKey.spec.ts New test suite for KEY IR + codegen behavior.
packages/compiler-vapor/tests/transforms/transformElement.spec.ts Removes key-related assertions now covered by transformKey tests.
packages/compiler-vapor/tests/transforms/logicalIndex.spec.ts Adds logicalIndex/insertion-state coverage for key fragments.
packages/compiler-vapor/tests/transforms/snapshots/vOnce.spec.ts.snap Snapshot updates for new v-once + key test.
packages/compiler-vapor/tests/transforms/snapshots/transformKey.spec.ts.snap New snapshots for key transform/codegen cases.
packages/compiler-vapor/tests/transforms/snapshots/transformElement.spec.ts.snap Removes keyed-fragment snapshots tied to old keyed-block behavior.
packages/compiler-vapor/tests/transforms/snapshots/logicalIndex.spec.ts.snap Adds snapshots for new key insertion-state cases.
packages/compiler-vapor/tests/transforms/snapshots/TransformTransition.spec.ts.snap Snapshot updates due to transform ordering/ID shifts with KEY ops.
packages/compiler-vapor/tests/transforms/TransformTransition.spec.ts Adds transformKey to the transform chain and updates expectation string.
Comments suppressed due to low confidence (1)

packages/compiler-vapor/src/transforms/utils.ts:90

  • wrapTemplate now correctly reserves v-bind:key / :key when dirs includes 'key' for non-<template> nodes, but the node.tagType === ElementTypes.TEMPLATE branch above still only checks dirs.includes(prop.name) and will not reserve :key (since its directive name is 'bind'). In cases where a <template> needs to be wrapped (e.g. combining structural directives) this can push :key down into the child, allowing transformKey to run on it (or leaving the key on the wrong node) and produce an extra keyed fragment.

Apply the same v-bind:key detection logic to the template-branch splitting as well so :key stays with the intended wrapper when dirs includes 'key'.

  const reserved: Array<AttributeNode | DirectiveNode> = []
  const pass: Array<AttributeNode | DirectiveNode> = []
  node.props.forEach(prop => {
    if (
      prop.type === NodeTypes.DIRECTIVE &&
      (dirs.includes(prop.name) ||
        (prop.name === 'bind' &&
          prop.arg &&
          prop.arg.type === NodeTypes.SIMPLE_EXPRESSION &&
          prop.arg.content === 'key' &&
          dirs.includes('key')))
    ) {
      reserved.push(prop)
    } else {
      pass.push(prop)
    }
  })

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@zhiyuanzmj zhiyuanzmj added version: minor scope: vapor related to vapor mode labels Feb 4, 2026
@edison1105 edison1105 changed the title fix(compiler-vapor): split key into new IR refactor(compiler-vapor): introduce a dedicated KEY IR node + generator path Feb 6, 2026
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: 1

🤖 Fix all issues with AI agents
In `@packages/compiler-vapor/__tests__/transforms/vOnce.spec.ts`:
- Around line 220-224: The test uses an inconsistent Jest matcher: change the
incorrect expect(code).matchSnapshot() in the 'with key' test to the standard
expect(code).toMatchSnapshot() so it matches the other tests; locate the
assertion that calls matchSnapshot() (in the test that calls
compileWithOnce(`<div :key="foo" v-once />`)) and replace it with
toMatchSnapshot().
🧹 Nitpick comments (1)
packages/compiler-vapor/src/transforms/transformKey.ts (1)

23-23: Prefer const for non-reassigned variables.

id is never reassigned after initialization.

♻️ Suggested fix
-  let id = context.reference()
+  const id = context.reference()

@edison1105 edison1105 merged commit 74d08c8 into vuejs:minor Feb 6, 2026
15 checks passed
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 version: minor

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants