From: 三咲智子 Kevin Deng Date: Sat, 2 Dec 2023 08:59:43 +0000 (+0800) Subject: refactor: transformElement X-Git-Tag: v3.6.0-alpha.1~16^2~764 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=03344eea7eac60e08487ae61670743448815f8d9;p=thirdparty%2Fvuejs%2Fcore.git refactor: transformElement --- diff --git a/packages/compiler-vapor/__tests__/__snapshots__/compile.test.ts.snap b/packages/compiler-vapor/__tests__/__snapshots__/compile.test.ts.snap index 515fc539b9..232189808e 100644 --- a/packages/compiler-vapor/__tests__/__snapshots__/compile.test.ts.snap +++ b/packages/compiler-vapor/__tests__/__snapshots__/compile.test.ts.snap @@ -136,38 +136,38 @@ export function render(_ctx) { `; exports[`compile > directives > v-pre > self-closing v-pre 1`] = ` -"import { template as _template, children as _children, createTextNode as _createTextNode, append as _append, effect as _effect, setAttr as _setAttr, setText as _setText } from 'vue/vapor'; +"import { template as _template, children as _children, createTextNode as _createTextNode, append as _append, effect as _effect, setText as _setText, setAttr as _setAttr } from 'vue/vapor'; export function render(_ctx) { const t0 = _template(\\"
\\") const n0 = t0() - const { 1: [n1],} = _children(n0) - const n2 = _createTextNode(bar) - _append(n1, n2) + const { 1: [n2],} = _children(n0) + const n1 = _createTextNode(bar) + _append(n2, n1) _effect(() => { - _setAttr(n1, \\"id\\", undefined, foo) + _setText(n1, undefined, bar) }) _effect(() => { - _setText(n2, undefined, bar) + _setAttr(n2, \\"id\\", undefined, foo) }) return n0 }" `; exports[`compile > directives > v-pre > should not affect siblings after it 1`] = ` -"import { template as _template, children as _children, createTextNode as _createTextNode, append as _append, effect as _effect, setAttr as _setAttr, setText as _setText } from 'vue/vapor'; +"import { template as _template, children as _children, createTextNode as _createTextNode, append as _append, effect as _effect, setText as _setText, setAttr as _setAttr } from 'vue/vapor'; export function render(_ctx) { const t0 = _template(\\"
{{ bar }}
\\") const n0 = t0() - const { 1: [n1],} = _children(n0) - const n2 = _createTextNode(bar.value) - _append(n1, n2) + const { 1: [n2],} = _children(n0) + const n1 = _createTextNode(bar.value) + _append(n2, n1) _effect(() => { - _setAttr(n1, \\"id\\", undefined, foo.value) + _setText(n1, undefined, bar.value) }) _effect(() => { - _setText(n2, undefined, bar.value) + _setAttr(n2, \\"id\\", undefined, foo.value) }) return n0 }" @@ -220,23 +220,20 @@ export function render(_ctx) { `; exports[`compile > dynamic root nodes and interpolation 1`] = ` -"import { template as _template, children as _children, createTextNode as _createTextNode, prepend as _prepend, insert as _insert, append as _append, effect as _effect, on as _on, setAttr as _setAttr, setText as _setText } from 'vue/vapor'; +"import { template as _template, children as _children, createTextNode as _createTextNode, prepend as _prepend, insert as _insert, append as _append, effect as _effect, setText as _setText, on as _on, setAttr as _setAttr } from 'vue/vapor'; export function render(_ctx) { const t0 = _template(\\"\\") const n0 = t0() - const { 0: [n1, { 1: [n5],}],} = _children(n0) + const { 0: [n4, { 1: [n5],}],} = _children(n0) + const n1 = _createTextNode(count) const n2 = _createTextNode(count) const n3 = _createTextNode(count) - const n4 = _createTextNode(count) - _prepend(n1, n2) - _insert(n3, n1, n5) - _append(n1, n4) + _prepend(n4, n1) + _insert(n2, n4, n5) + _append(n4, n3) _effect(() => { - _on(n1, \\"click\\", handleClick) - }) - _effect(() => { - _setAttr(n1, \\"id\\", undefined, count) + _setText(n1, undefined, count) }) _effect(() => { _setText(n2, undefined, count) @@ -245,7 +242,10 @@ export function render(_ctx) { _setText(n3, undefined, count) }) _effect(() => { - _setText(n4, undefined, count) + _on(n4, \\"click\\", handleClick) + }) + _effect(() => { + _setAttr(n4, \\"id\\", undefined, count) }) return n0 }" diff --git a/packages/compiler-vapor/src/compile.ts b/packages/compiler-vapor/src/compile.ts index 1892d4974c..f9a922b331 100644 --- a/packages/compiler-vapor/src/compile.ts +++ b/packages/compiler-vapor/src/compile.ts @@ -11,8 +11,9 @@ import { import { extend, isString } from '@vue/shared' import { NodeTransform, transform } from './transform' import { generate } from './generate' -import { transformOnce } from './transforms/vOnce' import { HackOptions } from './hack' +import { transformOnce } from './transforms/vOnce' +import { transformElement } from './transforms/transformElement' export type CompilerOptions = HackOptions @@ -84,5 +85,5 @@ export type TransformPreset = [ export function getBaseTransformPreset( prefixIdentifiers?: boolean, ): TransformPreset { - return [[transformOnce], {}] + return [[transformOnce, transformElement], {}] } diff --git a/packages/compiler-vapor/src/transform.ts b/packages/compiler-vapor/src/transform.ts index 9dfe6a15c4..bfb3d732f7 100644 --- a/packages/compiler-vapor/src/transform.ts +++ b/packages/compiler-vapor/src/transform.ts @@ -2,22 +2,16 @@ import { type RootNode, type TemplateChildNode, type ElementNode, - type AttributeNode, type InterpolationNode, type TransformOptions as BaseTransformOptions, - type DirectiveNode, type ParentNode, type AllNode, type CompilerCompatOptions, NodeTypes, defaultOnError, defaultOnWarn, - ErrorCodes, - createCompilerError, - DOMErrorCodes, - createDOMCompilerError, } from '@vue/compiler-dom' -import { EMPTY_OBJ, NOOP, isArray, isVoidTag } from '@vue/shared' +import { EMPTY_OBJ, NOOP, isArray } from '@vue/shared' import { type OperationNode, type RootIRNode, @@ -29,7 +23,7 @@ import type { HackOptions } from './hack' export type NodeTransform = ( node: RootNode | TemplateChildNode, - context: TransformContext, + context: TransformContext, ) => void | (() => void) | (() => void)[] export type TransformOptions = HackOptions @@ -44,6 +38,7 @@ export interface TransformContext { > template: string + childrenTemplate: string[] dynamic: IRDynamicInfo inVOnce: boolean @@ -121,6 +116,7 @@ function createRootContext( }, template: '', + childrenTemplate: [], registerTemplate() { if (!ctx.template) return -1 @@ -164,6 +160,7 @@ function createContext( index, template: '', + childrenTemplate: [], dynamic: { id: null, referenced: false, @@ -200,9 +197,11 @@ export function transform( } const ctx = createRootContext(ir, root, options) - - // TODO: transform presets, see packages/compiler-core/src/transforms transformNode(ctx) + + if (ctx.node.type === NodeTypes.ROOT) { + ctx.registerTemplate() + } if (ir.template.length === 0) { ir.template.push({ type: IRNodeTypes.FRAGMENT_FACTORY, @@ -222,8 +221,7 @@ function transformNode( const { nodeTransforms } = context.options const exitFns = [] for (const nodeTransform of nodeTransforms) { - // TODO nodeTransform type - const onExit = nodeTransform(node, context as any) + const onExit = nodeTransform(node, context) if (onExit) { if (isArray(onExit)) { exitFns.push(...onExit) @@ -240,18 +238,14 @@ function transformNode( } } - if (node.type === NodeTypes.ROOT) { - transformChildren(context as TransformContext) - return - } - - const parentChildren = context.parent!.node.children + const parentChildren = context.parent ? context.parent.node.children : [] const isFirst = index === 0 const isLast = index === parentChildren.length - 1 switch (node.type) { + case NodeTypes.ROOT: case NodeTypes.ELEMENT: { - transformElement(context as TransformContext) + transformChildren(context as TransformContext) break } case NodeTypes.TEXT: { @@ -289,93 +283,81 @@ function transformNode( while (i--) { exitFns[i]() } + + if (context.node.type === NodeTypes.ROOT) + context.template += context.childrenTemplate.join('') } function transformChildren(ctx: TransformContext) { - const { - node: { children }, - } = ctx - const childrenTemplate: string[] = [] - children.forEach((child, index) => { - const childContext = createContext(child, ctx, index) + const { children } = ctx.node + let i = 0 + // const nodeRemoved = () => { + // i-- + // } + for (; i < children.length; i++) { + const child = children[i] + const childContext = createContext(child, ctx, i) transformNode(childContext) - - childrenTemplate.push(childContext.template) + ctx.childrenTemplate.push(childContext.template) if ( childContext.dynamic.ghost || childContext.dynamic.referenced || childContext.dynamic.placeholder || Object.keys(childContext.dynamic.children).length ) { - ctx.dynamic.children[index] = childContext.dynamic - } - }) - - processDynamicChildren() - ctx.template += childrenTemplate.join('') - - if (ctx.node.type === NodeTypes.ROOT) ctx.registerTemplate() - - function processDynamicChildren() { - let prevChildren: IRDynamicInfo[] = [] - let hasStatic = false - for (let index = 0; index < children.length; index++) { - const child = ctx.dynamic.children[index] - - if (!child || !child.ghost) { - if (prevChildren.length) - if (hasStatic) { - childrenTemplate[index - prevChildren.length] = `` - const anchor = (prevChildren[0].placeholder = ctx.increaseId()) - - ctx.registerOperation({ - type: IRNodeTypes.INSERT_NODE, - loc: ctx.node.loc, - element: prevChildren.map((child) => child.id!), - parent: ctx.reference(), - anchor, - }) - } else { - ctx.registerOperation({ - type: IRNodeTypes.PREPEND_NODE, - loc: ctx.node.loc, - elements: prevChildren.map((child) => child.id!), - parent: ctx.reference(), - }) - } - hasStatic = true - prevChildren = [] - continue - } - - prevChildren.push(child) - - if (index === children.length - 1) { - ctx.registerOperation({ - type: IRNodeTypes.APPEND_NODE, - loc: ctx.node.loc, - elements: prevChildren.map((child) => child.id!), - parent: ctx.reference(), - }) - } + ctx.dynamic.children[i] = childContext.dynamic } } + + processDynamicChildren(ctx) } -function transformElement(ctx: TransformContext) { +function processDynamicChildren(ctx: TransformContext) { const { node } = ctx - const { tag, props, children } = node - ctx.template += `<${tag}` + let prevChildren: IRDynamicInfo[] = [] + let hasStatic = false + + for (let index = 0; index < node.children.length; index++) { + const child = ctx.dynamic.children[index] + + if (!child || !child.ghost) { + if (prevChildren.length) { + if (hasStatic) { + ctx.childrenTemplate[index - prevChildren.length] = `` + const anchor = (prevChildren[0].placeholder = ctx.increaseId()) - props.forEach((prop) => transformProp(prop, ctx)) - ctx.template += `>` + ctx.registerOperation({ + type: IRNodeTypes.INSERT_NODE, + loc: node.loc, + element: prevChildren.map((child) => child.id!), + parent: ctx.reference(), + anchor, + }) + } else { + ctx.registerOperation({ + type: IRNodeTypes.PREPEND_NODE, + loc: node.loc, + elements: prevChildren.map((child) => child.id!), + parent: ctx.reference(), + }) + } + } + hasStatic = true + prevChildren = [] + continue + } - if (children.length) transformChildren(ctx) + prevChildren.push(child) - // TODO remove unnecessary close tag, e.g. if it's the last element of the template - if (!isVoidTag(tag)) { - ctx.template += `` + if (index === node.children.length - 1) { + ctx.registerOperation({ + type: IRNodeTypes.APPEND_NODE, + loc: node.loc, + elements: prevChildren.map((child) => child.id!), + parent: ctx.reference(), + }) + } } } @@ -424,134 +406,3 @@ function transformInterpolation( ) } } - -function transformProp( - node: DirectiveNode | AttributeNode, - ctx: TransformContext, -): void { - const { name } = node - - if (node.type === NodeTypes.ATTRIBUTE) { - if (node.value) { - ctx.template += ` ${name}="${node.value.content}"` - } else { - ctx.template += ` ${name}` - } - return - } - - const { arg, exp, loc, modifiers } = node - - switch (name) { - case 'bind': { - if ( - !exp || - (exp.type === NodeTypes.SIMPLE_EXPRESSION && !exp.content.trim()) - ) { - ctx.options.onError( - createCompilerError(ErrorCodes.X_V_BIND_NO_EXPRESSION, loc), - ) - return - } - - if (exp === null) { - // TODO: Vue 3.4 supported shorthand syntax - // https://github.com/vuejs/core/pull/9451 - return - } else if (!arg) { - // TODO support v-bind="{}" - return - } - - ctx.registerEffect( - [exp], - [ - { - type: IRNodeTypes.SET_PROP, - loc: node.loc, - element: ctx.reference(), - name: arg, - value: exp, - }, - ], - ) - break - } - case 'on': { - if (!exp && !modifiers.length) { - ctx.options.onError( - createCompilerError(ErrorCodes.X_V_ON_NO_EXPRESSION, loc), - ) - return - } - - if (!arg) { - // TODO support v-on="{}" - return - } else if (exp === undefined) { - // TODO: support @foo - // https://github.com/vuejs/core/pull/9451 - return - } - - ctx.registerEffect( - [exp], - [ - { - type: IRNodeTypes.SET_EVENT, - loc: node.loc, - element: ctx.reference(), - name: arg, - value: exp, - modifiers, - }, - ], - ) - break - } - case 'html': { - if (!exp) { - ctx.options.onError( - createDOMCompilerError(DOMErrorCodes.X_V_HTML_NO_EXPRESSION, loc), - ) - } - if (ctx.node.children.length) { - ctx.options.onError( - createDOMCompilerError(DOMErrorCodes.X_V_HTML_WITH_CHILDREN, loc), - ) - ctx.node.children.length = 0 - } - - ctx.registerEffect( - [exp], - [ - { - type: IRNodeTypes.SET_HTML, - loc: node.loc, - element: ctx.reference(), - value: exp || '""', - }, - ], - ) - break - } - case 'text': { - ctx.registerEffect( - [exp], - [ - { - type: IRNodeTypes.SET_TEXT, - loc: node.loc, - element: ctx.reference(), - value: exp || '""', - }, - ], - ) - break - } - case 'cloak': { - // do nothing - break - } - } -} diff --git a/packages/compiler-vapor/src/transforms/transformElement.ts b/packages/compiler-vapor/src/transforms/transformElement.ts new file mode 100644 index 0000000000..b205bf1770 --- /dev/null +++ b/packages/compiler-vapor/src/transforms/transformElement.ts @@ -0,0 +1,175 @@ +import { + type ElementNode, + type AttributeNode, + type DirectiveNode, + NodeTypes, + ErrorCodes, + createCompilerError, + DOMErrorCodes, + createDOMCompilerError, + ElementTypes, +} from '@vue/compiler-dom' +import { isVoidTag } from '@vue/shared' +import { NodeTransform, TransformContext } from '../transform' +import { IRNodeTypes } from '../ir' + +export const transformElement: NodeTransform = (node, ctx) => { + return function postTransformElement() { + node = ctx.node + + if ( + !( + node.type === NodeTypes.ELEMENT && + (node.tagType === ElementTypes.ELEMENT || + node.tagType === ElementTypes.COMPONENT) + ) + ) { + return + } + + const { tag, props } = node + + ctx.template += `<${tag}` + props.forEach((prop) => + transformProp(prop, ctx as TransformContext), + ) + ctx.template += `>` + ctx.template += ctx.childrenTemplate.join('') + + // TODO remove unnecessary close tag, e.g. if it's the last element of the template + if (!isVoidTag(tag)) { + ctx.template += `` + } + } +} + +function transformProp( + node: DirectiveNode | AttributeNode, + ctx: TransformContext, +): void { + const { name } = node + + if (node.type === NodeTypes.ATTRIBUTE) { + if (node.value) { + ctx.template += ` ${name}="${node.value.content}"` + } else { + ctx.template += ` ${name}` + } + return + } + + const { arg, exp, loc, modifiers } = node + + switch (name) { + case 'bind': { + if ( + !exp || + (exp.type === NodeTypes.SIMPLE_EXPRESSION && !exp.content.trim()) + ) { + ctx.options.onError( + createCompilerError(ErrorCodes.X_V_BIND_NO_EXPRESSION, loc), + ) + return + } + + if (exp === null) { + // TODO: Vue 3.4 supported shorthand syntax + // https://github.com/vuejs/core/pull/9451 + return + } else if (!arg) { + // TODO support v-bind="{}" + return + } + + ctx.registerEffect( + [exp], + [ + { + type: IRNodeTypes.SET_PROP, + loc: node.loc, + element: ctx.reference(), + name: arg, + value: exp, + }, + ], + ) + break + } + case 'on': { + if (!exp && !modifiers.length) { + ctx.options.onError( + createCompilerError(ErrorCodes.X_V_ON_NO_EXPRESSION, loc), + ) + return + } + + if (!arg) { + // TODO support v-on="{}" + return + } else if (exp === undefined) { + // TODO: support @foo + // https://github.com/vuejs/core/pull/9451 + return + } + + ctx.registerEffect( + [exp], + [ + { + type: IRNodeTypes.SET_EVENT, + loc: node.loc, + element: ctx.reference(), + name: arg, + value: exp, + modifiers, + }, + ], + ) + break + } + case 'html': { + if (!exp) { + ctx.options.onError( + createDOMCompilerError(DOMErrorCodes.X_V_HTML_NO_EXPRESSION, loc), + ) + } + if (ctx.node.children.length) { + ctx.options.onError( + createDOMCompilerError(DOMErrorCodes.X_V_HTML_WITH_CHILDREN, loc), + ) + ctx.childrenTemplate.length = 0 + } + + ctx.registerEffect( + [exp], + [ + { + type: IRNodeTypes.SET_HTML, + loc: node.loc, + element: ctx.reference(), + value: exp || '""', + }, + ], + ) + break + } + case 'text': { + ctx.registerEffect( + [exp], + [ + { + type: IRNodeTypes.SET_TEXT, + loc: node.loc, + element: ctx.reference(), + value: exp || '""', + }, + ], + ) + break + } + case 'cloak': { + // do nothing + break + } + } +}