diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/expression.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/expression.spec.ts.snap index 518c2a5fe70..454e50e9cbc 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/expression.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/expression.spec.ts.snap @@ -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("
", 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 +}" +`; diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformTemplateRef.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformTemplateRef.spec.ts.snap index f2eade4bcdf..4a691056ae2 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformTemplateRef.spec.ts.snap +++ b/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("
", 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("
", true) diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vBind.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vBind.spec.ts.snap index 6e7d4229df8..9ffac2fb932 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vBind.spec.ts.snap +++ b/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("
", 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("
", true) diff --git a/packages/compiler-vapor/__tests__/transforms/expression.spec.ts b/packages/compiler-vapor/__tests__/transforms/expression.spec.ts index c97decd9ddd..5983bde67d1 100644 --- a/packages/compiler-vapor/__tests__/transforms/expression.spec.ts +++ b/packages/compiler-vapor/__tests__/transforms/expression.spec.ts @@ -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(` +
+ {{ String(foo.id++) }} {{ foo }} {{ bar }} +
+ `) + expect(code).toMatchSnapshot() + expect(code).contains(`_String(_foo.id++)`) + }) }) diff --git a/packages/compiler-vapor/__tests__/transforms/transformTemplateRef.spec.ts b/packages/compiler-vapor/__tests__/transforms/transformTemplateRef.spec.ts index 6be8f18779c..f026675e4eb 100644 --- a/packages/compiler-vapor/__tests__/transforms/transformTemplateRef.spec.ts +++ b/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( + `
`, + ) + expect(ir.block.dynamic.children[0]).toMatchObject({ + id: 0, + flags: DynamicFlag.REFERENCED, + }) + expect(ir.template).toEqual(['
']) + 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( `
`, diff --git a/packages/compiler-vapor/__tests__/transforms/vBind.spec.ts b/packages/compiler-vapor/__tests__/transforms/vBind.spec.ts index 9a5f6ab6971..60c3ebf0c27 100644 --- a/packages/compiler-vapor/__tests__/transforms/vBind.spec.ts +++ b/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(` +
+ `) + expect(code).matchSnapshot() + expect(code).not.contains('const _bar = _ctx.bar') + }) }) diff --git a/packages/compiler-vapor/src/generators/expression.ts b/packages/compiler-vapor/src/generators/expression.ts index eedaeeb380a..1baa8553590 100644 --- a/packages/compiler-vapor/src/generators/expression.ts +++ b/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( @@ -245,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` @@ -256,6 +260,7 @@ export function processExpressions( variableToExpMap, expToVariableMap, seenIdentifier, + updatedVariable, ) // process duplicate expressions after identifier and member expression handling. @@ -264,6 +269,8 @@ export function processExpressions( context, expressions, varDeclarations, + updatedVariable, + expToVariableMap, ) return genDeclarations([...varDeclarations, ...expDeclarations], context) @@ -274,11 +281,13 @@ function analyzeExpressions(expressions: SimpleExpressionNode[]) { const variableToExpMap = new Map>() const expToVariableMap = new Map() const seenIdentifier = new Set() + const updatedVariable = new Set() const registerVariable = ( name: string, exp: SimpleExpressionNode, isIdentifier: boolean, + parentStack: Node[] = [], ) => { if (isIdentifier) seenIdentifier.add(name) seenVariable[name] = (seenVariable[name] || 0) + 1 @@ -287,6 +296,8 @@ 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')) + updatedVariable.add(name) } for (const exp of expressions) { @@ -295,37 +306,25 @@ 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, parentStack) + } else if (!parentStack.some(isMemberExpression)) { + registerVariable(currentNode.name, exp, true, parentStack) + } }) } - return { seenVariable, seenIdentifier, variableToExpMap, expToVariableMap } + return { + seenVariable, + seenIdentifier, + variableToExpMap, + expToVariableMap, + updatedVariable, + } } function processRepeatedVariables( @@ -334,9 +333,11 @@ function processRepeatedVariables( variableToExpMap: Map>, expToVariableMap: Map, seenIdentifier: Set, + updatedVariable: Set, ): 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) @@ -428,12 +429,19 @@ function processRepeatedExpressions( context: CodegenContext, expressions: SimpleExpressionNode[], varDeclarations: DeclarationValue[], + updatedVariable: Set, + expToVariableMap: Map, ): 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 @@ -580,3 +588,9 @@ function extractMemberExpression( return '' } } + +const isMemberExpression = (node: Node) => { + return ( + node.type === 'MemberExpression' || node.type === 'OptionalMemberExpression' + ) +}