]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(compiler-vapor): prevent caching variables in function expression (#13244)
authorzhiyuanzmj <260480378@qq.com>
Wed, 18 Jun 2025 00:53:38 +0000 (08:53 +0800)
committerGitHub <noreply@github.com>
Wed, 18 Jun 2025 00:53:38 +0000 (08:53 +0800)
packages/compiler-vapor/__tests__/transforms/__snapshots__/transformTemplateRef.spec.ts.snap
packages/compiler-vapor/__tests__/transforms/__snapshots__/vBind.spec.ts.snap
packages/compiler-vapor/__tests__/transforms/transformTemplateRef.spec.ts
packages/compiler-vapor/__tests__/transforms/vBind.spec.ts
packages/compiler-vapor/src/generators/expression.ts

index f2eade4bcdf4e275f5a0b839ae9d762de1bb54a7..4a691056ae2c6090e490a412bf1b9cdec826343e 100644 (file)
@@ -13,6 +13,19 @@ export function render(_ctx) {
 }"
 `;
 
+exports[`compiler: template ref transform > function ref 1`] = `
+"import { createTemplateRefSetter as _createTemplateRefSetter, renderEffect as _renderEffect, template as _template } from 'vue';
+const t0 = _template("<div></div>", true)
+
+export function render(_ctx) {
+  const _setTemplateRef = _createTemplateRefSetter()
+  const n0 = t0()
+  let r0
+  _renderEffect(() => r0 = _setTemplateRef(n0, bar => _ctx.foo = bar, r0))
+  return n0
+}"
+`;
+
 exports[`compiler: template ref transform > ref + v-for 1`] = `
 "import { createTemplateRefSetter as _createTemplateRefSetter, createFor as _createFor, template as _template } from 'vue';
 const t0 = _template("<div></div>", true)
index 6e7d4229df8f766351f3a2726313f14e089e1828..9ffac2fb9328cf8af3c55322741a835886ea87b8 100644 (file)
@@ -75,6 +75,17 @@ export function render(_ctx) {
 }"
 `;
 
+exports[`cache multiple access > not cache variable in function expression 1`] = `
+"import { setDynamicProps as _setDynamicProps, renderEffect as _renderEffect, template as _template } from 'vue';
+const t0 = _template("<div></div>", true)
+
+export function render(_ctx) {
+  const n0 = t0()
+  _renderEffect(() => _setDynamicProps(n0, [{ foo: bar => _ctx.foo = bar }], true))
+  return n0
+}"
+`;
+
 exports[`cache multiple access > not cache variable only used in property shorthand 1`] = `
 "import { setStyle as _setStyle, renderEffect as _renderEffect, template as _template } from 'vue';
 const t0 = _template("<div></div>", true)
index 6be8f18779cc777e9f966105bf24eead7668d08b..f026675e4ebc537ed4805e021b1281fdf243378d 100644 (file)
@@ -81,6 +81,40 @@ describe('compiler: template ref transform', () => {
     expect(code).contains('_setTemplateRef(n0, _ctx.foo, r0)')
   })
 
+  test('function ref', () => {
+    const { ir, code } = compileWithTransformRef(
+      `<div :ref="bar => foo = bar" />`,
+    )
+    expect(ir.block.dynamic.children[0]).toMatchObject({
+      id: 0,
+      flags: DynamicFlag.REFERENCED,
+    })
+    expect(ir.template).toEqual(['<div></div>'])
+    expect(ir.block.operation).toMatchObject([
+      {
+        type: IRNodeTypes.DECLARE_OLD_REF,
+        id: 0,
+      },
+    ])
+    expect(ir.block.effect).toMatchObject([
+      {
+        operations: [
+          {
+            type: IRNodeTypes.SET_TEMPLATE_REF,
+            element: 0,
+            value: {
+              content: 'bar => foo = bar',
+              isStatic: false,
+            },
+          },
+        ],
+      },
+    ])
+    expect(code).toMatchSnapshot()
+    expect(code).contains('const _setTemplateRef = _createTemplateRefSetter()')
+    expect(code).contains('_setTemplateRef(n0, bar => _ctx.foo = bar, r0)')
+  })
+
   test('ref + v-if', () => {
     const { ir, code } = compileWithTransformRef(
       `<div ref="foo" v-if="true" />`,
index 9a5f6ab69710a78246e79696d4ac2ef279e6665b..60c3ebf0c275cbc067e4ff648b649288cfbe5b06 100644 (file)
@@ -809,4 +809,12 @@ describe('cache multiple access', () => {
     expect(code).matchSnapshot()
     expect(code).not.contains('const _bar = _ctx.bar')
   })
+
+  test('not cache variable in function expression', () => {
+    const { code } = compileWithVBind(`
+        <div v-bind="{ foo: bar => foo = bar }"></div>
+      `)
+    expect(code).matchSnapshot()
+    expect(code).not.contains('const _bar = _ctx.bar')
+  })
 })
index eedaeeb380a9459fee7383626331cae270e2fa26..eab50c625917cf18c63c66b384fabb096c1a101c 100644 (file)
@@ -20,7 +20,6 @@ import type { Identifier, Node } from '@babel/types'
 import type { CodegenContext } from '../generate'
 import { isConstantExpression } from '../utils'
 import { type CodeFragment, NEWLINE, buildCodeFragment } from './utils'
-import { walk } from 'estree-walker'
 import { type ParserOptions, parseExpression } from '@babel/parser'
 
 export function genExpression(
@@ -295,33 +294,15 @@ function analyzeExpressions(expressions: SimpleExpressionNode[]) {
       continue
     }
 
-    walk(exp.ast, {
-      enter(currentNode: Node, parent: Node | null) {
-        if (currentNode.type === 'MemberExpression') {
-          const memberExp = extractMemberExpression(
-            currentNode,
-            (name: string) => {
-              registerVariable(name, exp, true)
-            },
-          )
-          registerVariable(memberExp, exp, false)
-          return this.skip()
-        }
-
-        // skip shorthand or non-computed property keys
-        if (
-          parent &&
-          parent.type === 'ObjectProperty' &&
-          parent.key === currentNode &&
-          (parent.shorthand || !parent.computed)
-        ) {
-          return this.skip()
-        }
-
-        if (currentNode.type === 'Identifier') {
-          registerVariable(currentNode.name, exp, true)
-        }
-      },
+    walkIdentifiers(exp.ast, (currentNode, parent, parentStack) => {
+      if (parent && isMemberExpression(parent)) {
+        const memberExp = extractMemberExpression(parent, name => {
+          registerVariable(name, exp, true)
+        })
+        registerVariable(memberExp, exp, false)
+      } else if (!parentStack.some(isMemberExpression)) {
+        registerVariable(currentNode.name, exp, true)
+      }
     })
   }
 
@@ -580,3 +561,9 @@ function extractMemberExpression(
       return ''
   }
 }
+
+const isMemberExpression = (node: Node) => {
+  return (
+    node.type === 'MemberExpression' || node.type === 'OptionalMemberExpression'
+  )
+}