Parcourir la source

fix(compiler-vapor): prevent caching variables in function expression (#13244)

zhiyuanzmj il y a 10 mois
Parent
commit
99482a4ddf

+ 13 - 0
packages/compiler-vapor/__tests__/transforms/__snapshots__/transformTemplateRef.spec.ts.snap

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

+ 11 - 0
packages/compiler-vapor/__tests__/transforms/__snapshots__/vBind.spec.ts.snap

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

+ 34 - 0
packages/compiler-vapor/__tests__/transforms/transformTemplateRef.spec.ts

@@ -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" />`,

+ 8 - 0
packages/compiler-vapor/__tests__/transforms/vBind.spec.ts

@@ -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')
+  })
 })

+ 15 - 28
packages/compiler-vapor/src/generators/expression.ts

@@ -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'
+  )
+}