]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
refactor(compile): add independent transform for VBindShorthand
authordaiwei <daiwei521@126.com>
Thu, 5 Jun 2025 06:52:12 +0000 (14:52 +0800)
committerdaiwei <daiwei521@126.com>
Thu, 5 Jun 2025 06:52:12 +0000 (14:52 +0800)
12 files changed:
packages/compiler-core/__tests__/transforms/vBind.spec.ts
packages/compiler-core/__tests__/transforms/vFor.spec.ts
packages/compiler-core/__tests__/transforms/vIf.spec.ts
packages/compiler-core/src/compile.ts
packages/compiler-core/src/index.ts
packages/compiler-core/src/transforms/transformVBindShorthand.ts [new file with mode: 0644]
packages/compiler-core/src/transforms/vBind.ts
packages/compiler-core/src/transforms/vFor.ts
packages/compiler-dom/__tests__/transforms/__snapshots__/vModel.spec.ts.snap
packages/compiler-dom/__tests__/transforms/vModel.spec.ts
packages/compiler-ssr/__tests__/ssrTransitionGroup.spec.ts
packages/compiler-ssr/src/index.ts

index be063b8a9d5c630fd21ef463b98e8a652cc05a4d..2221d926e52f5784c9e3fcdd92f043d9806d6abe 100644 (file)
@@ -17,6 +17,7 @@ import {
   helperNameMap,
 } from '../../src/runtimeHelpers'
 import { transformExpression } from '../../src/transforms/transformExpression'
