Skip to content

Commit

Permalink
wip: save
Browse files Browse the repository at this point in the history
  • Loading branch information
edison1105 committed Dec 18, 2024
1 parent c54687d commit 8da1e65
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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("<div></div>")
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("<div></div>", true)
Expand Down
17 changes: 17 additions & 0 deletions packages/compiler-vapor/__tests__/transforms/vBind.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -693,4 +693,21 @@ describe('compiler v-bind', () => {
)
expect(code).matchSnapshot()
})

test('cache multiple access to the same expression', () => {
const { code } = compileWithVBind(`
<div :id="obj['foo']['baz'] + obj.bar"></div>
<div :id="obj['foo']['baz'] + obj.bar"></div>
<div :id="obj[1][baz] + obj.bar"></div>
<div :id="foo + bar"></div>
<div :id="foo + bar"></div>
<div :id="foo + foo + bar"></div>
<div :id="foo"></div>
<div :id="foo[bar(baz)]"></div>
<div :id="foo[bar(baz)]"></div>
<div :id="bar() + foo"></div>
<div :[key+1]="foo[key+1]()" />
`)
expect(code).matchSnapshot()
})
})
175 changes: 106 additions & 69 deletions packages/compiler-vapor/src/generators/operation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -151,33 +151,31 @@ function genDeclarations(
context: CodegenContext,
): [Record<string, string>, 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<string, string>,
)
const ids: Record<string, string> = {}

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]
}

Expand All @@ -186,41 +184,101 @@ 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<string, number> = Object.create(null)
const replaceMap = new Map<string, Set<SimpleExpressionNode>>()
const expMap = new Map<string, Node>()
const memberExpMap = new Map<string, Node>()

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<string, number>,
replaceMap: Map<string, Set<SimpleExpressionNode>>,
memberExpMap: Map<string, Node>,
): 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),
),
})
}
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<string, number> = Object.create(null)
expressions.forEach(exp => {
if (exp.ast && exp.ast.type !== 'Identifier') {
Expand All @@ -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 {
Expand All @@ -257,35 +323,6 @@ function getReplacementName(name: string): string {
.replace(/_+$/, '')}`
}

function extractExpression(
node: SimpleExpressionNode,
seenExp: Record<string, number>,
replaceMap: Map<string, Set<SimpleExpressionNode>>,
expMap: Map<string, Node>,
) {
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,
Expand Down

0 comments on commit 8da1e65

Please sign in to comment.