const n0 = t0()
_delegate(n0, "click", () => _ctx.handleClick)
_renderEffect(() => {
- _setText(n0, _ctx.count, "foo", _ctx.count, "foo", _ctx.count)
- _setProp(n0, "id", _ctx.count)
+ const _count = _ctx.count
+ _setText(n0, _count, "foo", _count, "foo", _count)
+ _setProp(n0, "id", _count)
})
return n0
}"
exports[`compile > expression parsing > v-bind 1`] = `
"
const n0 = t0()
- _renderEffect(() => _setDynamicProps(n0, [{ [key.value+1]: _unref(foo)[key.value+1]() }], true))
+ _renderEffect(() => {
+ const _key = key.value
+ _setDynamicProps(n0, [{ [_key+1]: _unref(foo)[_key+1]() }], true)
+ })
return n0
"
`;
},
})
expect(code).matchSnapshot()
- expect(code).contains('key.value+1')
+ expect(code).contains('const _key = key.value')
+ expect(code).contains('_key+1')
expect(code).contains(
- '_setDynamicProps(n0, [{ [key.value+1]: _unref(foo)[key.value+1]() }], true)',
+ '_setDynamicProps(n0, [{ [_key+1]: _unref(foo)[_key+1]() }], true)',
)
})
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+exports[`cache multiple access > dynamic key bindings with expressions 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(() => {
+ const _key = _ctx.key
+ _setDynamicProps(n0, [{ [_key+1]: _ctx.foo[_key+1]() }], true)
+ })
+ return n0
+}"
+`;
+
+exports[`cache multiple access > dynamic property access 1`] = `
+"import { setProp as _setProp, renderEffect as _renderEffect, template as _template } from 'vue';
+const t0 = _template("<div></div>", true)
+
+export function render(_ctx) {
+ const n0 = t0()
+ _renderEffect(() => {
+ const _obj = _ctx.obj
+ _setProp(n0, "id", _obj[1][_ctx.baz] + _obj.bar)
+ })
+ return n0
+}"
+`;
+
+exports[`cache multiple access > function calls with arguments 1`] = `
+"import { setProp as _setProp, 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()
+ _renderEffect(() => {
+ const _foo = _ctx.foo
+ const _bar = _ctx.bar
+ const _foo_bar_baz = _foo[_bar(_ctx.baz)]
+
+ _setProp(n0, "id", _foo_bar_baz)
+ _setProp(n1, "id", _foo_bar_baz)
+ _setProp(n2, "id", _bar() + _foo)
+ })
+ return [n0, n1, n2]
+}"
+`;
+
+exports[`cache multiple access > not cache variable and member expression with the same name 1`] = `
+"import { setProp as _setProp, renderEffect as _renderEffect, template as _template } from 'vue';
+const t0 = _template("<div></div>", true)
+
+export function render(_ctx) {
+ const n0 = t0()
+ _renderEffect(() => _setProp(n0, "id", _ctx.bar + _ctx.obj.bar))
+ return n0
+}"
+`;
+
+exports[`cache multiple access > object property chain access 1`] = `
+"import { setProp as _setProp, renderEffect as _renderEffect, template as _template } from 'vue';
+const t0 = _template("<div></div>")
+
+export function render(_ctx) {
+ const n0 = t0()
+ const n1 = t0()
+ _renderEffect(() => {
+ const _obj = _ctx.obj
+ const _obj_foo_baz_obj_bar = _obj['foo']['baz'] + _obj.bar
+
+ _setProp(n0, "id", _obj_foo_baz_obj_bar)
+ _setProp(n1, "id", _obj_foo_baz_obj_bar)
+ })
+ return [n0, n1]
+}"
+`;
+
+exports[`cache multiple access > repeated expression in expressions 1`] = `
+"import { setProp as _setProp, 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()
+ _renderEffect(() => {
+ const _foo = _ctx.foo
+ const _foo_bar = _foo + _ctx.bar
+
+ _setProp(n0, "id", _foo_bar)
+ _setProp(n1, "id", _foo_bar)
+ _setProp(n2, "id", _foo + _foo_bar)
+ })
+ return [n0, n1, n2]
+}"
+`;
+
+exports[`cache multiple access > repeated expressions 1`] = `
+"import { setProp as _setProp, renderEffect as _renderEffect, template as _template } from 'vue';
+const t0 = _template("<div></div>")
+
+export function render(_ctx) {
+ const n0 = t0()
+ const n1 = t0()
+ _renderEffect(() => {
+ const _foo_bar = _ctx.foo + _ctx.bar
+
+ _setProp(n0, "id", _foo_bar)
+ _setProp(n1, "id", _foo_bar)
+ })
+ return [n0, n1]
+}"
+`;
+
+exports[`cache multiple access > repeated variable in expressions 1`] = `
+"import { setProp as _setProp, renderEffect as _renderEffect, template as _template } from 'vue';
+const t0 = _template("<div></div>")
+
+export function render(_ctx) {
+ const n0 = t0()
+ const n1 = t0()
+ _renderEffect(() => {
+ const _foo = _ctx.foo
+ _setProp(n0, "id", _foo + _foo + _ctx.bar)
+ _setProp(n1, "id", _foo)
+ })
+ return [n0, n1]
+}"
+`;
+
+exports[`cache multiple access > repeated variables 1`] = `
+"import { setClass as _setClass, renderEffect as _renderEffect, template as _template } from 'vue';
+const t0 = _template("<div></div>")
+
+export function render(_ctx) {
+ const n0 = t0()
+ const n1 = t0()
+ _renderEffect(() => {
+ const _foo = _ctx.foo
+
+ _setClass(n0, _foo)
+ _setClass(n1, _foo)
+ })
+ return [n0, n1]
+}"
+`;
+
exports[`compiler v-bind > .attr modifier 1`] = `
"import { setAttr as _setAttr, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
const n5 = t5()
const n6 = t6()
_renderEffect(() => {
+ const _width = _ctx.width
+ const _height = _ctx.height
_setAttr(n0, "spellcheck", _ctx.spellcheck)
_setAttr(n0, "draggable", _ctx.draggable)
_setAttr(n0, "translate", _ctx.translate)
_setAttr(n1, "list", _ctx.list)
_setAttr(n2, "type", _ctx.type)
- _setAttr(n3, "width", _ctx.width)
- _setAttr(n4, "width", _ctx.width)
- _setAttr(n5, "width", _ctx.width)
- _setAttr(n6, "width", _ctx.width)
+ _setAttr(n3, "width", _width)
+ _setAttr(n4, "width", _width)
+ _setAttr(n5, "width", _width)
+ _setAttr(n6, "width", _width)
- _setAttr(n3, "height", _ctx.height)
- _setAttr(n4, "height", _ctx.height)
- _setAttr(n5, "height", _ctx.height)
- _setAttr(n6, "height", _ctx.height)
+ _setAttr(n3, "height", _height)
+ _setAttr(n4, "height", _height)
+ _setAttr(n5, "height", _height)
+ _setAttr(n6, "height", _height)
})
return [n0, n1, n2, n3, n4, n5, n6]
}"
export function render(_ctx) {
const n0 = t0()
- _renderEffect(() => _setDynamicProps(n0, [{ [_ctx.id]: _ctx.id, [_ctx.title]: _ctx.title }], true))
+ _renderEffect(() => {
+ const _id = _ctx.id
+ const _title = _ctx.title
+ _setDynamicProps(n0, [{ [_id]: _id, [_title]: _title }], true)
+ })
return n0
}"
`;
export function render(_ctx) {
const n0 = t0()
- _renderEffect(() => _setDynamicProps(n0, [{ [_ctx.id]: _ctx.id, foo: "bar", checked: "" }], true))
+ _renderEffect(() => {
+ const _id = _ctx.id
+ _setDynamicProps(n0, [{ [_id]: _id, foo: "bar", checked: "" }], true)
+ })
return n0
}"
`;
],
})
expect(code).contains(
- '_setDynamicProps(n0, [{ [_ctx.id]: _ctx.id, [_ctx.title]: _ctx.title }], true)',
+ '_setDynamicProps(n0, [{ [_id]: _id, [_title]: _title }], true)',
)
})
],
})
expect(code).contains(
- '_setDynamicProps(n0, [{ [_ctx.id]: _ctx.id, foo: "bar", checked: "" }], true)',
+ '_setDynamicProps(n0, [{ [_id]: _id, foo: "bar", checked: "" }], true)',
)
})
expect(code).contains('_setAttr(n0, "form", _ctx.form)')
expect(code).contains('_setAttr(n1, "list", _ctx.list)')
expect(code).contains('_setAttr(n2, "type", _ctx.type)')
- expect(code).contains('_setAttr(n3, "width", _ctx.width)')
- expect(code).contains('_setAttr(n3, "height", _ctx.height)')
- expect(code).contains('_setAttr(n4, "width", _ctx.width)')
- expect(code).contains('_setAttr(n4, "height", _ctx.height)')
- expect(code).contains('_setAttr(n5, "width", _ctx.width)')
- expect(code).contains('_setAttr(n5, "height", _ctx.height)')
- expect(code).contains(' _setAttr(n6, "width", _ctx.width)')
+ expect(code).contains('_setAttr(n3, "width", _width)')
+ expect(code).contains('_setAttr(n3, "height", _height)')
+ expect(code).contains('_setAttr(n4, "width", _width)')
+ expect(code).contains('_setAttr(n4, "height", _height)')
+ expect(code).contains('_setAttr(n5, "width", _width)')
+ expect(code).contains('_setAttr(n5, "height", _height)')
+ expect(code).contains(' _setAttr(n6, "width", _width)')
})
test(':innerHTML', () => {
expect(code).matchSnapshot()
})
})
+
+describe('cache multiple access', () => {
+ test('repeated variables', () => {
+ const { code } = compileWithVBind(`
+ <div :class="foo"></div>
+ <div :class="foo"></div>
+ `)
+ expect(code).matchSnapshot()
+ expect(code).contains('const _foo = _ctx.foo')
+ expect(code).contains('setClass(n0, _foo)')
+ expect(code).contains('setClass(n1, _foo)')
+ })
+
+ test('repeated expressions', () => {
+ const { code } = compileWithVBind(`
+ <div :id="foo + bar"></div>
+ <div :id="foo + bar"></div>
+ `)
+ expect(code).matchSnapshot()
+ expect(code).contains('const _foo_bar = _ctx.foo + _ctx.bar')
+ expect(code).contains('_setProp(n0, "id", _foo_bar)')
+ expect(code).contains('_setProp(n1, "id", _foo_bar)')
+ })
+
+ test('repeated variable in expressions', () => {
+ const { code } = compileWithVBind(`
+ <div :id="foo + foo + bar"></div>
+ <div :id="foo"></div>
+ `)
+ expect(code).matchSnapshot()
+ expect(code).contains('const _foo = _ctx.foo')
+ expect(code).contains('_setProp(n0, "id", _foo + _foo + _ctx.bar)')
+ expect(code).contains('_setProp(n1, "id", _foo)')
+ })
+
+ test('repeated expression in expressions', () => {
+ const { code } = compileWithVBind(`
+ <div :id="foo + bar"></div>
+ <div :id="foo + bar"></div>
+ <div :id="foo + foo + bar"></div>
+ `)
+ expect(code).matchSnapshot()
+ expect(code).contains('const _foo_bar = _foo + _ctx.bar')
+ expect(code).contains('_setProp(n0, "id", _foo_bar)')
+ expect(code).contains('_setProp(n2, "id", _foo + _foo_bar)')
+ })
+
+ test('function calls with arguments', () => {
+ const { code } = compileWithVBind(`
+ <div :id="foo[bar(baz)]"></div>
+ <div :id="foo[bar(baz)]"></div>
+ <div :id="bar() + foo"></div>
+ `)
+ expect(code).matchSnapshot()
+ expect(code).contains('const _foo_bar_baz = _foo[_bar(_ctx.baz)]')
+ expect(code).contains('_setProp(n0, "id", _foo_bar_baz)')
+ expect(code).contains('_setProp(n1, "id", _foo_bar_baz)')
+ expect(code).contains('_setProp(n2, "id", _bar() + _foo)')
+ })
+
+ test('dynamic key bindings with expressions', () => {
+ const { code } = compileWithVBind(`
+ <div :[key+1]="foo[key+1]()" />
+ `)
+ expect(code).matchSnapshot()
+ expect(code).contains('const _key = _ctx.key')
+ expect(code).contains('[{ [_key+1]: _ctx.foo[_key+1]() }]')
+ })
+
+ test('object property chain access', () => {
+ const { code } = compileWithVBind(`
+ <div :id="obj['foo']['baz'] + obj.bar"></div>
+ <div :id="obj['foo']['baz'] + obj.bar"></div>
+ `)
+ expect(code).matchSnapshot()
+ expect(code).contains(
+ "const _obj_foo_baz_obj_bar = _obj['foo']['baz'] + _obj.bar",
+ )
+ expect(code).contains('_setProp(n0, "id", _obj_foo_baz_obj_bar)')
+ expect(code).contains('_setProp(n1, "id", _obj_foo_baz_obj_bar)')
+ })
+
+ test('dynamic property access', () => {
+ const { code } = compileWithVBind(`
+ <div :id="obj[1][baz] + obj.bar"></div>
+ `)
+ expect(code).matchSnapshot()
+ expect(code).contains('const _obj = _ctx.obj')
+ expect(code).contains('_setProp(n0, "id", _obj[1][_ctx.baz] + _obj.bar)')
+ })
+
+ test('not cache variable and member expression with the same name', () => {
+ const { code } = compileWithVBind(`
+ <div :id="bar + obj.bar"></div>
+ `)
+ expect(code).matchSnapshot()
+ expect(code).not.contains('const _bar = _ctx.bar')
+ })
+})
-import { genPropsAccessExp, isGloballyAllowed } from '@vue/shared'
+import { NOOP, extend, genPropsAccessExp, isGloballyAllowed } from '@vue/shared'
import {
BindingTypes,
NewlineType,
type SimpleExpressionNode,
type SourceLocation,
advancePositionWithClone,
+ createSimpleExpression,
isInDestructureAssignment,
isStaticProperty,
walkIdentifiers,
} from '@vue/compiler-dom'
-import type { Identifier } from '@babel/types'
+import type { Identifier, Node } from '@babel/types'
import type { CodegenContext } from '../generate'
-import type { Node } from '@babel/types'
import { isConstantExpression } from '../utils'
-import { type CodeFragment, buildCodeFragment } from './utils'
+import { type CodeFragment, NEWLINE, buildCodeFragment } from './utils'
+import { walk } from 'estree-walker'
+import { type ParserOptions, parseExpression } from '@babel/parser'
export function genExpression(
node: SimpleExpressionNode,
return false
return true
}
+
+type DeclarationResult = {
+ ids: Record<string, string>
+ frag: CodeFragment[]
+}
+type DeclarationValue = {
+ name: string
+ isIdentifier?: boolean
+ value: SimpleExpressionNode
+ rawName?: string
+ exps?: Set<SimpleExpressionNode>
+ seenCount?: number
+}
+
+export function processExpressions(
+ context: CodegenContext,
+ expressions: SimpleExpressionNode[],
+): DeclarationResult {
+ // analyze variables
+ const { seenVariable, variableToExpMap, expToVariableMap, seenIdentifier } =
+ analyzeExpressions(expressions)
+
+ // process repeated identifiers and member expressions
+ // e.g., `foo[baz]` will be transformed into `foo_baz`
+ const varDeclarations = processRepeatedVariables(
+ context,
+ seenVariable,
+ variableToExpMap,
+ expToVariableMap,
+ seenIdentifier,
+ )
+
+ // process duplicate expressions after identifier and member expression handling.
+ // e.g., `foo + bar` will be transformed into `foo_bar`
+ const expDeclarations = processRepeatedExpressions(
+ context,
+ expressions,
+ varDeclarations,
+ )
+
+ return genDeclarations([...varDeclarations, ...expDeclarations], context)
+}
+
+function analyzeExpressions(expressions: SimpleExpressionNode[]) {
+ const seenVariable: Record<string, number> = Object.create(null)
+ const variableToExpMap = new Map<string, Set<SimpleExpressionNode>>()
+ const expToVariableMap = new Map<SimpleExpressionNode, string[]>()
+ const seenIdentifier = new Set<string>()
+
+ const registerVariable = (
+ name: string,
+ exp: SimpleExpressionNode,
+ isIdentifier: boolean,
+ ) => {
+ if (isIdentifier) seenIdentifier.add(name)
+ seenVariable[name] = (seenVariable[name] || 0) + 1
+ variableToExpMap.set(
+ name,
+ (variableToExpMap.get(name) || new Set()).add(exp),
+ )
+ expToVariableMap.set(exp, (expToVariableMap.get(exp) || []).concat(name))
+ }
+
+ for (const exp of expressions) {
+ if (!exp.ast) {
+ exp.ast === null && registerVariable(exp.content, exp, true)
+ continue
+ }
+
+ walk(exp.ast, {
+ enter(currentNode: Node) {
+ if (currentNode.type === 'MemberExpression') {
+ const memberExp = extractMemberExpression(
+ currentNode,
+ (name: string) => {
+ registerVariable(name, exp, true)
+ },
+ )
+ registerVariable(memberExp, exp, false)
+ return this.skip()
+ }
+
+ if (currentNode.type === 'Identifier') {
+ registerVariable(currentNode.name, exp, true)
+ }
+ },
+ })
+ }
+
+ return { seenVariable, seenIdentifier, variableToExpMap, expToVariableMap }
+}
+
+function processRepeatedVariables(
+ context: CodegenContext,
+ seenVariable: Record<string, number>,
+ variableToExpMap: Map<string, Set<SimpleExpressionNode>>,
+ expToVariableMap: Map<SimpleExpressionNode, string[]>,
+ seenIdentifier: Set<string>,
+): DeclarationValue[] {
+ const declarations: DeclarationValue[] = []
+ for (const [name, exps] of variableToExpMap) {
+ if (seenVariable[name] > 1 && exps.size > 0) {
+ const isIdentifier = seenIdentifier.has(name)
+ const varName = isIdentifier ? name : genVarName(name)
+
+ // replaces all non-identifiers with the new name. if node content
+ // includes only one member expression, it will become an identifier,
+ // e.g., foo[baz] -> foo_baz.
+ // for identifiers, we don't need to replace the content - they will be
+ // replaced during context.withId(..., ids)
+ const replaceRE = new RegExp(escapeRegExp(name), 'g')
+ exps.forEach(node => {
+ if (node.ast) {
+ node.content = node.content.replace(replaceRE, varName)
+ // re-parse the expression
+ node.ast = parseExp(context, node.content)
+ }
+ })
+
+ if (
+ !declarations.some(d => d.name === varName) &&
+ (!isIdentifier || shouldDeclareVariable(name, expToVariableMap, exps))
+ ) {
+ declarations.push({
+ name: varName,
+ isIdentifier,
+ value: extend(
+ { ast: isIdentifier ? null : parseExp(context, name) },
+ createSimpleExpression(name),
+ ),
+ rawName: name,
+ exps,
+ seenCount: seenVariable[name],
+ })
+ }
+ }
+ }
+
+ return declarations
+}
+
+function shouldDeclareVariable(
+ name: string,
+ expToVariableMap: Map<SimpleExpressionNode, string[]>,
+ exps: Set<SimpleExpressionNode>,
+): boolean {
+ const vars = Array.from(exps, exp => expToVariableMap.get(exp)!)
+ // assume name equals to `foo`
+ // if each expression only references `foo`, declaration is needed
+ // to avoid reactivity tracking
+ // e.g., [[foo],[foo]]
+ if (vars.every(v => v.length === 1)) {
+ return true
+ }
+
+ // if `foo` appears multiple times in one array, declaration is needed
+ // e.g., [[foo,foo]]
+ if (vars.some(v => v.filter(e => e === name).length > 1)) {
+ return true
+ }
+
+ const first = vars[0]
+ // if arrays have different lengths, declaration is needed
+ // e.g., [[foo],[foo,bar]]
+ if (vars.some(v => v.length !== first.length)) {
+ // special case, no declaration needed if one array is a subset of the other
+ // because they will be treated as repeated expressions
+ // e.g., [[foo,bar],[foo,foo,bar]] -> const foo_bar = _ctx.foo + _ctx.bar
+ if (
+ vars.some(
+ v => v.length > first.length && v.every(e => first.includes(e)),
+ ) ||
+ vars.some(v => first.length > v.length && first.every(e => v.includes(e)))
+ ) {
+ return false
+ }
+ return true
+ }
+ // if arrays share common elements, no declaration needed
+ // because they will be treat as repeated expressions
+ // e.g., [[foo,bar],[foo,bar]] -> const foo_bar = _ctx.foo + _ctx.bar
+ if (vars.some(v => v.some(e => first.includes(e)))) {
+ return false
+ }
+
+ return true
+}
+
+function processRepeatedExpressions(
+ context: CodegenContext,
+ expressions: SimpleExpressionNode[],
+ varDeclarations: DeclarationValue[],
+): DeclarationValue[] {
+ const declarations: DeclarationValue[] = []
+ const seenExp = expressions.reduce(
+ (acc, exp) => {
+ // only handle expressions that are not identifiers
+ if (exp.ast && exp.ast.type !== 'Identifier') {
+ acc[exp.content] = (acc[exp.content] || 0) + 1
+ }
+ return acc
+ },
+ Object.create(null) as Record<string, number>,
+ )
+
+ Object.entries(seenExp).forEach(([content, count]) => {
+ if (count > 1) {
+ // foo + baz -> foo_baz
+ const varName = genVarName(content)
+ if (!declarations.some(d => d.name === varName)) {
+ // if foo and baz have no other references, we don't need to declare separate variables
+ // instead of:
+ // const foo = _ctx.foo
+ // const baz = _ctx.baz
+ // const foo_baz = foo + baz
+ // we can generate:
+ // const foo_baz = _ctx.foo + _ctx.baz
+ const delVars: Record<string, string> = {}
+ for (let i = varDeclarations.length - 1; i >= 0; i--) {
+ const item = varDeclarations[i]
+ if (!item.exps || !item.seenCount) continue
+
+ const shouldRemove = [...item.exps].every(
+ node => node.content === content && item.seenCount === count,
+ )
+ if (shouldRemove) {
+ delVars[item.name] = item.rawName!
+ varDeclarations.splice(i, 1)
+ }
+ }
+ const value = extend(
+ {},
+ expressions.find(exp => exp.content === content)!,
+ )
+ Object.keys(delVars).forEach(name => {
+ value.content = value.content.replace(name, delVars[name])
+ if (value.ast) value.ast = parseExp(context, value.content)
+ })
+ declarations.push({
+ name: varName,
+ value: value,
+ })
+ }
+
+ // assume content equals to `foo + baz`
+ expressions.forEach(exp => {
+ // foo + baz -> foo_baz
+ if (exp.content === content) {
+ exp.content = varName
+ // ast is no longer needed since it becomes an identifier.
+ exp.ast = null
+ }
+ // foo + foo + baz -> foo + foo_baz
+ else if (exp.content.includes(content)) {
+ exp.content = exp.content.replace(
+ new RegExp(escapeRegExp(content), 'g'),
+ varName,
+ )
+ // re-parse the expression
+ exp.ast = parseExp(context, exp.content)
+ }
+ })
+ }
+ })
+
+ return declarations
+}
+
+function genDeclarations(
+ declarations: DeclarationValue[],
+ context: CodegenContext,
+): DeclarationResult {
+ const [frag, push] = buildCodeFragment()
+ const ids: Record<string, string> = Object.create(null)
+
+ // process identifiers first as expressions may rely on them
+ declarations.forEach(({ name, isIdentifier, value }) => {
+ if (isIdentifier) {
+ const varName = (ids[name] = `_${name}`)
+ push(`const ${varName} = `, ...genExpression(value, context), NEWLINE)
+ }
+ })
+
+ // process expressions
+ declarations.forEach(({ name, isIdentifier, value }) => {
+ if (!isIdentifier) {
+ const varName = (ids[name] = `_${name}`)
+ push(
+ `const ${varName} = `,
+ ...context.withId(() => genExpression(value, context), ids),
+ NEWLINE,
+ )
+ }
+ })
+
+ return { ids, frag }
+}
+
+function escapeRegExp(string: string) {
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
+}
+
+function parseExp(context: CodegenContext, content: string): Node {
+ const plugins = context.options.expressionPlugins
+ const options: ParserOptions = {
+ plugins: plugins ? [...plugins, 'typescript'] : ['typescript'],
+ }
+ return parseExpression(`(${content})`, options)
+}
+
+function genVarName(exp: string): string {
+ return `${exp
+ .replace(/[^a-zA-Z0-9]/g, '_')
+ .replace(/_+/g, '_')
+ .replace(/_+$/, '')}`
+}
+
+function extractMemberExpression(
+ exp: Node,
+ onIdentifier: (name: string) => void,
+): string {
+ if (!exp) return ''
+ switch (exp.type) {
+ case 'Identifier': // foo[bar]
+ onIdentifier(exp.name)
+ return exp.name
+ case 'StringLiteral': // foo['bar']
+ return exp.extra ? (exp.extra.raw as string) : exp.value
+ case 'NumericLiteral': // foo[0]
+ return exp.value.toString()
+ case 'BinaryExpression': // foo[bar + 1]
+ return `${extractMemberExpression(exp.left, onIdentifier)} ${exp.operator} ${extractMemberExpression(exp.right, onIdentifier)}`
+ case 'CallExpression': // foo[bar(baz)]
+ return `${extractMemberExpression(exp.callee, onIdentifier)}(${exp.arguments.map(arg => extractMemberExpression(arg, onIdentifier)).join(', ')})`
+ case 'MemberExpression': // foo[bar.baz]
+ const object = extractMemberExpression(exp.object, onIdentifier)
+ const prop = exp.computed
+ ? `[${extractMemberExpression(exp.property, onIdentifier)}]`
+ : `.${extractMemberExpression(exp.property, NOOP)}`
+ return `${object}${prop}`
+ default:
+ return ''
+ }
+}
} from './utils'
import { genCreateComponent } from './component'
import { genSlotOutlet } from './slotOutlet'
+import { processExpressions } from './expression'
export function genOperations(
opers: OperationNode[],
effects: IREffect[],
context: CodegenContext,
): CodeFragment[] {
- const { helper } = context
+ const {
+ helper,
+ block: { expressions },
+ } = context
const [frag, push, unshift] = buildCodeFragment()
let operationsCount = 0
+ const { ids, frag: declarationFrags } = processExpressions(
+ context,
+ expressions,
+ )
+ push(...declarationFrags)
for (let i = 0; i < effects.length; i++) {
const effect = effects[i]
operationsCount += effect.operations.length
- const frags = genEffect(effect, context)
+ const frags = context.withId(() => genEffect(effect, context), ids)
i > 0 && push(NEWLINE)
if (frag[frag.length - 1] === ')' && frags[0] === '(') {
push(';')
}
const newLineCount = frag.filter(frag => frag === NEWLINE).length
- if (newLineCount > 1 || operationsCount > 1) {
+ if (newLineCount > 1 || operationsCount > 1 || declarationFrags.length > 0) {
unshift(`{`, INDENT_START, NEWLINE)
push(INDENT_END, NEWLINE, '}')
}
dynamic: IRDynamicInfo
effect: IREffect[]
operation: OperationNode[]
+ expressions: SimpleExpressionNode[]
returns: number[]
}
) {
return this.registerOperation(...operations)
}
+
+ this.block.expressions.push(...expressions)
const existing = this.block.effect.find(e =>
isSameExpression(e.expressions, expressions),
)
effect: [],
operation: [],
returns: [],
+ expressions: [],
})
export function wrapTemplate(node: ElementNode, dirs: string[]): TemplateNode {