From: Evan You Date: Tue, 11 Feb 2020 23:12:56 +0000 (-0500) Subject: refactor(compiler-core): use dedicated node type for element codegen X-Git-Tag: v3.0.0-alpha.5~52 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=e3988b40d86990a8876288db703cef1ac2d8e078;p=thirdparty%2Fvuejs%2Fcore.git refactor(compiler-core): use dedicated node type for element codegen Previously codegen node for elements and components used raw expressions, which leads to multiple permutations of AST shapes based on whether the node is a block or has directives. The complexity is spread across the entire compiler and occurs whenever a transform needs to deal with element codegen nodes. This refactor centralizes the handling of all possible permutations into the codegen phase, so that all elements/components will have a consistent node type throughout the transform phase. The refactor is split into two commits (with test updates in a separate one) so changes can be easier to inspect. --- diff --git a/packages/compiler-core/src/ast.ts b/packages/compiler-core/src/ast.ts index 0039b6e05e..8655a5dc8d 100644 --- a/packages/compiler-core/src/ast.ts +++ b/packages/compiler-core/src/ast.ts @@ -1,17 +1,17 @@ import { isString } from '@vue/shared' import { ForParseResult } from './transforms/vFor' import { - CREATE_VNODE, - WITH_DIRECTIVES, RENDER_SLOT, CREATE_SLOTS, RENDER_LIST, OPEN_BLOCK, CREATE_BLOCK, - FRAGMENT + FRAGMENT, + CREATE_VNODE, + WITH_DIRECTIVES } from './runtimeHelpers' import { PropsExpression } from './transforms/transformElement' -import { ImportItem } from './transform' +import { ImportItem, TransformContext } from './transform' // Vue template is a platform-agnostic superset of HTML (syntax only). // More namespaces like SVG and MathML are declared by platform specific @@ -38,12 +38,12 @@ export const enum NodeTypes { FOR, TEXT_CALL, // codegen + VNODE_CALL, JS_CALL_EXPRESSION, JS_OBJECT_EXPRESSION, JS_PROPERTY, JS_ARRAY_EXPRESSION, JS_FUNCTION_EXPRESSION, - JS_SEQUENCE_EXPRESSION, JS_CONDITIONAL_EXPRESSION, JS_CACHE_EXPRESSION, @@ -123,21 +123,14 @@ export interface BaseElementNode extends Node { isSelfClosing: boolean props: Array children: TemplateChildNode[] - codegenNode: - | CallExpression - | SimpleExpressionNode - | CacheExpression - | SequenceExpression - | undefined } export interface PlainElementNode extends BaseElementNode { tagType: ElementTypes.ELEMENT codegenNode: - | ElementCodegenNode + | VNodeCall | SimpleExpressionNode // when hoisted | CacheExpression // when cached by v-once - | SequenceExpression // when turned into a block | undefined ssrCodegenNode?: TemplateLiteral } @@ -145,7 +138,7 @@ export interface PlainElementNode extends BaseElementNode { export interface ComponentNode extends BaseElementNode { tagType: ElementTypes.COMPONENT codegenNode: - | ComponentCodegenNode + | VNodeCall | CacheExpression // when cached by v-once | undefined ssrCodegenNode?: CallExpression @@ -153,13 +146,17 @@ export interface ComponentNode extends BaseElementNode { export interface SlotOutletNode extends BaseElementNode { tagType: ElementTypes.SLOT - codegenNode: SlotOutletCodegenNode | undefined | CacheExpression // when cached by v-once + codegenNode: + | RenderSlotCall + | CacheExpression // when cached by v-once + | undefined ssrCodegenNode?: CallExpression } export interface TemplateNode extends BaseElementNode { tagType: ElementTypes.TEMPLATE // TemplateNode is a container type that always gets compiled away + codegenNode: undefined } export interface TextNode extends Node { @@ -220,7 +217,7 @@ export interface CompoundExpressionNode extends Node { export interface IfNode extends Node { type: NodeTypes.IF branches: IfBranchNode[] - codegenNode?: IfCodegenNode + codegenNode?: IfConditionalExpression } export interface IfBranchNode extends Node { @@ -246,6 +243,28 @@ export interface TextCallNode extends Node { codegenNode: CallExpression | SimpleExpressionNode // when hoisted } +export type TemplateTextChildNode = + | TextNode + | InterpolationNode + | CompoundExpressionNode + +export interface VNodeCall extends Node { + type: NodeTypes.VNODE_CALL + tag: string | symbol | CallExpression + props: PropsExpression | undefined + children: + | TemplateChildNode[] // multiple children + | TemplateTextChildNode // single text child + | SlotsExpression // component slots + | ForRenderListExpression // v-for fragment call + | undefined + patchFlag: string | undefined + dynamicProps: string | undefined + directives: DirectiveArguments | undefined + isBlock: boolean + isForBlock: boolean +} + // JS Node Types --------------------------------------------------------------- // We also include a number of JavaScript AST nodes for code generation. @@ -253,13 +272,13 @@ export interface TextCallNode extends Node { // Vue render function generation. export type JSChildNode = + | VNodeCall | CallExpression | ObjectExpression | ArrayExpression | ExpressionNode | FunctionExpression | ConditionalExpression - | SequenceExpression | CacheExpression | AssignmentExpression @@ -301,11 +320,6 @@ export interface FunctionExpression extends Node { isSlot: boolean } -export interface SequenceExpression extends Node { - type: NodeTypes.JS_SEQUENCE_EXPRESSION - expressions: JSChildNode[] -} - export interface ConditionalExpression extends Node { type: NodeTypes.JS_CONDITIONAL_EXPRESSION test: JSChildNode @@ -360,58 +374,32 @@ export interface ReturnStatement extends Node { // Codegen Node Types ---------------------------------------------------------- -// createVNode(...) -export interface PlainElementCodegenNode extends CallExpression { - callee: typeof CREATE_VNODE | typeof CREATE_BLOCK - arguments: // tag, props, children, patchFlag, dynamicProps - | [string | symbol] - | [string | symbol, PropsExpression] - | [string | symbol, 'null' | PropsExpression, TemplateChildNode[]] - | [ - string | symbol, - 'null' | PropsExpression, - 'null' | TemplateChildNode[], - string - ] - | [ - string | symbol, - 'null' | PropsExpression, - 'null' | TemplateChildNode[], - string, - string - ] +export interface DirectiveArguments extends ArrayExpression { + elements: DirectiveArgumentNode[] } -export type ElementCodegenNode = - | PlainElementCodegenNode - | CodegenNodeWithDirective +export interface DirectiveArgumentNode extends ArrayExpression { + elements: // dir, exp, arg, modifiers + | [string] + | [string, ExpressionNode] + | [string, ExpressionNode, ExpressionNode] + | [string, ExpressionNode, ExpressionNode, ObjectExpression] +} -// createVNode(...) -export interface PlainComponentCodegenNode extends CallExpression { - callee: typeof CREATE_VNODE | typeof CREATE_BLOCK - arguments: // Comp, props, slots, patchFlag, dynamicProps - | [string | symbol] - | [string | symbol, PropsExpression] - | [string | symbol, 'null' | PropsExpression, SlotsExpression] - | [ - string | symbol, - 'null' | PropsExpression, - 'null' | SlotsExpression, - string - ] +// renderSlot(...) +export interface RenderSlotCall extends CallExpression { + callee: typeof RENDER_SLOT + arguments: // $slots, name, props, fallback + | [string, string | ExpressionNode] + | [string, string | ExpressionNode, PropsExpression] | [ - string | symbol, - 'null' | PropsExpression, - 'null' | SlotsExpression, string, - string + string | ExpressionNode, + PropsExpression | '{}', + TemplateChildNode[] ] } -export type ComponentCodegenNode = - | PlainComponentCodegenNode - | CodegenNodeWithDirective - export type SlotsExpression = SlotsObjectExpression | DynamicSlotsExpression // { foo: () => [...] } @@ -462,63 +450,20 @@ export interface DynamicSlotFnProperty extends Property { value: SlotFunctionExpression } -// withDirectives(createVNode(...), [ -// [_directive_foo, someValue], -// [_directive_bar, someValue, "arg", { mod: true }] -// ]) -export interface CodegenNodeWithDirective - extends CallExpression { - callee: typeof WITH_DIRECTIVES - arguments: [T, DirectiveArguments] -} - -export interface DirectiveArguments extends ArrayExpression { - elements: DirectiveArgumentNode[] -} - -export interface DirectiveArgumentNode extends ArrayExpression { - elements: // dir, exp, arg, modifiers - | [string] - | [string, ExpressionNode] - | [string, ExpressionNode, ExpressionNode] - | [string, ExpressionNode, ExpressionNode, ObjectExpression] -} - -// renderSlot(...) -export interface SlotOutletCodegenNode extends CallExpression { - callee: typeof RENDER_SLOT - arguments: // $slots, name, props, fallback - | [string, string | ExpressionNode] - | [string, string | ExpressionNode, PropsExpression] - | [ - string, - string | ExpressionNode, - PropsExpression | '{}', - TemplateChildNode[] - ] -} - -export type BlockCodegenNode = - | ElementCodegenNode - | ComponentCodegenNode - | SlotOutletCodegenNode - -export interface IfCodegenNode extends SequenceExpression { - expressions: [OpenBlockExpression, IfConditionalExpression] -} +export type BlockCodegenNode = VNodeCall | RenderSlotCall export interface IfConditionalExpression extends ConditionalExpression { consequent: BlockCodegenNode alternate: BlockCodegenNode | IfConditionalExpression } -export interface ForCodegenNode extends SequenceExpression { - expressions: [OpenBlockExpression, ForBlockCodegenNode] -} - -export interface ForBlockCodegenNode extends CallExpression { - callee: typeof CREATE_BLOCK - arguments: [typeof FRAGMENT, 'null', ForRenderListExpression, string] +export interface ForCodegenNode extends VNodeCall { + isBlock: true + tag: typeof FRAGMENT + props: undefined + children: ForRenderListExpression + patchFlag: string + isForBlock: true } export interface ForRenderListExpression extends CallExpression { @@ -530,11 +475,6 @@ export interface ForIteratorExpression extends FunctionExpression { returns: BlockCodegenNode } -export interface OpenBlockExpression extends CallExpression { - callee: typeof OPEN_BLOCK - arguments: [] -} - // AST Utilities --------------------------------------------------------------- // Some expressions, e.g. sequence and conditional expressions, are never @@ -565,6 +505,42 @@ export function createRoot( } } +export function createVNodeCall( + context: TransformContext, + tag: VNodeCall['tag'], + props?: VNodeCall['props'], + children?: VNodeCall['children'], + patchFlag?: VNodeCall['patchFlag'], + dynamicProps?: VNodeCall['dynamicProps'], + directives?: VNodeCall['directives'], + isBlock: VNodeCall['isBlock'] = false, + isForBlock: VNodeCall['isForBlock'] = false, + loc = locStub +): VNodeCall { + if (isBlock) { + context.helper(OPEN_BLOCK) + context.helper(CREATE_BLOCK) + } else { + context.helper(CREATE_VNODE) + } + if (directives) { + context.helper(WITH_DIRECTIVES) + } + + return { + type: NodeTypes.VNODE_CALL, + tag, + props, + children, + patchFlag, + dynamicProps, + directives, + isBlock, + isForBlock, + loc + } +} + export function createArrayExpression( elements: ArrayExpression['elements'], loc: SourceLocation = locStub @@ -638,15 +614,9 @@ export function createCompoundExpression( } } -type InferCodegenNodeType = T extends - | typeof CREATE_VNODE - | typeof CREATE_BLOCK - ? PlainElementCodegenNode | PlainComponentCodegenNode - : T extends typeof WITH_DIRECTIVES - ? - | CodegenNodeWithDirective - | CodegenNodeWithDirective - : T extends typeof RENDER_SLOT ? SlotOutletCodegenNode : CallExpression +type InferCodegenNodeType = T extends typeof RENDER_SLOT + ? RenderSlotCall + : CallExpression export function createCallExpression( callee: T, @@ -678,16 +648,6 @@ export function createFunctionExpression( } } -export function createSequenceExpression( - expressions: SequenceExpression['expressions'] -): SequenceExpression { - return { - type: NodeTypes.JS_SEQUENCE_EXPRESSION, - expressions, - loc: locStub - } -} - export function createConditionalExpression( test: ConditionalExpression['test'], consequent: ConditionalExpression['consequent'], diff --git a/packages/compiler-core/src/codegen.ts b/packages/compiler-core/src/codegen.ts index 1a88da5e22..8014416f22 100644 --- a/packages/compiler-core/src/codegen.ts +++ b/packages/compiler-core/src/codegen.ts @@ -15,7 +15,6 @@ import { CompoundExpressionNode, SimpleExpressionNode, FunctionExpression, - SequenceExpression, ConditionalExpression, CacheExpression, locStub, @@ -23,7 +22,8 @@ import { TemplateLiteral, IfStatement, AssignmentExpression, - ReturnStatement + ReturnStatement, + VNodeCall } from './ast' import { SourceMapGenerator, RawSourceMap } from 'source-map' import { @@ -45,7 +45,10 @@ import { CREATE_TEXT, PUSH_SCOPE_ID, POP_SCOPE_ID, - WITH_SCOPE_ID + WITH_SCOPE_ID, + WITH_DIRECTIVES, + CREATE_BLOCK, + OPEN_BLOCK } from './runtimeHelpers' import { ImportItem } from './transform' @@ -547,6 +550,10 @@ function genNode(node: CodegenNode | symbol | string, context: CodegenContext) { case NodeTypes.COMMENT: genComment(node, context) break + case NodeTypes.VNODE_CALL: + genVNodeCall(node, context) + break + case NodeTypes.JS_CALL_EXPRESSION: genCallExpression(node, context) break @@ -559,9 +566,6 @@ function genNode(node: CodegenNode | symbol | string, context: CodegenContext) { case NodeTypes.JS_FUNCTION_EXPRESSION: genFunctionExpression(node, context) break - case NodeTypes.JS_SEQUENCE_EXPRESSION: - genSequenceExpression(node, context) - break case NodeTypes.JS_CONDITIONAL_EXPRESSION: genConditionalExpression(node, context) break @@ -657,6 +661,48 @@ function genComment(node: CommentNode, context: CodegenContext) { } } +function genVNodeCall(node: VNodeCall, context: CodegenContext) { + const { push, helper } = context + const { + tag, + props, + children, + patchFlag, + dynamicProps, + directives, + isBlock, + isForBlock + } = node + if (directives) { + push(helper(WITH_DIRECTIVES) + `(`) + } + if (isBlock) { + push(`(${helper(OPEN_BLOCK)}(${isForBlock ? `true` : ``}), `) + } + push(helper(isBlock ? CREATE_BLOCK : CREATE_VNODE) + `(`, node) + genNodeList( + genNullableArgs([tag, props, children, patchFlag, dynamicProps]), + context + ) + push(`)`) + if (isBlock) { + push(`)`) + } + if (directives) { + push(`, `) + genNode(directives, context) + push(`)`) + } +} + +function genNullableArgs(args: any[]): CallExpression['arguments'] { + let i = args.length + while (i--) { + if (args[i] != null) break + } + return args.slice(0, i + 1).map(arg => arg || `null`) +} + // JavaScript function genCallExpression(node: CallExpression, context: CodegenContext) { const callee = isString(node.callee) @@ -782,15 +828,6 @@ function genConditionalExpression( needNewline && deindent(true /* without newline */) } -function genSequenceExpression( - node: SequenceExpression, - context: CodegenContext -) { - context.push(`(`) - genNodeList(node.expressions, context) - context.push(`)`) -} - function genCacheExpression(node: CacheExpression, context: CodegenContext) { const { push, helper, indent, deindent, newline } = context push(`_cache[${node.index}] || (`) diff --git a/packages/compiler-core/src/transform.ts b/packages/compiler-core/src/transform.ts index ee8f98f6ca..758dc8357b 100644 --- a/packages/compiler-core/src/transform.ts +++ b/packages/compiler-core/src/transform.ts @@ -12,12 +12,10 @@ import { JSChildNode, SimpleExpressionNode, ElementTypes, - ElementCodegenNode, - ComponentCodegenNode, - createCallExpression, CacheExpression, createCacheExpression, - TemplateLiteral + TemplateLiteral, + createVNodeCall } from './ast' import { isString, @@ -31,11 +29,11 @@ import { TO_DISPLAY_STRING, FRAGMENT, helperNameMap, - WITH_DIRECTIVES, CREATE_BLOCK, - CREATE_COMMENT + CREATE_COMMENT, + OPEN_BLOCK } from './runtimeHelpers' -import { isVSlot, createBlockExpression } from './utils' +import { isVSlot } from './utils' import { hoistStatic, isSingleElementRoot } from './transforms/hoistStatic' // There are two types of transforms: @@ -286,20 +284,13 @@ function createRootCodegen(root: RootNode, context: TransformContext) { if (isSingleElementRoot(root, child) && child.codegenNode) { // single element root is never hoisted so codegenNode will never be // SimpleExpressionNode - const codegenNode = child.codegenNode as - | ElementCodegenNode - | ComponentCodegenNode - | CacheExpression - if (codegenNode.type !== NodeTypes.JS_CACHE_EXPRESSION) { - if (codegenNode.callee === WITH_DIRECTIVES) { - codegenNode.arguments[0].callee = helper(CREATE_BLOCK) - } else { - codegenNode.callee = helper(CREATE_BLOCK) - } - root.codegenNode = createBlockExpression(codegenNode, context) - } else { - root.codegenNode = codegenNode + const codegenNode = child.codegenNode + if (codegenNode.type === NodeTypes.VNODE_CALL) { + codegenNode.isBlock = true + helper(OPEN_BLOCK) + helper(CREATE_BLOCK) } + root.codegenNode = codegenNode } else { // - single , IfNode, ForNode: already blocks. // - single text node: always patched. @@ -308,16 +299,17 @@ function createRootCodegen(root: RootNode, context: TransformContext) { } } else if (children.length > 1) { // root has multiple nodes - return a fragment block. - root.codegenNode = createBlockExpression( - createCallExpression(helper(CREATE_BLOCK), [ - helper(FRAGMENT), - `null`, - root.children, - `${PatchFlags.STABLE_FRAGMENT} /* ${ - PatchFlagNames[PatchFlags.STABLE_FRAGMENT] - } */` - ]), - context + root.codegenNode = createVNodeCall( + context, + helper(FRAGMENT), + undefined, + root.children, + `${PatchFlags.STABLE_FRAGMENT} /* ${ + PatchFlagNames[PatchFlags.STABLE_FRAGMENT] + } */`, + undefined, + undefined, + true ) } else { // no children = noop. codegen will return null. diff --git a/packages/compiler-core/src/transforms/hoistStatic.ts b/packages/compiler-core/src/transforms/hoistStatic.ts index f04f24449c..2dd514bee1 100644 --- a/packages/compiler-core/src/transforms/hoistStatic.ts +++ b/packages/compiler-core/src/transforms/hoistStatic.ts @@ -8,11 +8,9 @@ import { ComponentNode, TemplateNode, ElementNode, - PlainElementCodegenNode, - CodegenNodeWithDirective + VNodeCall } from '../ast' import { TransformContext } from '../transform' -import { WITH_DIRECTIVES } from '../runtimeHelpers' import { PatchFlags, isString, isSymbol } from '@vue/shared' import { isSlotOutlet, findProp } from '../utils' @@ -60,7 +58,7 @@ function walk( // node may contain dynamic children, but its props may be eligible for // hoisting. const codegenNode = child.codegenNode! - if (codegenNode.type === NodeTypes.JS_CALL_EXPRESSION) { + if (codegenNode.type === NodeTypes.VNODE_CALL) { const flag = getPatchFlag(codegenNode) if ( (!flag || @@ -70,8 +68,8 @@ function walk( !hasCachedProps(child) ) { const props = getNodeProps(child) - if (props && props !== `null`) { - getVNodeCall(codegenNode).arguments[1] = context.hoist(props) + if (props) { + codegenNode.props = context.hoist(props) } } } @@ -111,7 +109,7 @@ export function isStaticNode( return cached } const codegenNode = node.codegenNode! - if (codegenNode.type !== NodeTypes.JS_CALL_EXPRESSION) { + if (codegenNode.type !== NodeTypes.VNODE_CALL) { return false } const flag = getPatchFlag(codegenNode) @@ -123,6 +121,12 @@ export function isStaticNode( return false } } + // only svg/foeignObject could be block here, however if they are static + // then they don't need to be blocks since there will be no nested + // udpates. + if (codegenNode.isBlock) { + codegenNode.isBlock = false + } resultCache.set(node, true) return true } else { @@ -164,11 +168,7 @@ function hasCachedProps(node: PlainElementNode): boolean { return false } const props = getNodeProps(node) - if ( - props && - props !== 'null' && - props.type === NodeTypes.JS_OBJECT_EXPRESSION - ) { + if (props && props.type === NodeTypes.JS_OBJECT_EXPRESSION) { const { properties } = props for (let i = 0; i < properties.length; i++) { if (properties[i].value.type === NodeTypes.JS_CACHE_EXPRESSION) { @@ -181,30 +181,12 @@ function hasCachedProps(node: PlainElementNode): boolean { function getNodeProps(node: PlainElementNode) { const codegenNode = node.codegenNode! - if (codegenNode.type === NodeTypes.JS_CALL_EXPRESSION) { - return getVNodeArgAt( - codegenNode, - 1 - ) as PlainElementCodegenNode['arguments'][1] + if (codegenNode.type === NodeTypes.VNODE_CALL) { + return codegenNode.props } } -type NonCachedCodegenNode = - | PlainElementCodegenNode - | CodegenNodeWithDirective - -function getVNodeArgAt( - node: NonCachedCodegenNode, - index: number -): PlainElementCodegenNode['arguments'][number] { - return getVNodeCall(node).arguments[index] -} - -function getVNodeCall(node: NonCachedCodegenNode) { - return node.callee === WITH_DIRECTIVES ? node.arguments[0] : node -} - -function getPatchFlag(node: NonCachedCodegenNode): number | undefined { - const flag = getVNodeArgAt(node, 3) as string +function getPatchFlag(node: VNodeCall): number | undefined { + const flag = node.patchFlag return flag ? parseInt(flag, 10) : undefined } diff --git a/packages/compiler-core/src/transforms/transformElement.ts b/packages/compiler-core/src/transforms/transformElement.ts index f12f220493..2d8eead62d 100644 --- a/packages/compiler-core/src/transforms/transformElement.ts +++ b/packages/compiler-core/src/transforms/transformElement.ts @@ -14,23 +14,22 @@ import { createSimpleExpression, createObjectExpression, Property, - createSequenceExpression, - ComponentNode + ComponentNode, + VNodeCall, + TemplateTextChildNode, + DirectiveArguments, + createVNodeCall } from '../ast' import { PatchFlags, PatchFlagNames, isSymbol } from '@vue/shared' import { createCompilerError, ErrorCodes } from '../errors' import { - CREATE_VNODE, - WITH_DIRECTIVES, RESOLVE_DIRECTIVE, RESOLVE_COMPONENT, RESOLVE_DYNAMIC_COMPONENT, MERGE_PROPS, TO_HANDLERS, PORTAL, - KEEP_ALIVE, - OPEN_BLOCK, - CREATE_BLOCK + KEEP_ALIVE } from '../runtimeHelpers' import { getInnerRange, @@ -63,6 +62,20 @@ export const transformElement: NodeTransform = (node, context) => { const { tag, props } = node const isComponent = node.tagType === ElementTypes.COMPONENT + // The goal of the transform is to create a codegenNode implementing the + // VNodeCall interface. + const vnodeTag = isComponent + ? resolveComponentType(node as ComponentNode, context) + : `"${tag}"` + + let vnodeProps: VNodeCall['props'] + let vnodeChildren: VNodeCall['children'] + let vnodePatchFlag: VNodeCall['patchFlag'] + let patchFlag: number = 0 + let vnodeDynamicProps: VNodeCall['dynamicProps'] + let dynamicPropNames: string[] | undefined + let vnodeDirectives: VNodeCall['directives'] + // and must be forced into blocks so that block // updates inside get proper isSVG flag at runtime. (#639, #643) // This is technically web-specific, but splitting the logic out of core @@ -70,38 +83,24 @@ export const transformElement: NodeTransform = (node, context) => { let shouldUseBlock = !isComponent && (tag === 'svg' || tag === 'foreignObject') - const nodeType = isComponent - ? resolveComponentType(node as ComponentNode, context) - : `"${tag}"` - - const args: CallExpression['arguments'] = [nodeType] - - let hasProps = props.length > 0 - let patchFlag: number = 0 - let runtimeDirectives: DirectiveNode[] | undefined - let dynamicPropNames: string[] | undefined - // props - if (hasProps) { + if (props.length > 0) { const propsBuildResult = buildProps(node, context) + vnodeProps = propsBuildResult.props patchFlag = propsBuildResult.patchFlag dynamicPropNames = propsBuildResult.dynamicPropNames - runtimeDirectives = propsBuildResult.directives - if (!propsBuildResult.props) { - hasProps = false - } else { - args.push(propsBuildResult.props) - } + const directives = propsBuildResult.directives + vnodeDirectives = + directives && directives.length + ? (createArrayExpression( + directives.map(dir => buildDirectiveArgs(dir, context)) + ) as DirectiveArguments) + : undefined } // children - const hasChildren = node.children.length > 0 - if (hasChildren) { - if (!hasProps) { - args.push(`null`) - } - - if (nodeType === KEEP_ALIVE) { + if (node.children.length > 0) { + if (vnodeTag === KEEP_ALIVE) { // Although a built-in component, we compile KeepAlive with raw children // instead of slot functions so that it can be used inside Transition // or other Transition-wrapping HOCs. @@ -125,13 +124,13 @@ export const transformElement: NodeTransform = (node, context) => { const shouldBuildAsSlots = isComponent && // Portal is not a real component has dedicated handling in the renderer - nodeType !== PORTAL && + vnodeTag !== PORTAL && // explained above. - nodeType !== KEEP_ALIVE + vnodeTag !== KEEP_ALIVE if (shouldBuildAsSlots) { const { slots, hasDynamicSlots } = buildSlots(node, context) - args.push(slots) + vnodeChildren = slots if (hasDynamicSlots) { patchFlag |= PatchFlags.DYNAMIC_SLOTS } @@ -148,60 +147,44 @@ export const transformElement: NodeTransform = (node, context) => { // pass directly if the only child is a text node // (plain / interpolation / expression) if (hasDynamicTextChild || type === NodeTypes.TEXT) { - args.push(child) + vnodeChildren = child as TemplateTextChildNode } else { - args.push(node.children) + vnodeChildren = node.children } } else { - args.push(node.children) + vnodeChildren = node.children } } // patchFlag & dynamicPropNames if (patchFlag !== 0) { - if (!hasChildren) { - if (!hasProps) { - args.push(`null`) - } - args.push(`null`) - } if (__DEV__) { const flagNames = Object.keys(PatchFlagNames) .map(Number) .filter(n => n > 0 && patchFlag & n) .map(n => PatchFlagNames[n]) .join(`, `) - args.push(patchFlag + ` /* ${flagNames} */`) + vnodePatchFlag = patchFlag + ` /* ${flagNames} */` } else { - args.push(patchFlag + '') + vnodePatchFlag = String(patchFlag) } if (dynamicPropNames && dynamicPropNames.length) { - args.push(stringifyDynamicPropNames(dynamicPropNames)) + vnodeDynamicProps = stringifyDynamicPropNames(dynamicPropNames) } } - const { loc } = node - const vnode = shouldUseBlock - ? createSequenceExpression([ - createCallExpression(context.helper(OPEN_BLOCK)), - createCallExpression(context.helper(CREATE_BLOCK), args, loc) - ]) - : createCallExpression(context.helper(CREATE_VNODE), args, loc) - if (runtimeDirectives && runtimeDirectives.length) { - node.codegenNode = createCallExpression( - context.helper(WITH_DIRECTIVES), - [ - vnode, - createArrayExpression( - runtimeDirectives.map(dir => buildDirectiveArgs(dir, context)), - loc - ) - ], - loc - ) - } else { - node.codegenNode = vnode - } + node.codegenNode = createVNodeCall( + context, + vnodeTag, + vnodeProps, + vnodeChildren, + vnodePatchFlag, + vnodeDynamicProps, + vnodeDirectives, + shouldUseBlock, + false /* isForBlock */, + node.loc + ) } } diff --git a/packages/compiler-core/src/transforms/vFor.ts b/packages/compiler-core/src/transforms/vFor.ts index 48b2e3d1e8..a1869ba7f3 100644 --- a/packages/compiler-core/src/transforms/vFor.ts +++ b/packages/compiler-core/src/transforms/vFor.ts @@ -8,26 +8,28 @@ import { createSimpleExpression, SourceLocation, SimpleExpressionNode, - createSequenceExpression, createCallExpression, createFunctionExpression, ElementTypes, createObjectExpression, createObjectProperty, ForCodegenNode, - ElementCodegenNode, - SlotOutletCodegenNode, + RenderSlotCall, SlotOutletNode, ElementNode, DirectiveNode, ForNode, - PlainElementNode + PlainElementNode, + createVNodeCall, + VNodeCall, + ForRenderListExpression, + BlockCodegenNode, + ForIteratorExpression } from '../ast' import { createCompilerError, ErrorCodes } from '../errors' import { getInnerRange, findProp, - createBlockExpression, isTemplateNode, isSlotOutlet, injectProp @@ -36,8 +38,7 @@ import { RENDER_LIST, OPEN_BLOCK, CREATE_BLOCK, - FRAGMENT, - WITH_DIRECTIVES + FRAGMENT } from '../runtimeHelpers' import { processExpression } from './transformExpression' import { PatchFlags, PatchFlagNames } from '@vue/shared' @@ -51,26 +52,27 @@ export const transformFor = createStructuralDirectiveTransform( // iterator on exit after all children have been traversed const renderExp = createCallExpression(helper(RENDER_LIST), [ forNode.source - ]) + ]) as ForRenderListExpression const keyProp = findProp(node, `key`) const fragmentFlag = keyProp ? PatchFlags.KEYED_FRAGMENT : PatchFlags.UNKEYED_FRAGMENT - forNode.codegenNode = createSequenceExpression([ - // v-for fragment blocks disable tracking since they always diff their - // children - createCallExpression(helper(OPEN_BLOCK), [`true`]), - createCallExpression(helper(CREATE_BLOCK), [ - helper(FRAGMENT), - `null`, - renderExp, - `${fragmentFlag} /* ${PatchFlagNames[fragmentFlag]} */` - ]) - ]) as ForCodegenNode + forNode.codegenNode = createVNodeCall( + context, + helper(FRAGMENT), + undefined, + renderExp, + `${fragmentFlag} /* ${PatchFlagNames[fragmentFlag]} */`, + undefined, + undefined, + true /* isBlock */, + true /* isForBlock */, + node.loc + ) as ForCodegenNode return () => { // finish the codegen now that all children have been traversed - let childBlock + let childBlock: BlockCodegenNode const isTemplate = isTemplateNode(node) const { children } = forNode const needFragmentWrapper = @@ -92,7 +94,7 @@ export const transformFor = createStructuralDirectiveTransform( : null if (slotOutlet) { // or - childBlock = slotOutlet.codegenNode as SlotOutletCodegenNode + childBlock = slotOutlet.codegenNode as RenderSlotCall if (isTemplate && keyProperty) { // // we need to inject the key to the renderSlot() call. @@ -102,37 +104,33 @@ export const transformFor = createStructuralDirectiveTransform( } else if (needFragmentWrapper) { //