From: 三咲智子 Kevin Deng Date: Fri, 1 Dec 2023 15:30:21 +0000 (+0800) Subject: refactor: process expression X-Git-Tag: v3.6.0-alpha.1~16^2~770 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=2f029b659ca08236d18b1715b4ba86e80fac6580;p=thirdparty%2Fvuejs%2Fcore.git refactor: process expression --- diff --git a/packages/compiler-vapor/__tests__/__snapshots__/compile.test.ts.snap b/packages/compiler-vapor/__tests__/__snapshots__/compile.test.ts.snap index 4babbb9fd0..1074169fac 100644 --- a/packages/compiler-vapor/__tests__/__snapshots__/compile.test.ts.snap +++ b/packages/compiler-vapor/__tests__/__snapshots__/compile.test.ts.snap @@ -1,7 +1,8 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`compile > bindings 1`] = ` -"export function render(_ctx) { +" +export function render(_ctx) { const t0 = _template(\\"
count is .
\\") const n0 = t0() const { 0: [n3, { 1: [n2],}],} = _children(n0) @@ -18,7 +19,8 @@ import { template as _template, children as _children, createTextNode as _create `; exports[`compile > directives > v-bind > simple expression 1`] = ` -"export function render(_ctx) { +" +export function render(_ctx) { const t0 = _template(\\"
\\") const n0 = t0() const { 0: [n1],} = _children(n0) @@ -33,22 +35,22 @@ import { template as _template, children as _children, effect as _effect, setAtt `; exports[`compile > directives > v-html > no expression 1`] = ` -"export function render(_ctx) { +" +export function render(_ctx) { const t0 = _template(\\"
\\") const n0 = t0() const { 0: [n1],} = _children(n0) - _effect(() => { - _setHtml(n1, undefined, \\"\\") - }) + _setHtml(n1, undefined, \\"\\") return n0 } -import { template as _template, children as _children, effect as _effect, setHtml as _setHtml } from 'vue/vapor' +import { template as _template, children as _children, setHtml as _setHtml } from 'vue/vapor' " `; exports[`compile > directives > v-html > simple expression 1`] = ` -"export function render(_ctx) { +" +export function render(_ctx) { const t0 = _template(\\"
\\") const n0 = t0() const { 0: [n1],} = _children(n0) @@ -63,7 +65,8 @@ import { template as _template, children as _children, effect as _effect, setHtm `; exports[`compile > directives > v-on > event modifier 1`] = ` -"export function render(_ctx) { +" +export function render(_ctx) { const t0 = _template(\\"
\\") const n0 = t0() const { 0: [n1],} = _children(n0) @@ -78,7 +81,8 @@ import { template as _template, children as _children, effect as _effect, withMo `; exports[`compile > directives > v-on > simple expression 1`] = ` -"export function render(_ctx) { +" +export function render(_ctx) { const t0 = _template(\\"
\\") const n0 = t0() const { 0: [n1],} = _children(n0) @@ -93,7 +97,8 @@ import { template as _template, children as _children, effect as _effect, on as `; exports[`compile > directives > v-once > as root node 1`] = ` -"export function render(_ctx) { +" +export function render(_ctx) { const t0 = _template(\\"
\\") const n0 = t0() const { 0: [n1],} = _children(n0) @@ -106,7 +111,8 @@ import { template as _template, children as _children, setAttr as _setAttr } fro `; exports[`compile > directives > v-once > basic 1`] = ` -"export function render(_ctx) { +" +export function render(_ctx) { const t0 = _template(\\"
\\") const n0 = t0() const { 0: [n3, { 1: [n2],}],} = _children(n0) @@ -122,7 +128,8 @@ import { template as _template, children as _children, createTextNode as _create `; exports[`compile > directives > v-pre > basic 1`] = ` -"export function render(_ctx) { +" +export function render(_ctx) { const t0 = _template(\\"
{{ bar }}
\\") const n0 = t0() return n0 @@ -133,7 +140,8 @@ import { template as _template } from 'vue/vapor' `; exports[`compile > directives > v-pre > self-closing v-pre 1`] = ` -"export function render(_ctx) { +" +export function render(_ctx) { const t0 = _template(\\"
\\") const n0 = t0() const { 1: [n1],} = _children(n0) @@ -153,7 +161,8 @@ import { template as _template, children as _children, createTextNode as _create `; exports[`compile > directives > v-pre > should not affect siblings after it 1`] = ` -"export function render(_ctx) { +" +export function render(_ctx) { const t0 = _template(\\"
{{ bar }}
\\") const n0 = t0() const { 1: [n1],} = _children(n0) @@ -173,22 +182,22 @@ import { template as _template, children as _children, createTextNode as _create `; exports[`compile > directives > v-text > no expression 1`] = ` -"export function render(_ctx) { +" +export function render(_ctx) { const t0 = _template(\\"
\\") const n0 = t0() const { 0: [n1],} = _children(n0) - _effect(() => { - _setText(n1, undefined, \\"\\") - }) + _setText(n1, undefined, \\"\\") return n0 } -import { template as _template, children as _children, effect as _effect, setText as _setText } from 'vue/vapor' +import { template as _template, children as _children, setText as _setText } from 'vue/vapor' " `; exports[`compile > directives > v-text > simple expression 1`] = ` -"export function render(_ctx) { +" +export function render(_ctx) { const t0 = _template(\\"
\\") const n0 = t0() const { 0: [n1],} = _children(n0) @@ -203,7 +212,8 @@ import { template as _template, children as _children, effect as _effect, setTex `; exports[`compile > dynamic root 1`] = ` -"export function render(_ctx) { +" +export function render(_ctx) { const t0 = _fragment() const n0 = t0() @@ -224,7 +234,8 @@ import { fragment as _fragment, createTextNode as _createTextNode, append as _ap `; exports[`compile > dynamic root nodes and interpolation 1`] = ` -"export function render(_ctx) { +" +export function render(_ctx) { const t0 = _template(\\"\\") const n0 = t0() const { 0: [n1, { 1: [n5],}],} = _children(n0) @@ -239,8 +250,14 @@ exports[`compile > dynamic root nodes and interpolation 1`] = ` }) _effect(() => { _setAttr(n1, \\"id\\", undefined, count) + }) + _effect(() => { _setText(n2, undefined, count) + }) + _effect(() => { _setText(n3, undefined, count) + }) + _effect(() => { _setText(n4, undefined, count) }) return n0 @@ -251,7 +268,8 @@ import { template as _template, children as _children, createTextNode as _create `; exports[`compile > fragment 1`] = ` -"export function render(_ctx) { +" +export function render(_ctx) { const t0 = _template(\\"

\\") const n0 = t0() return n0 @@ -262,7 +280,8 @@ import { template as _template } from 'vue/vapor' `; exports[`compile > static + dynamic root 1`] = ` -"export function render(_ctx) { +" +export function render(_ctx) { const t0 = _template(\\"369\\") const n0 = t0() const { 1: [n9], 3: [n10],} = _children(n0) @@ -310,7 +329,8 @@ import { template as _template, children as _children, createTextNode as _create `; exports[`compile > static template 1`] = ` -"export function render(_ctx) { +" +export function render(_ctx) { const t0 = _template(\\"

hello

\\") const n0 = t0() return n0 diff --git a/packages/compiler-vapor/src/generate.ts b/packages/compiler-vapor/src/generate.ts index 69bef1b0c5..3b8b45d04f 100644 --- a/packages/compiler-vapor/src/generate.ts +++ b/packages/compiler-vapor/src/generate.ts @@ -5,23 +5,26 @@ import { NewlineType, advancePositionWithMutation, locStub, + NodeTypes, + BindingTypes, } from '@vue/compiler-dom' import { - type DynamicChildren, + type IRDynamicChildren, type RootIRNode, IRNodeTypes, OperationNode, VaporHelper, - IRNode, + BaseIRNode, + IRExpression, } from './ir' import { SourceMapGenerator } from 'source-map-js' +import { isString } from '@vue/shared' // remove when stable // @ts-expect-error function checkNever(x: never): never {} -export interface CodegenContext - extends Omit, 'bindingMetadata' | 'inline'> { +export interface CodegenContext extends Required { source: string code: string line: number @@ -30,8 +33,8 @@ export interface CodegenContext indentLevel: number map?: SourceMapGenerator - push(code: string, newlineIndex?: number, node?: IRNode): void - pushWithNewline(code: string, newlineIndex?: number, node?: IRNode): void + push(code: string, newlineIndex?: number, node?: BaseIRNode): void + pushWithNewline(code: string, newlineIndex?: number, node?: BaseIRNode): void indent(): void deindent(): void newline(): void @@ -57,6 +60,8 @@ function createCodegenContext( ssr = false, isTS = false, inSSR = false, + inline = false, + bindingMetadata = {}, }: CodegenOptions, ) { const { helpers, vaporHelpers } = ir @@ -73,6 +78,8 @@ function createCodegenContext( ssr, isTS, inSSR, + bindingMetadata, + inline, source: ir.source, code: ``, @@ -205,7 +212,7 @@ export function generate( if (isSetupInlined) { push(`(() => {`) } else { - push(`export function ${functionName}(_ctx) {`) + pushWithNewline(`export function ${functionName}(_ctx) {`) } indent() @@ -239,7 +246,7 @@ export function generate( for (const operation of ir.operation) { genOperation(operation, ctx) } - for (const [_expr, operations] of Object.entries(ir.effect)) { + for (const { operations } of ir.effect) { pushWithNewline(`${vaporHelper('effect')}(() => {`) indent() for (const operation of operations) { @@ -279,6 +286,8 @@ export function generate( NewlineType.End, ) + console.log(ctx.code) + return { code: ctx.code, ast: ir as any, @@ -287,24 +296,25 @@ export function generate( } } -function genOperation( - oper: OperationNode, - { vaporHelper, pushWithNewline }: CodegenContext, -) { +function genOperation(oper: OperationNode, context: CodegenContext) { + const { vaporHelper, pushWithNewline } = context // TODO: cache old value switch (oper.type) { case IRNodeTypes.SET_PROP: { pushWithNewline( - `${vaporHelper('setAttr')}(n${oper.element}, ${JSON.stringify( + `${vaporHelper('setAttr')}(n${oper.element}, ${processExpression( oper.name, - )}, undefined, ${oper.value})`, + context, + )}, undefined, ${processExpression(oper.value, context)})`, ) return } case IRNodeTypes.SET_TEXT: { pushWithNewline( - `${vaporHelper('setText')}(n${oper.element}, undefined, ${oper.value})`, + `${vaporHelper('setText')}(n${ + oper.element + }, undefined, ${processExpression(oper.value, context)})`, ) return } @@ -312,28 +322,34 @@ function genOperation( case IRNodeTypes.SET_EVENT: { let value = oper.value if (oper.modifiers.length) { - value = `${vaporHelper('withModifiers')}(${value}, ${genArrayExpression( - oper.modifiers, - )})` + value = `${vaporHelper('withModifiers')}(${processExpression( + value, + context, + )}, ${genArrayExpression(oper.modifiers)})` } pushWithNewline( - `${vaporHelper('on')}(n${oper.element}, ${JSON.stringify( + `${vaporHelper('on')}(n${oper.element}, ${processExpression( oper.name, - )}, ${value})`, + context, + )}, ${processExpression(value, context)})`, ) return } case IRNodeTypes.SET_HTML: { pushWithNewline( - `${vaporHelper('setHtml')}(n${oper.element}, undefined, ${oper.value})`, + `${vaporHelper('setHtml')}(n${ + oper.element + }, undefined, ${processExpression(oper.value, context)})`, ) return } case IRNodeTypes.CREATE_TEXT_NODE: { pushWithNewline( - `const n${oper.id} = ${vaporHelper('createTextNode')}(${oper.value})`, + `const n${oper.id} = ${vaporHelper( + 'createTextNode', + )}(${processExpression(oper.value, context)})`, ) return } @@ -370,7 +386,7 @@ function genOperation( } } -function genChildren(children: DynamicChildren) { +function genChildren(children: IRDynamicChildren) { let code = '' // TODO let offset = 0 @@ -400,3 +416,35 @@ function genChildren(children: DynamicChildren) { function genArrayExpression(elements: string[]) { return `[${elements.map((it) => `"${it}"`).join(', ')}]` } + +function processExpression( + exp: IRExpression, + { inline, prefixIdentifiers, bindingMetadata, vaporHelper }: CodegenContext, +) { + if (isString(exp)) return exp + + let content = '' + if (exp.type === NodeTypes.SIMPLE_EXPRESSION) { + content = exp.content + if (exp.isStatic) { + return JSON.stringify(content) + } + } else { + // TODO NodeTypes.COMPOUND_EXPRESSION + } + + switch (bindingMetadata[content]) { + case BindingTypes.SETUP_REF: + content += '.value' + break + case BindingTypes.SETUP_MAYBE_REF: + content = `${vaporHelper('unref')}(${content})` + break + } + + if (prefixIdentifiers && !inline) { + content = `_ctx.${content}` + } + + return content +} diff --git a/packages/compiler-vapor/src/ir.ts b/packages/compiler-vapor/src/ir.ts index de518c10c3..5bf801a159 100644 --- a/packages/compiler-vapor/src/ir.ts +++ b/packages/compiler-vapor/src/ir.ts @@ -1,4 +1,8 @@ -import type { SourceLocation } from '@vue/compiler-dom' +import type { + ExpressionNode, + RootNode, + SourceLocation, +} from '@vue/compiler-dom' export enum IRNodeTypes { ROOT, @@ -16,7 +20,7 @@ export enum IRNodeTypes { CREATE_TEXT_NODE, } -export interface IRNode { +export interface BaseIRNode { type: IRNodeTypes loc: SourceLocation } @@ -24,79 +28,84 @@ export interface IRNode { // TODO refactor export type VaporHelper = keyof typeof import('../../runtime-vapor/src') -export interface RootIRNode extends IRNode { +export interface RootIRNode extends BaseIRNode { type: IRNodeTypes.ROOT source: string + node: RootNode template: Array - dynamic: DynamicInfo - // TODO multi-expression effect - effect: Record + dynamic: IRDynamicInfo + effect: IREffect[] operation: OperationNode[] helpers: Set vaporHelpers: Set } -export interface TemplateFactoryIRNode extends IRNode { +export interface TemplateFactoryIRNode extends BaseIRNode { type: IRNodeTypes.TEMPLATE_FACTORY template: string } -export interface FragmentFactoryIRNode extends IRNode { +export interface FragmentFactoryIRNode extends BaseIRNode { type: IRNodeTypes.FRAGMENT_FACTORY } -export interface SetPropIRNode extends IRNode { +export interface SetPropIRNode extends BaseIRNode { type: IRNodeTypes.SET_PROP element: number - name: string - value: string + name: IRExpression + value: IRExpression } -export interface SetTextIRNode extends IRNode { +export interface SetTextIRNode extends BaseIRNode { type: IRNodeTypes.SET_TEXT element: number - value: string + value: IRExpression } -export interface SetEventIRNode extends IRNode { +export interface SetEventIRNode extends BaseIRNode { type: IRNodeTypes.SET_EVENT element: number - name: string - value: string + name: IRExpression + value: IRExpression modifiers: string[] } -export interface SetHtmlIRNode extends IRNode { +export interface SetHtmlIRNode extends BaseIRNode { type: IRNodeTypes.SET_HTML element: number - value: string + value: IRExpression } -export interface CreateTextNodeIRNode extends IRNode { +export interface CreateTextNodeIRNode extends BaseIRNode { type: IRNodeTypes.CREATE_TEXT_NODE id: number - value: string + value: IRExpression } -export interface InsertNodeIRNode extends IRNode { +export interface InsertNodeIRNode extends BaseIRNode { type: IRNodeTypes.INSERT_NODE element: number | number[] parent: number anchor: number } -export interface PrependNodeIRNode extends IRNode { +export interface PrependNodeIRNode extends BaseIRNode { type: IRNodeTypes.PREPEND_NODE elements: number[] parent: number } -export interface AppendNodeIRNode extends IRNode { +export interface AppendNodeIRNode extends BaseIRNode { type: IRNodeTypes.APPEND_NODE elements: number[] parent: number } +export type IRNode = + | OperationNode + | RootIRNode + | TemplateFactoryIRNode + | FragmentFactoryIRNode export type OperationNode = | SetPropIRNode | SetTextIRNode @@ -107,12 +116,19 @@ export type OperationNode = | PrependNodeIRNode | AppendNodeIRNode -export interface DynamicInfo { +export interface IRDynamicInfo { id: number | null referenced: boolean /** created by DOM API */ ghost: boolean placeholder: number | null - children: DynamicChildren + children: IRDynamicChildren +} +export type IRDynamicChildren = Record + +export type IRExpression = ExpressionNode | string +export interface IREffect { + // TODO multi-expression effect + expressions: IRExpression[] + operations: OperationNode[] } -export type DynamicChildren = Record diff --git a/packages/compiler-vapor/src/transform.ts b/packages/compiler-vapor/src/transform.ts index df7e73d6d9..7b9c5e206c 100644 --- a/packages/compiler-vapor/src/transform.ts +++ b/packages/compiler-vapor/src/transform.ts @@ -6,12 +6,10 @@ import { type InterpolationNode, type TransformOptions as BaseTransformOptions, type DirectiveNode, - type ExpressionNode, type ParentNode, type AllNode, type CompilerCompatOptions, NodeTypes, - BindingTypes, defaultOnError, defaultOnWarn, ErrorCodes, @@ -21,8 +19,9 @@ import { EMPTY_OBJ, NOOP, isArray, isVoidTag } from '@vue/shared' import { type OperationNode, type RootIRNode, + type IRDynamicInfo, + type IRExpression, IRNodeTypes, - DynamicInfo, } from './ir' import type { HackOptions } from './hack' @@ -43,14 +42,17 @@ export interface TransformContext { > template: string - dynamic: DynamicInfo + dynamic: IRDynamicInfo inVOnce: boolean reference(): number increaseId(): number registerTemplate(): number - registerEffect(expr: string, operation: OperationNode): void + registerEffect( + expressions: Array, + operation: OperationNode[], + ): void registerOperation(...operations: OperationNode[]): void helper(name: string): string } @@ -102,12 +104,18 @@ function createRootContext( this.dynamic.referenced = true return (this.dynamic.id = this.increaseId()) }, - registerEffect(expr, operation) { - if (this.inVOnce) { - return this.registerOperation(operation) + registerEffect(expressions, operations) { + if ( + this.inVOnce || + (expressions = expressions.filter(Boolean)).length === 0 + ) { + return this.registerOperation(...operations) } - if (!effect[expr]) effect[expr] = [] - effect[expr].push(operation) + // TODO combine effects + effect.push({ + expressions: expressions as IRExpression[], + operations, + }) }, template: '', @@ -175,6 +183,7 @@ export function transform( const ir: RootIRNode = { type: IRNodeTypes.ROOT, + node: root, source: root.source, loc: root.loc, template: [], @@ -185,7 +194,7 @@ export function transform( placeholder: null, children: {}, }, - effect: Object.create(null), + effect: [], operation: [], helpers: new Set([]), vaporHelpers: new Set([]), @@ -309,7 +318,7 @@ function transformChildren(ctx: TransformContext) { if (ctx.node.type === NodeTypes.ROOT) ctx.registerTemplate() function processDynamicChildren() { - let prevChildren: DynamicInfo[] = [] + let prevChildren: IRDynamicInfo[] = [] let hasStatic = false for (let index = 0; index < children.length; index++) { const child = ctx.dynamic.children[index] @@ -378,22 +387,22 @@ function transformInterpolation( ) { const { node } = ctx - if (node.content.type === NodeTypes.COMPOUND_EXPRESSION) { - // TODO: CompoundExpressionNode: {{ count + 1 }} - return - } - - const expr = processExpression(ctx, node.content)! + const expr = node.content if (isFirst && isLast) { const parent = ctx.parent! const parentId = parent.reference() - ctx.registerEffect(expr, { - type: IRNodeTypes.SET_TEXT, - loc: node.loc, - element: parentId, - value: expr, - }) + ctx.registerEffect( + [expr], + [ + { + type: IRNodeTypes.SET_TEXT, + loc: node.loc, + element: parentId, + value: expr, + }, + ], + ) } else { const id = ctx.reference() ctx.dynamic.ghost = true @@ -403,12 +412,17 @@ function transformInterpolation( id, value: expr, }) - ctx.registerEffect(expr, { - type: IRNodeTypes.SET_TEXT, - loc: node.loc, - element: id, - value: expr, - }) + ctx.registerEffect( + [expr], + [ + { + type: IRNodeTypes.SET_TEXT, + loc: node.loc, + element: id, + value: expr, + }, + ], + ) } } @@ -427,9 +441,8 @@ function transformProp( return } - const { exp, loc, modifiers } = node + const { arg, exp, loc, modifiers } = node - const expr = processExpression(ctx, exp) switch (name) { case 'bind': { if ( @@ -442,25 +455,27 @@ function transformProp( return } - if (expr === null) { + if (exp === null) { // TODO: Vue 3.4 supported shorthand syntax // https://github.com/vuejs/core/pull/9451 return - } else if (!node.arg) { + } else if (!arg) { // TODO support v-bind="{}" return - } else if (node.arg.type === NodeTypes.COMPOUND_EXPRESSION) { - // TODO support :[foo]="bar" - return } - ctx.registerEffect(expr, { - type: IRNodeTypes.SET_PROP, - loc: node.loc, - element: ctx.reference(), - name: node.arg.content, - value: expr, - }) + ctx.registerEffect( + [exp], + [ + { + type: IRNodeTypes.SET_PROP, + loc: node.loc, + element: ctx.reference(), + name: arg, + value: exp, + }, + ], + ) break } case 'on': { @@ -471,46 +486,56 @@ function transformProp( return } - if (!node.arg) { + if (!arg) { // TODO support v-on="{}" return - } else if (node.arg.type === NodeTypes.COMPOUND_EXPRESSION) { - // TODO support @[foo]="bar" - return - } else if (expr === null) { + } else if (exp === undefined) { // TODO: support @foo // https://github.com/vuejs/core/pull/9451 return } - ctx.registerEffect(expr, { - type: IRNodeTypes.SET_EVENT, - loc: node.loc, - element: ctx.reference(), - name: node.arg.content, - value: expr, - modifiers, - }) + ctx.registerEffect( + [exp], + [ + { + type: IRNodeTypes.SET_EVENT, + loc: node.loc, + element: ctx.reference(), + name: arg, + value: exp, + modifiers, + }, + ], + ) break } case 'html': { - const value = expr || '""' - ctx.registerEffect(value, { - type: IRNodeTypes.SET_HTML, - loc: node.loc, - element: ctx.reference(), - value, - }) + ctx.registerEffect( + [exp], + [ + { + type: IRNodeTypes.SET_HTML, + loc: node.loc, + element: ctx.reference(), + value: exp || '""', + }, + ], + ) break } case 'text': { - const value = expr || '""' - ctx.registerEffect(value, { - type: IRNodeTypes.SET_TEXT, - loc: node.loc, - element: ctx.reference(), - value, - }) + ctx.registerEffect( + [exp], + [ + { + type: IRNodeTypes.SET_TEXT, + loc: node.loc, + element: ctx.reference(), + value: exp || '""', + }, + ], + ) break } case 'cloak': { @@ -519,24 +544,3 @@ function transformProp( } } } - -// TODO: reuse packages/compiler-core/src/transforms/transformExpression.ts -function processExpression( - ctx: TransformContext, - expr: ExpressionNode | undefined, -): string | null { - if (!expr) return null - if (expr.type === NodeTypes.COMPOUND_EXPRESSION) { - // TODO - return '' - } - - let { content } = expr - if (ctx.options.bindingMetadata?.[content] === BindingTypes.SETUP_REF) { - content += '.value' - } - if (ctx.options.prefixIdentifiers && !ctx.options.inline) { - content = `_ctx.${content}` - } - return content -}