From 8da1e65da7aca1b48c04351163b1f4908a75baef Mon Sep 17 00:00:00 2001 From: daiwei Date: Wed, 18 Dec 2024 21:59:12 +0800 Subject: [PATCH] wip: save --- .../__snapshots__/vBind.spec.ts.snap | 46 +++++ .../__tests__/transforms/vBind.spec.ts | 17 ++ .../src/generators/operation.ts | 175 +++++++++++------- 3 files changed, 169 insertions(+), 69 deletions(-) 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 bf7631dbddc..8d2caf3cbf6 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vBind.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vBind.spec.ts.snap @@ -339,6 +339,52 @@ export function render(_ctx) { }" `; +exports[`compiler v-bind > cache multiple access to the same expression 1`] = ` +"import { setProp as _setProp, setDynamicProps as _setDynamicProps, renderEffect as _renderEffect, template as _template } from 'vue'; +const t0 = _template("
") + +export function render(_ctx) { + const n0 = t0() + const n1 = t0() + const n2 = t0() + const n3 = t0() + const n4 = t0() + const n5 = t0() + const n6 = t0() + const n7 = t0() + const n8 = t0() + const n9 = t0() + const n10 = t0() + _renderEffect(() => { + const _obj = _ctx.obj + const _baz = _ctx.baz + const _foo = _ctx.foo + const _bar = _ctx.bar + const _key = _ctx.key + const _obj_foo_baz = _obj['foo']['baz'] + const _obj_bar = _obj.bar + const _foo_bar_baz = _foo[_bar(_baz)] + const _obj_foo_baz_obj_bar = _obj_foo_baz + _obj_bar + const _foo_bar = _foo + _bar + + _setProp(n0, "id", _obj_foo_baz_obj_bar) + _setProp(n1, "id", _obj_foo_baz_obj_bar) + _setProp(n2, "id", _obj[1][_baz] + _obj_bar) + + _setProp(n3, "id", _foo_bar) + _setProp(n4, "id", _foo_bar) + _setProp(n5, "id", _foo + _foo + _bar) + _setProp(n6, "id", _foo) + + _setProp(n7, "id", _foo_bar_baz) + _setProp(n8, "id", _foo_bar_baz) + _setProp(n9, "id", _bar() + _foo) + _setDynamicProps(n10, [{ [_key+1]: _foo[_key+1]() }]) + }) + return [n0, n1, n2, n3, n4, n5, n6, n7, n8, n9, n10] +}" +`; + exports[`compiler v-bind > dynamic arg 1`] = ` "import { setDynamicProps as _setDynamicProps, renderEffect as _renderEffect, template as _template } from 'vue'; const t0 = _template("
", true) diff --git a/packages/compiler-vapor/__tests__/transforms/vBind.spec.ts b/packages/compiler-vapor/__tests__/transforms/vBind.spec.ts index 4273b4bff01..ed928001a8d 100644 --- a/packages/compiler-vapor/__tests__/transforms/vBind.spec.ts +++ b/packages/compiler-vapor/__tests__/transforms/vBind.spec.ts @@ -693,4 +693,21 @@ describe('compiler v-bind', () => { ) expect(code).matchSnapshot() }) + + test('cache multiple access to the same expression', () => { + const { code } = compileWithVBind(` +
+
+
+
+
+
+
+
+
+
+
+ `) + expect(code).matchSnapshot() + }) }) diff --git a/packages/compiler-vapor/src/generators/operation.ts b/packages/compiler-vapor/src/generators/operation.ts index 903ebde4ee7..b093b5cff8c 100644 --- a/packages/compiler-vapor/src/generators/operation.ts +++ b/packages/compiler-vapor/src/generators/operation.ts @@ -22,7 +22,7 @@ import { type SimpleExpressionNode, createSimpleExpression, } from '@vue/compiler-core' -import { extend, NOOP } from '@vue/shared' +import { NOOP, extend } from '@vue/shared' import { genExpression } from './expression' import { walk } from 'estree-walker' import type { Node } from '@babel/types' @@ -151,33 +151,31 @@ function genDeclarations( context: CodegenContext, ): [Record, CodeFragment[]] { const [frag, push] = buildCodeFragment() - const ids = declarations.reduce( - (acc, cur) => { - if (!cur.replacement.includes('_')) { - acc[cur.replacement] = `_${cur.replacement}` - push( - `const _${cur.replacement} = `, - ...genExpression(cur.value, context), - NEWLINE, - ) - } - return acc - }, - {} as Record, - ) + const ids: Record = {} - for (let i = 0; i < declarations.length; i++) { - const { replacement, value } = declarations[i] - const isExp = replacement.includes('_') - if (isExp) { - ids[replacement] = `_${replacement}` - const descareFrag = context.withId( - () => genExpression(value, context), - isExp ? ids : {}, + // process identifiers first + declarations.forEach(({ replacement, value }) => { + if (!replacement.includes('_')) { + const prefixedName = `_${replacement}` + ids[replacement] = prefixedName + push( + `const ${prefixedName} = `, + ...genExpression(value, context), + NEWLINE, ) - push(`const _${replacement} = `, ...descareFrag, NEWLINE) } - } + }) + + // process expressions with potential identifier references + declarations.forEach(({ replacement, value }) => { + if (replacement.includes('_')) { + const prefixedName = `_${replacement}` + ids[replacement] = prefixedName + const expFrag = context.withId(() => genExpression(value, context), ids) + push(`const ${prefixedName} = `, ...expFrag, NEWLINE) + } + }) + return [ids, frag] } @@ -186,29 +184,81 @@ function escapeRegExp(string: string) { } function processExpressions(context: CodegenContext): DeclarationValue[] { - const plugins = context.options.expressionPlugins - const options: BabelOptions = { - plugins: plugins ? [...plugins, 'typescript'] : ['typescript'], - } const { block: { expressions }, } = context + // 1. extract identifiers and member expressions + const { seenExp, replaceMap, memberExpMap } = + extractRepeatedExpressions(expressions) + // 2. handle identifiers and member expressions that appear more than once + // foo + obj.bar -> _foo + _obj.bar + const declarations = processRepeatedExpressions( + context, + seenExp, + replaceMap, + memberExpMap, + ) + // 3. after processing identifiers and member expressions, remaining expressions may still contain duplicates + // for example: `_foo + _obj.bar` may appear multiple times. + // `_foo + _obj.bar` -> `_foo_obj_bar` + processRemainingExpressions(context, expressions, declarations) + + return declarations +} + +function extractRepeatedExpressions(expressions: SimpleExpressionNode[]) { const seenExp: Record = Object.create(null) const replaceMap = new Map>() - const expMap = new Map() + const memberExpMap = new Map() + + for (const exp of expressions) { + const addToMaps = (name: string) => { + seenExp[name] = (seenExp[name] || 0) + 1 + const set = replaceMap.get(name) || new Set() + set.add(exp) + replaceMap.set(name, set) + } + + if (!exp.ast) { + addToMaps(exp.content) + continue + } + + walk(exp.ast, { + enter(currentNode: Node) { + if (currentNode.type === 'MemberExpression') { + const memberExp = getMemberExp(currentNode, addToMaps) + addToMaps(memberExp) + memberExpMap.set(memberExp, currentNode) + return this.skip() + } + + if (currentNode.type === 'Identifier') { + addToMaps(currentNode.name) + } + }, + }) + } + + return { seenExp, replaceMap, memberExpMap } +} + +function processRepeatedExpressions( + context: CodegenContext, + seenExp: Record, + replaceMap: Map>, + memberExpMap: Map, +): DeclarationValue[] { const declarations: DeclarationValue[] = [] - expressions.forEach(exp => - extractExpression(exp, seenExp, replaceMap, expMap), - ) for (const [key, values] of replaceMap) { if (seenExp[key]! > 1 && values.size > 0) { const varName = getReplacementName(key) - const isExp = expMap.has(key) + const isMemberExp = memberExpMap.has(key) if (!declarations.some(d => d.replacement === varName)) { declarations.push({ replacement: varName, value: extend( - { ast: isExp ? parseExpression(`(${key})`, options) : null }, + { ast: isMemberExp ? parseExp(context, key) : null }, createSimpleExpression(key), ), }) @@ -216,11 +266,19 @@ function processExpressions(context: CodegenContext): DeclarationValue[] { const replaceRE = new RegExp(escapeRegExp(key), 'g') values.forEach(node => { node.content = node.content.replace(replaceRE, varName) - node.ast = parseExpression(`(${node.content})`, options) + node.ast = parseExp(context, node.content) }) } } + return declarations +} + +function processRemainingExpressions( + context: CodegenContext, + expressions: SimpleExpressionNode[], + declarations: DeclarationValue[], +): void { const seenContent: Record = Object.create(null) expressions.forEach(exp => { if (exp.ast && exp.ast.type !== 'Identifier') { @@ -236,18 +294,26 @@ function processExpressions(context: CodegenContext): DeclarationValue[] { const varName = getReplacementName(exp.content) const oldContent = exp.content exp.content = varName - exp.ast = parseExpression(`(${exp.content})`, options) + exp.ast = parseExp(context, exp.content) if (!declarations.some(d => d.replacement === varName)) { declarations.push({ replacement: varName, - value: extend({ ast: parseExpression(`(${oldContent})`, options) }, createSimpleExpression(oldContent)), + value: extend( + { ast: parseExp(context, oldContent) }, + createSimpleExpression(oldContent), + ), }) } } }) +} - // console.log({seenExp, replaceMap, declarations, expressions}) - return declarations +function parseExp(context: CodegenContext, content: string): Node { + const plugins = context.options.expressionPlugins + const options: BabelOptions = { + plugins: plugins ? [...plugins, 'typescript'] : ['typescript'], + } + return parseExpression(`(${content})`, options) } function getReplacementName(name: string): string { @@ -257,35 +323,6 @@ function getReplacementName(name: string): string { .replace(/_+$/, '')}` } -function extractExpression( - node: SimpleExpressionNode, - seenExp: Record, - replaceMap: Map>, - expMap: Map, -) { - const add = (name: string) => { - seenExp[name] = (seenExp[name] || 0) + 1 - replaceMap.set(name, (replaceMap.get(name) ?? new Set()).add(node)) - } - if (node.ast) { - walk(node.ast, { - enter(node: Node) { - if (node.type === 'MemberExpression') { - const exp = getMemberExp(node, add) - add(exp) - expMap.set(exp, node) - return this.skip() - } - if (node.type === 'Identifier') { - add(node.name) - } - }, - }) - } else if (node.ast === null) { - add((node as SimpleExpressionNode).content) - } -} - function getMemberExp( expr: Node, onIdentifier: (name: string) => void,