]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(compiler-vapor): prevent caching UpdateExpression (#13346)
authorzhiyuanzmj <260480378@qq.com>
Fri, 20 Jun 2025 00:16:47 +0000 (08:16 +0800)
committerGitHub <noreply@github.com>
Fri, 20 Jun 2025 00:16:47 +0000 (08:16 +0800)
packages/compiler-vapor/__tests__/transforms/__snapshots__/expression.spec.ts.snap
packages/compiler-vapor/__tests__/transforms/__snapshots__/transformTemplateRef.spec.ts.snap
packages/compiler-vapor/__tests__/transforms/expression.spec.ts
packages/compiler-vapor/__tests__/transforms/transformTemplateRef.spec.ts
packages/compiler-vapor/src/generators/expression.ts

index 518c2a5fe70ab860765c010af51dd5eb39020785..454e50e9cbc62c95a66c630f0a321b6bd209007b 100644 (file)
@@ -32,3 +32,25 @@ export function render(_ctx, $props, $emit, $attrs, $slots) {
   return n0
 }"
 `;
+
+exports[`compiler: expression > update expression 1`] = `
+"import { child as _child, toDisplayString as _toDisplayString, setText as _setText, setProp as _setProp, renderEffect as _renderEffect, template as _template } from 'vue';
+const t0 = _template("<div> </div>", true)
+
+export function render(_ctx) {
+  const n1 = t0()
+  const n0 = _child(n1)
+  const x1 = _child(n1)
+  _renderEffect(() => {
+    const _String = String
+    const _foo = _ctx.foo
+    
+    _setText(n0, _toDisplayString(_String(_foo.id++)) + " " + _toDisplayString(_foo) + " " + _toDisplayString(_ctx.bar))
+    _setText(x1, _toDisplayString(_String(_foo.id++)) + " " + _toDisplayString(_foo) + " " + _toDisplayString(_ctx.bar))
+    _setProp(n1, "id", _String(_foo.id++))
+    _setProp(n1, "foo", _foo)
+    _setProp(n1, "bar", _ctx.bar++)
+  })
+  return n1
+}"
+`;
index 4a691056ae2c6090e490a412bf1b9cdec826343e..cb520a4b2a0a2951bcff0626db587ca6230f421d 100644 (file)
@@ -21,7 +21,14 @@ export function render(_ctx) {
   const _setTemplateRef = _createTemplateRefSetter()
   const n0 = t0()
   let r0
-  _renderEffect(() => r0 = _setTemplateRef(n0, bar => _ctx.foo = bar, r0))
+  _renderEffect(() => {
+    const _foo = _ctx.foo
+    r0 = _setTemplateRef(n0, bar => {
+        _foo.value = bar
+        ;({ baz: _ctx.baz } = bar)
+        console.log(_foo.value, _ctx.baz)
+      }, r0)
+  })
   return n0
 }"
 `;
index c97decd9ddd887f196a006f4e4d1034f49117698..5983bde67d14ba3f51face24b98c63214afada82 100644 (file)
@@ -1,9 +1,15 @@
 import { BindingTypes } from '@vue/compiler-dom'
-import { transformChildren, transformText } from '../../src'
+import {
+  transformChildren,
+  transformElement,
+  transformText,
+  transformVBind,
+} from '../../src'
 import { makeCompile } from './_utils'
 
 const compileWithExpression = makeCompile({
-  nodeTransforms: [transformChildren, transformText],
+  nodeTransforms: [transformElement, transformChildren, transformText],
+  directiveTransforms: { bind: transformVBind },
 })
 
 describe('compiler: expression', () => {
@@ -31,4 +37,14 @@ describe('compiler: expression', () => {
     expect(code).toMatchSnapshot()
     expect(code).contains(`$props['bar']`)
   })
+
+  test('update expression', () => {
+    const { code } = compileWithExpression(`
+      <div :id="String(foo.id++)" :foo="foo" :bar="bar++">
+        {{ String(foo.id++) }} {{ foo }} {{ bar }}
+      </div>
+    `)
+    expect(code).toMatchSnapshot()
+    expect(code).contains(`_String(_foo.id++)`)
+  })
 })
index f026675e4ebc537ed4805e021b1281fdf243378d..b6bc479a016ab0096d6fabcf7f5717213a497911 100644 (file)
@@ -83,7 +83,11 @@ describe('compiler: template ref transform', () => {
 
   test('function ref', () => {
     const { ir, code } = compileWithTransformRef(
-      `<div :ref="bar => foo = bar" />`,
+      `<div :ref="bar => {
+        foo.value = bar
+        ;({ baz } = bar)
+        console.log(foo.value, baz)
+      }" />`,
     )
     expect(ir.block.dynamic.children[0]).toMatchObject({
       id: 0,
@@ -103,7 +107,6 @@ describe('compiler: template ref transform', () => {
             type: IRNodeTypes.SET_TEMPLATE_REF,
             element: 0,
             value: {
-              content: 'bar => foo = bar',
               isStatic: false,
             },
           },
@@ -112,7 +115,11 @@ describe('compiler: template ref transform', () => {
     ])
     expect(code).toMatchSnapshot()
     expect(code).contains('const _setTemplateRef = _createTemplateRefSetter()')
-    expect(code).contains('_setTemplateRef(n0, bar => _ctx.foo = bar, r0)')
+    expect(code).contains(`_setTemplateRef(n0, bar => {
+        _foo.value = bar
+        ;({ baz: _ctx.baz } = bar)
+        console.log(_foo.value, _ctx.baz)
+      }, r0)`)
   })
 
   test('ref + v-if', () => {
index eab50c625917cf18c63c66b384fabb096c1a101c..76b04f58d0baabf2ae630a35de2b58109f0ed29a 100644 (file)
@@ -244,8 +244,13 @@ export function processExpressions(
   expressions: SimpleExpressionNode[],
 ): DeclarationResult {
   // analyze variables
-  const { seenVariable, variableToExpMap, expToVariableMap, seenIdentifier } =
-    analyzeExpressions(expressions)
+  const {
+    seenVariable,
+    variableToExpMap,
+    expToVariableMap,
+    seenIdentifier,
+    updatedVariable,
+  } = analyzeExpressions(expressions)
 
   // process repeated identifiers and member expressions
   // e.g., `foo[baz]` will be transformed into `foo_baz`
@@ -255,6 +260,7 @@ export function processExpressions(
     variableToExpMap,
     expToVariableMap,
     seenIdentifier,
+    updatedVariable,
   )
 
   // process duplicate expressions after identifier and member expression handling.
@@ -263,6 +269,8 @@ export function processExpressions(
     context,
     expressions,
     varDeclarations,
+    updatedVariable,
+    expToVariableMap,
   )
 
   return genDeclarations([...varDeclarations, ...expDeclarations], context)
@@ -273,11 +281,13 @@ function analyzeExpressions(expressions: SimpleExpressionNode[]) {
   const variableToExpMap = new Map<string, Set<SimpleExpressionNode>>()
   const expToVariableMap = new Map<SimpleExpressionNode, string[]>()
   const seenIdentifier = new Set<string>()
+  const updatedVariable = new Set<string>()
 
   const registerVariable = (
     name: string,
     exp: SimpleExpressionNode,
     isIdentifier: boolean,
+    parentStack: Node[] = [],
   ) => {
     if (isIdentifier) seenIdentifier.add(name)
     seenVariable[name] = (seenVariable[name] || 0) + 1
@@ -286,6 +296,13 @@ function analyzeExpressions(expressions: SimpleExpressionNode[]) {
       (variableToExpMap.get(name) || new Set()).add(exp),
     )
     expToVariableMap.set(exp, (expToVariableMap.get(exp) || []).concat(name))
+    if (
+      parentStack.some(
+        p => p.type === 'UpdateExpression' || p.type === 'AssignmentExpression',
+      )
+    ) {
+      updatedVariable.add(name)
+    }
   }
 
   for (const exp of expressions) {
@@ -299,14 +316,20 @@ function analyzeExpressions(expressions: SimpleExpressionNode[]) {
         const memberExp = extractMemberExpression(parent, name => {
           registerVariable(name, exp, true)
         })
-        registerVariable(memberExp, exp, false)
+        registerVariable(memberExp, exp, false, parentStack)
       } else if (!parentStack.some(isMemberExpression)) {
-        registerVariable(currentNode.name, exp, true)
+        registerVariable(currentNode.name, exp, true, parentStack)
       }
     })
   }
 
-  return { seenVariable, seenIdentifier, variableToExpMap, expToVariableMap }
+  return {
+    seenVariable,
+    seenIdentifier,
+    variableToExpMap,
+    expToVariableMap,
+    updatedVariable,
+  }
 }
 
 function processRepeatedVariables(
@@ -315,9 +338,11 @@ function processRepeatedVariables(
   variableToExpMap: Map<string, Set<SimpleExpressionNode>>,
   expToVariableMap: Map<SimpleExpressionNode, string[]>,
   seenIdentifier: Set<string>,
+  updatedVariable: Set<string>,
 ): DeclarationValue[] {
   const declarations: DeclarationValue[] = []
   for (const [name, exps] of variableToExpMap) {
+    if (updatedVariable.has(name)) continue
     if (seenVariable[name] > 1 && exps.size > 0) {
       const isIdentifier = seenIdentifier.has(name)
       const varName = isIdentifier ? name : genVarName(name)
@@ -409,12 +434,19 @@ function processRepeatedExpressions(
   context: CodegenContext,
   expressions: SimpleExpressionNode[],
   varDeclarations: DeclarationValue[],
+  updatedVariable: Set<string>,
+  expToVariableMap: Map<SimpleExpressionNode, string[]>,
 ): DeclarationValue[] {
   const declarations: DeclarationValue[] = []
   const seenExp = expressions.reduce(
     (acc, exp) => {
+      const variables = expToVariableMap.get(exp)
       // only handle expressions that are not identifiers
-      if (exp.ast && exp.ast.type !== 'Identifier') {
+      if (
+        exp.ast &&
+        exp.ast.type !== 'Identifier' &&
+        !(variables && variables.some(v => updatedVariable.has(v)))
+      ) {
         acc[exp.content] = (acc[exp.content] || 0) + 1
       }
       return acc