]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
refactor(compiler-core): use dedicated node type for element codegen
authorEvan You <yyx990803@gmail.com>
Tue, 11 Feb 2020 23:12:56 +0000 (18:12 -0500)
committerEvan You <yyx990803@gmail.com>
Tue, 11 Feb 2020 23:40:42 +0000 (18:40 -0500)
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.

packages/compiler-core/src/ast.ts
packages/compiler-core/src/codegen.ts
packages/compiler-core/src/transform.ts
packages/compiler-core/src/transforms/hoistStatic.ts
packages/compiler-core/src/transforms/transformElement.ts
packages/compiler-core/src/transforms/vFor.ts
packages/compiler-core/src/transforms/vIf.ts
packages/compiler-core/src/transforms/vSlot.ts
packages/compiler-core/src/utils.ts
packages/runtime-core/src/vnode.ts

index 0039b6e05ebee3f166f0a924fe066bcfe6759bb1..8655a5dc8da6d72750527a21399bb37554695363 100644 (file)
@@ -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<AttributeNode | DirectiveNode>
   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<PlainElementCodegenNode>
+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<PlainComponentCodegenNode>
-
 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<T extends CallExpression>
-  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> = T extends
-  | typeof CREATE_VNODE
-  | typeof CREATE_BLOCK
-  ? PlainElementCodegenNode | PlainComponentCodegenNode
-  : T extends typeof WITH_DIRECTIVES
-    ?
-        | CodegenNodeWithDirective<PlainElementCodegenNode>
-        | CodegenNodeWithDirective<PlainComponentCodegenNode>
-    : T extends typeof RENDER_SLOT ? SlotOutletCodegenNode : CallExpression
+type InferCodegenNodeType<T> = T extends typeof RENDER_SLOT
+  ? RenderSlotCall
+  : CallExpression
 
 export function createCallExpression<T extends CallExpression['callee']>(
   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'],
index 1a88da5e221549a561d6be139e36713abf3d1fc8..8014416f22704357f4859fe4f6050a51f65a4123 100644 (file)
@@ -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}] || (`)
index ee8f98f6ca3682bd35e290e9936b4701f30b7954..758dc8357b92fb07e180ddc533af876e44013c37 100644 (file)
@@ -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 <slot/>, 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.
index f04f24449cb8efacd63e5997afd5c6b80915b142..2dd514bee1f79319a263b0ad8e2e0415a3088b87 100644 (file)
@@ -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<PlainElementCodegenNode>
-
-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
 }
index f12f220493898b9d1d630a4b1712d966c5e6b3eb..2d8eead62d76d1497ca8f3c37141177561d2a871 100644 (file)
@@ -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']
+
     // <svg> and <foreignObject> 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
+    )
   }
 }
 
index 48b2e3d1e8231b7b84de2e4238544b7f5d99d4a5..a1869ba7f39d840654421061452abec510f4c4b5 100644 (file)
@@ -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) {
           // <slot v-for="..."> or <template v-for="..."><slot/></template>
-          childBlock = slotOutlet.codegenNode as SlotOutletCodegenNode
+          childBlock = slotOutlet.codegenNode as RenderSlotCall
           if (isTemplate && keyProperty) {
             // <template v-for="..." :key="..."><slot/></template>
             // we need to inject the key to the renderSlot() call.
@@ -102,37 +104,33 @@ export const transformFor = createStructuralDirectiveTransform(
         } else if (needFragmentWrapper) {
           // <template v-for="..."> with text or multi-elements
           // should generate a fragment block for each loop
-          childBlock = createBlockExpression(
-            createCallExpression(helper(CREATE_BLOCK), [
-              helper(FRAGMENT),
-              keyProperty ? createObjectExpression([keyProperty]) : `null`,
-              node.children,
-              `${PatchFlags.STABLE_FRAGMENT} /* ${
-                PatchFlagNames[PatchFlags.STABLE_FRAGMENT]
-              } */`
-            ]),
-            context
+          childBlock = createVNodeCall(
+            context,
+            helper(FRAGMENT),
+            keyProperty ? createObjectExpression([keyProperty]) : undefined,
+            node.children,
+            `${PatchFlags.STABLE_FRAGMENT} /* ${
+              PatchFlagNames[PatchFlags.STABLE_FRAGMENT]
+            } */`,
+            undefined,
+            undefined,
+            true
           )
         } else {
           // Normal element v-for. Directly use the child's codegenNode
-          // arguments, but replace createVNode() with createBlock()
-          let codegenNode = (children[0] as PlainElementNode)
-            .codegenNode as ElementCodegenNode
-          if (codegenNode.callee === WITH_DIRECTIVES) {
-            codegenNode.arguments[0].callee = helper(CREATE_BLOCK)
-          } else {
-            codegenNode.callee = helper(CREATE_BLOCK)
-          }
-          childBlock = createBlockExpression(codegenNode, context)
+          // but mark it as a block.
+          childBlock = (children[0] as PlainElementNode)
+            .codegenNode as VNodeCall
+          childBlock.isBlock = true
+          helper(OPEN_BLOCK)
+          helper(CREATE_BLOCK)
         }
 
-        renderExp.arguments.push(
-          createFunctionExpression(
-            createForLoopParams(forNode.parseResult),
-            childBlock,
-            true /* force newline */
-          )
-        )
+        renderExp.arguments.push(createFunctionExpression(
+          createForLoopParams(forNode.parseResult),
+          childBlock,
+          true /* force newline */
+        ) as ForIteratorExpression)
       }
     })
   }
index 5ddf8b176ea3185799871ae2a5057015aa5e26bc..15909a62d91cd0cd64947deb8eec4f43f45bd44d 100644 (file)
@@ -10,31 +10,23 @@ import {
   DirectiveNode,
   IfBranchNode,
   SimpleExpressionNode,
-  createSequenceExpression,
   createCallExpression,
   createConditionalExpression,
-  ConditionalExpression,
-  CallExpression,
   createSimpleExpression,
   createObjectProperty,
   createObjectExpression,
-  IfCodegenNode,
   IfConditionalExpression,
   BlockCodegenNode,
-  SlotOutletCodegenNode,
-  ElementCodegenNode,
-  ComponentCodegenNode,
-  IfNode
+  IfNode,
+  createVNodeCall
 } from '../ast'
 import { createCompilerError, ErrorCodes } from '../errors'
 import { processExpression } from './transformExpression'
 import {
-  OPEN_BLOCK,
   CREATE_BLOCK,
   FRAGMENT,
-  WITH_DIRECTIVES,
-  CREATE_VNODE,
-  CREATE_COMMENT
+  CREATE_COMMENT,
+  OPEN_BLOCK
 } from '../runtimeHelpers'
 import { injectProp } from '../utils'
 
@@ -46,14 +38,14 @@ export const transformIf = createStructuralDirectiveTransform(
       // transformed.
       return () => {
         if (isRoot) {
-          ifNode.codegenNode = createSequenceExpression([
-            createCallExpression(context.helper(OPEN_BLOCK)),
-            createCodegenNodeForBranch(branch, 0, context)
-          ]) as IfCodegenNode
+          ifNode.codegenNode = createCodegenNodeForBranch(
+            branch,
+            0,
+            context
+          ) as IfConditionalExpression
         } else {
           // attach this branch's codegen node to the v-if root.
           let parentCondition = ifNode.codegenNode!
-            .expressions[1] as ConditionalExpression
           while (
             parentCondition.alternate.type ===
             NodeTypes.JS_CONDITIONAL_EXPRESSION
@@ -175,7 +167,7 @@ function createCodegenNodeForBranch(
       ])
     ) as IfConditionalExpression
   } else {
-    return createChildrenCodegenNode(branch, index, context) as BlockCodegenNode
+    return createChildrenCodegenNode(branch, index, context)
   }
 }
 
@@ -183,7 +175,7 @@ function createChildrenCodegenNode(
   branch: IfBranchNode,
   index: number,
   context: TransformContext
-): CallExpression {
+): BlockCodegenNode {
   const { helper } = context
   const keyProperty = createObjectProperty(
     `key`,
@@ -194,35 +186,36 @@ function createChildrenCodegenNode(
   const needFragmentWrapper =
     children.length !== 1 || firstChild.type !== NodeTypes.ELEMENT
   if (needFragmentWrapper) {
-    const blockArgs: CallExpression['arguments'] = [
-      helper(FRAGMENT),
-      createObjectExpression([keyProperty]),
-      children
-    ]
     if (children.length === 1 && firstChild.type === NodeTypes.FOR) {
       // optimize away nested fragments when child is a ForNode
-      const forBlockArgs = firstChild.codegenNode!.expressions[1].arguments
-      // directly use the for block's children and patchFlag
-      blockArgs[2] = forBlockArgs[2]
-      blockArgs[3] = forBlockArgs[3]
+      const vnodeCall = firstChild.codegenNode!
+      injectProp(vnodeCall, keyProperty, context)
+      return vnodeCall
+    } else {
+      return createVNodeCall(
+        context,
+        helper(FRAGMENT),
+        createObjectExpression([keyProperty]),
+        children,
+        undefined,
+        undefined,
+        undefined,
+        true,
+        false,
+        branch.loc
+      )
     }
-    return createCallExpression(helper(CREATE_BLOCK), blockArgs)
   } else {
-    const childCodegen = (firstChild as ElementNode).codegenNode as
-      | ElementCodegenNode
-      | ComponentCodegenNode
-      | SlotOutletCodegenNode
-    let vnodeCall = childCodegen
-    // Element with custom directives. Locate the actual createVNode() call.
-    if (vnodeCall.callee === WITH_DIRECTIVES) {
-      vnodeCall = vnodeCall.arguments[0]
-    }
+    const vnodeCall = (firstChild as ElementNode)
+      .codegenNode as BlockCodegenNode
     // Change createVNode to createBlock.
-    if (vnodeCall.callee === CREATE_VNODE) {
-      vnodeCall.callee = helper(CREATE_BLOCK)
+    if (vnodeCall.type === NodeTypes.VNODE_CALL) {
+      vnodeCall.isBlock = true
+      helper(OPEN_BLOCK)
+      helper(CREATE_BLOCK)
     }
     // inject branch key
     injectProp(vnodeCall, keyProperty, context)
-    return childCodegen
+    return vnodeCall
   }
 }
index 053db5b8f243274b8e16170089c8b1750fd77bb5..00a9b40056ce77fa9b72e3c40ad181089d631c97 100644 (file)
@@ -19,7 +19,8 @@ import {
   FunctionExpression,
   CallExpression,
   createCallExpression,
-  createArrayExpression
+  createArrayExpression,
+  SlotsExpression
 } from '../ast'
 import { TransformContext, NodeTransform } from '../transform'
 import { createCompilerError, ErrorCodes } from '../errors'
@@ -115,7 +116,7 @@ export function buildSlots(
   context: TransformContext,
   buildSlotFn: SlotFnBuilder = buildClientSlotFn
 ): {
-  slots: ObjectExpression | CallExpression
+  slots: SlotsExpression
   hasDynamicSlots: boolean
 } {
   const { children, loc } = node
@@ -312,17 +313,17 @@ export function buildSlots(
     }
   }
 
-  let slots: ObjectExpression | CallExpression = createObjectExpression(
+  let slots = createObjectExpression(
     slotsProperties.concat(
       createObjectProperty(`_compiled`, createSimpleExpression(`true`, false))
     ),
     loc
-  )
+  ) as SlotsExpression
   if (dynamicSlots.length) {
     slots = createCallExpression(context.helper(CREATE_SLOTS), [
       slots,
       createArrayExpression(dynamicSlots)
-    ])
+    ]) as SlotsExpression
   }
 
   return {
index 3a99ff8ea09da92c5464be206b529b0a110c34c3..7fe6a4985ffab6b94ab472ede6879d93674be768 100644 (file)
@@ -4,8 +4,6 @@ import {
   ElementNode,
   NodeTypes,
   CallExpression,
-  SequenceExpression,
-  createSequenceExpression,
   createCallExpression,
   DirectiveNode,
   ElementTypes,
@@ -17,22 +15,18 @@ import {
   createObjectExpression,
   SlotOutletNode,
   TemplateNode,
-  BlockCodegenNode,
-  ElementCodegenNode,
-  SlotOutletCodegenNode,
-  ComponentCodegenNode,
+  RenderSlotCall,
   ExpressionNode,
   IfBranchNode,
   TextNode,
-  InterpolationNode
+  InterpolationNode,
+  VNodeCall
 } from './ast'
 import { parse } from 'acorn'
 import { walk } from 'estree-walker'
 import { TransformContext } from './transform'
 import {
-  OPEN_BLOCK,
   MERGE_PROPS,
-  RENDER_SLOT,
   PORTAL,
   SUSPENSE,
   KEEP_ALIVE,
@@ -218,16 +212,6 @@ export function hasDynamicKeyVBind(node: ElementNode): boolean {
   )
 }
 
-export function createBlockExpression(
-  blockExp: BlockCodegenNode,
-  context: TransformContext
-): SequenceExpression {
-  return createSequenceExpression([
-    createCallExpression(context.helper(OPEN_BLOCK)),
-    blockExp
-  ])
-}
-
 export function isText(
   node: TemplateChildNode
 ): node is TextNode | InterpolationNode {
@@ -253,13 +237,13 @@ export function isSlotOutlet(
 }
 
 export function injectProp(
-  node: ElementCodegenNode | ComponentCodegenNode | SlotOutletCodegenNode,
+  node: VNodeCall | RenderSlotCall,
   prop: Property,
   context: TransformContext
 ) {
   let propsWithInjection: ObjectExpression | CallExpression
   const props =
-    node.callee === RENDER_SLOT ? node.arguments[2] : node.arguments[1]
+    node.type === NodeTypes.VNODE_CALL ? node.props : node.arguments[2]
   if (props == null || isString(props)) {
     propsWithInjection = createObjectExpression([prop])
   } else if (props.type === NodeTypes.JS_CALL_EXPRESSION) {
@@ -295,10 +279,10 @@ export function injectProp(
       props
     ])
   }
-  if (node.callee === RENDER_SLOT) {
-    node.arguments[2] = propsWithInjection
+  if (node.type === NodeTypes.VNODE_CALL) {
+    node.props = propsWithInjection
   } else {
-    node.arguments[1] = propsWithInjection
+    node.arguments[2] = propsWithInjection
   }
 }
 
index c57dfae02f43615bbf4862ab5ce3ba19e15e05df..dd31a0ac86b4e90160954bf9d71cdf967adc7523 100644 (file)
@@ -335,7 +335,7 @@ export function createCommentVNode(
   asBlock: boolean = false
 ): VNode {
   return asBlock
-    ? createBlock(Comment, null, text)
+    ? (openBlock(), createBlock(Comment, null, text))
     : createVNode(Comment, null, text)
 }