+import { transformVBindShorthand } from '../../src/transforms/transformVBindShorthand'
 
 function parseWithVBind(
   template: string,
@@ -25,6 +26,7 @@ function parseWithVBind(
   const ast = parse(template)
   transform(ast, {
     nodeTransforms: [
+      transformVBindShorthand,
       ...(options.prefixIdentifiers ? [transformExpression] : []),
       transformElement,
     ],
index fead2476ac59fe6f876a2e775c787cfb0f527a37..db872be460c4235baebba90203ef457745297955 100644 (file)
@@ -21,6 +21,7 @@ import { type CompilerOptions, generate } from '../../src'
 import { FRAGMENT, RENDER_LIST, RENDER_SLOT } from '../../src/runtimeHelpers'
 import { PatchFlags } from '@vue/shared'
 import { createObjectMatcher } from '../testUtils'
+import { transformVBindShorthand } from '../../src/transforms/transformVBindShorthand'
 
 export function parseWithForTransform(
   template: string,
@@ -32,6 +33,7 @@ export function parseWithForTransform(
   const ast = parse(template, options)
   transform(ast, {
     nodeTransforms: [
+      transformVBindShorthand,
       transformIf,
       transformFor,
       ...(options.prefixIdentifiers ? [transformExpression] : []),
index 2c2fedab0d5850cc718a668b046646998c0ebe4f..734c0cf22b2d56e8feabf1e00b8d34cec41d316c 100644 (file)
@@ -17,7 +17,12 @@ import {
   type VNodeCall,
 } from '../../src/ast'
 import { ErrorCodes } from '../../src/errors'
-import { type CompilerOptions, TO_HANDLERS, generate } from '../../src'
+import {
+  type CompilerOptions,
+  TO_HANDLERS,
+  generate,
+  transformVBindShorthand,
+} from '../../src'
 import {
   CREATE_COMMENT,
   FRAGMENT,
@@ -35,7 +40,12 @@ function parseWithIfTransform(
 ) {
   const ast = parse(template, options)
   transform(ast, {
-    nodeTransforms: [transformIf, transformSlotOutlet, transformElement],
+    nodeTransforms: [
+      transformVBindShorthand,
+      transformIf,
+      transformSlotOutlet,
+      transformElement,
+    ],
     ...options,
   })
   if (!options.onError) {
@@ -209,6 +219,16 @@ describe('compiler: v-if', () => {
         content: `_ctx.ok`,
       })
     })
+
+    //#11321
+    test('v-if + :key shorthand', () => {
+      const { node } = parseWithIfTransform(`<div v-if="ok" :key></div>`)
+      expect(node.type).toBe(NodeTypes.IF)
+      expect(node.branches[0].userKey).toMatchObject({
+        arg: { content: 'key' },
+        exp: { content: 'key' },
+      })
+    })
   })
 
   describe('errors', () => {
index a697c9d22e679a8d0c7726aa8faa1933feb41026..eba004272899fc395bcf9a5b848525e936b856e3 100644 (file)
@@ -22,6 +22,7 @@ import { transformModel } from './transforms/vModel'
 import { transformFilter } from './compat/transformFilter'
 import { ErrorCodes, createCompilerError, defaultOnError } from './errors'
 import { transformMemo } from './transforms/vMemo'
+import { transformVBindShorthand } from './transforms/transformVBindShorthand'
 
 export type TransformPreset = [
   NodeTransform[],
@@ -33,6 +34,7 @@ export function getBaseTransformPreset(
 ): TransformPreset {
   return [
     [
+      transformVBindShorthand,
       transformOnce,
       transformIf,
       transformMemo,
index 29e5f681300aadc918134985832e0c55ff31f925..f3e6454bd6e7d321ccd3790ca146d82cd0aa0b67 100644 (file)
@@ -66,6 +66,7 @@ export {
   buildDirectiveArgs,
   type PropsExpression,
 } from './transforms/transformElement'
+export { transformVBindShorthand } from './transforms/transformVBindShorthand'
 export { processSlotOutlet } from './transforms/transformSlotOutlet'
 export { getConstantType } from './transforms/cacheStatic'
 export { generateCodeFrame } from '@vue/shared'
diff --git a/packages/compiler-core/src/transforms/transformVBindShorthand.ts b/packages/compiler-core/src/transforms/transformVBindShorthand.ts
new file mode 100644 (file)
index 0000000..a9d0339
--- /dev/null
@@ -0,0 +1,36 @@
+import { camelize } from '@vue/shared'
+import {
+  NodeTypes,
+  type SimpleExpressionNode,
+  createSimpleExpression,
+} from '../ast'
+import type { NodeTransform } from '../transform'
+import { ErrorCodes, createCompilerError } from '../errors'
+
+export const transformVBindShorthand: NodeTransform = (node, context) => {
+  if (node.type === NodeTypes.ELEMENT) {
+    for (const prop of node.props) {
+      // same-name shorthand - :arg is expanded to :arg="arg"
+      if (
+        prop.type === NodeTypes.DIRECTIVE &&
+        prop.name === 'bind' &&
+        !prop.exp
+      ) {
+        const arg = prop.arg!
+        if (arg.type !== NodeTypes.SIMPLE_EXPRESSION || !arg.isStatic) {
+          // only simple expression is allowed for same-name shorthand
+          context.onError(
+            createCompilerError(
+              ErrorCodes.X_V_BIND_INVALID_SAME_NAME_ARGUMENT,
+              arg.loc,
+            ),
+          )
+          prop.exp = createSimpleExpression('', true, arg.loc)
+        } else {
+          const propName = camelize((arg as SimpleExpressionNode).content)
+          prop.exp = createSimpleExpression(propName, false, arg.loc)
+        }
+      }
+    }
+  }
+}
index 1e5e371418bc5ecfcc871270c6a173d359ec38d6..6977b21efe6c23a026fb4a360b5ac078764541c3 100644 (file)
@@ -1,16 +1,13 @@
-import type { DirectiveTransform, TransformContext } from '../transform'
+import type { DirectiveTransform } from '../transform'
 import {
-  type DirectiveNode,
   type ExpressionNode,
   NodeTypes,
-  type SimpleExpressionNode,
   createObjectProperty,
   createSimpleExpression,
 } from '../ast'
 import { ErrorCodes, createCompilerError } from '../errors'
 import { camelize } from '@vue/shared'
 import { CAMELIZE } from '../runtimeHelpers'
-import { processExpression } from './transformExpression'
 
 // v-bind without arg is handled directly in ./transformElement.ts due to its affecting
 // codegen for the entire props object. This transform here is only for v-bind
@@ -40,27 +37,6 @@ export const transformBind: DirectiveTransform = (dir, _node, context) => {
     }
   }
 
-  // same-name shorthand - :arg is expanded to :arg="arg"
-  if (!exp) {
-    if (arg.type !== NodeTypes.SIMPLE_EXPRESSION || !arg.isStatic) {
-      // only simple expression is allowed for same-name shorthand
-      context.onError(
-        createCompilerError(
-          ErrorCodes.X_V_BIND_INVALID_SAME_NAME_ARGUMENT,
-          arg.loc,
-        ),
-      )
-      return {
-        props: [
-          createObjectProperty(arg, createSimpleExpression('', true, loc)),
-        ],
-      }
-    }
-
-    transformBindShorthand(dir, context)
-    exp = dir.exp!
-  }
-
   if (arg.type !== NodeTypes.SIMPLE_EXPRESSION) {
     arg.children.unshift(`(`)
     arg.children.push(`) || ""`)
@@ -92,20 +68,7 @@ export const transformBind: DirectiveTransform = (dir, _node, context) => {
   }
 
   return {
-    props: [createObjectProperty(arg, exp)],
-  }
-}
-
-export const transformBindShorthand = (
-  dir: DirectiveNode,
-  context: TransformContext,
-): void => {
-  const arg = dir.arg!
-
-  const propName = camelize((arg as SimpleExpressionNode).content)
-  dir.exp = createSimpleExpression(propName, false, arg.loc)
-  if (!__BROWSER__) {
-    dir.exp = processExpression(dir.exp, context)
+    props: [createObjectProperty(arg, exp!)],
   }
 }
 
index 0dca0ba9ab437216347a353dcabcec01d44fc07b..3e428528654befb6235e06df1cc1b284d8c4f053 100644 (file)
@@ -48,7 +48,6 @@ import {
 import { processExpression } from './transformExpression'
 import { validateBrowserExpression } from '../validateExpression'
 import { PatchFlags } from '@vue/shared'
-import { transformBindShorthand } from './vBind'
 
 export const transformFor: NodeTransform = createStructuralDirectiveTransform(
   'for',
@@ -64,10 +63,6 @@ export const transformFor: NodeTransform = createStructuralDirectiveTransform(
       const memo = findDir(node, 'memo')
       const keyProp = findProp(node, `key`, false, true)
       const isDirKey = keyProp && keyProp.type === NodeTypes.DIRECTIVE
-      if (isDirKey && !keyProp.exp) {
-        // resolve :key shorthand #10882
-        transformBindShorthand(keyProp, context)
-      }
       let keyExp =
         keyProp &&
         (keyProp.type === NodeTypes.ATTRIBUTE
index 5b610745e4184fdab6a6da505a38a0611d851f81..b357ac15a1b2a3ea3f433c14bd2da939bb2c7692 100644 (file)
@@ -48,6 +48,22 @@ return function render(_ctx, _cache) {
 }"
 `;
 
+exports[`compiler: transform v-model > input with v-bind shorthand type after v-model should use dynamic model 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+  with (_ctx) {
+    const { vModelDynamic: _vModelDynamic, withDirectives: _withDirectives, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
+
+    return _withDirectives((_openBlock(), _createElementBlock("input", {
+      "onUpdate:modelValue": $event => ((model) = $event)
+    }, null, 8 /* PROPS */, ["onUpdate:modelValue"])), [
+      [_vModelDynamic, model]
+    ])
+  }
+}"
+`;
+
 exports[`compiler: transform v-model > modifiers > .lazy 1`] = `
 "const _Vue = Vue
 
index 02d188f01b9e6e01f57961a65b2a25e40d1e73a6..6fe39900ca864ef025ceaf0be218fc70d51f216d 100644 (file)
@@ -3,6 +3,7 @@ import {
   generate,
   baseParse as parse,
   transform,
+  transformVBindShorthand,
 } from '@vue/compiler-core'
 import { transformModel } from '../../src/transforms/vModel'
 import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
@@ -18,7 +19,7 @@ import {
 function transformWithModel(template: string, options: CompilerOptions = {}) {
   const ast = parse(template)
   transform(ast, {
-    nodeTransforms: [transformElement],
+    nodeTransforms: [transformVBindShorthand, transformElement],
     directiveTransforms: {
       model: transformModel,
     },
@@ -63,6 +64,14 @@ describe('compiler: transform v-model', () => {
     expect(generate(root).code).toMatchSnapshot()
   })
 
+  // #13169
+  test('input with v-bind shorthand type after v-model should use dynamic model', () => {
+    const root = transformWithModel('<input v-model="model" :type/>')
+
+    expect(root.helpers).toContain(V_MODEL_DYNAMIC)
+    expect(generate(root).code).toMatchSnapshot()
+  })
+
   test('input w/ dynamic v-bind', () => {
     const root = transformWithModel('<input v-bind="obj" v-model="model" />')
 
index 82122e621c7b5890a9160172f51bba692086b270..abbb5f53e6ea72ac2c3eea36f8d7970813064fea 100644 (file)
@@ -101,6 +101,28 @@ describe('transition-group', () => {
     `)
   })
 
+  test('with dynamic tag shorthand', () => {
+    expect(
+      compile(
+        `<transition-group :tag><div v-for="i in list"/></transition-group>`,
+      ).code,
+    ).toMatchInlineSnapshot(`
+      "const { ssrRenderAttrs: _ssrRenderAttrs, ssrRenderList: _ssrRenderList } = require("vue/server-renderer")
+
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
+        _push(\`<\${
+          _ctx.tag
+        }\${
+          _ssrRenderAttrs(_attrs)
+        }>\`)
+        _ssrRenderList(_ctx.list, (i) => {
+          _push(\`<div></div>\`)
+        })
+        _push(\`</\${_ctx.tag}>\`)
+      }"
+    `)
+  })
+
   test('with multi fragments children', () => {
     expect(
       compile(
index f8a686555e8b32e6b941cda69f348af0c0a7dfd6..cc1658d783ce111bde5dbcba6cec69282ede0ec9 100644 (file)
@@ -13,6 +13,7 @@ import {
   transformExpression,
   transformOn,
   transformStyle,
+  transformVBindShorthand,
 } from '@vue/compiler-dom'
 import { ssrCodegenTransform } from './ssrCodegenTransform'
 import { ssrTransformElement } from './transforms/ssrTransformElement'
@@ -55,6 +56,7 @@ export function compile(
     ...options,
     hoistStatic: false,
     nodeTransforms: [
+      transformVBindShorthand,
       ssrTransformIf,
       ssrTransformFor,
       trackVForSlotScopes,