]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(compiler): handle block nodes with custom directives + improve ast types
authorEvan You <yyx990803@gmail.com>
Tue, 8 Oct 2019 14:50:00 +0000 (10:50 -0400)
committerEvan You <yyx990803@gmail.com>
Tue, 8 Oct 2019 14:50:10 +0000 (10:50 -0400)
15 files changed:
packages/compiler-core/__tests__/codegen.spec.ts
packages/compiler-core/__tests__/testUtils.ts
packages/compiler-core/__tests__/transform.spec.ts
packages/compiler-core/__tests__/transforms/__snapshots__/hoistStatic.spec.ts.snap
packages/compiler-core/__tests__/transforms/__snapshots__/optimizeText.spec.ts.snap
packages/compiler-core/__tests__/transforms/__snapshots__/vFor.spec.ts.snap
packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap
packages/compiler-core/__tests__/transforms/transformElement.spec.ts
packages/compiler-core/__tests__/transforms/vFor.spec.ts
packages/compiler-core/src/ast.ts
packages/compiler-core/src/transform.ts
packages/compiler-core/src/transforms/hoistStatic.ts
packages/compiler-core/src/transforms/vFor.ts
packages/compiler-core/src/transforms/vIf.ts
packages/compiler-core/src/utils.ts

index 240c1949ea775e58eda30cdeed406edb6cab138e..48a89c41572835043558ab7e02132e6a5d508c11 100644 (file)
@@ -34,7 +34,7 @@ function createRoot(options: Partial<RootNode> = {}): RootNode {
     components: [],
     directives: [],
     hoists: [],
-    codegenNode: undefined,
+    codegenNode: createSimpleExpression(`null`, false),
     loc: locStub,
     ...options
   }
index 74855d5183a49cb91b99dfd6e5bcf9d4f81c34e9..abffe1549cf0f74f82363f85c865de47dca09174 100644 (file)
@@ -4,7 +4,7 @@ import {
   locStub,
   Namespaces,
   ElementTypes,
-  ElementCodegenNode
+  PlainElementCodegenNode
 } from '../src'
 import { CREATE_VNODE } from '../src/runtimeHelpers'
 import { isString, PatchFlags, PatchFlagNames, isArray } from '@vue/shared'
@@ -39,7 +39,7 @@ export function createObjectMatcher(obj: any) {
 }
 
 export function createElementWithCodegen(
-  args: ElementCodegenNode['arguments']
+  args: PlainElementCodegenNode['arguments']
 ): ElementNode {
   return {
     type: NodeTypes.ELEMENT,
index 6f778e156153b2b634e5574284efc32a36246799..d9203fe8801be0523071a63af3931c44c8188e58 100644 (file)
@@ -14,7 +14,8 @@ import {
   OPEN_BLOCK,
   CREATE_BLOCK,
   FRAGMENT,
-  RENDER_SLOT
+  RENDER_SLOT,
+  APPLY_DIRECTIVES
 } from '../src/runtimeHelpers'
 import { transformIf } from '../src/transforms/vIf'
 import { transformFor } from '../src/transforms/vFor'
@@ -301,6 +302,28 @@ describe('compiler: transform', () => {
       })
     })
 
+    test('root element with custom directive', () => {
+      const ast = transformWithCodegen(`<div v-foo/>`)
+      expect(ast.codegenNode).toMatchObject({
+        type: NodeTypes.JS_SEQUENCE_EXPRESSION,
+        expressions: [
+          {
+            type: NodeTypes.JS_CALL_EXPRESSION,
+            callee: OPEN_BLOCK
+          },
+          {
+            type: NodeTypes.JS_CALL_EXPRESSION,
+            // should wrap applyDirectives() around createBlock()
+            callee: APPLY_DIRECTIVES,
+            arguments: [
+              { callee: CREATE_BLOCK },
+              { type: NodeTypes.JS_ARRAY_EXPRESSION }
+            ]
+          }
+        ]
+      })
+    })
+
     test('single text', () => {
       const ast = transformWithCodegen(`hello`)
       expect(ast.codegenNode).toMatchObject({
index 5ca448b03c028543fb40d04903a468ac54471e13..6ecefb03731380a14a94319b37232c1c6fead802 100644 (file)
@@ -11,7 +11,7 @@ const _hoisted_1 = _createVNode(\\"p\\", null, [
 
 return function render() {
   with (this) {
-    const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
+    const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
     
     return (_openBlock(), _createBlock(\\"div\\", null, [
       _hoisted_1
@@ -29,7 +29,7 @@ const _hoisted_2 = _createVNode(\\"div\\")
 
 return function render() {
   with (this) {
-    const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
+    const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
     
     return (_openBlock(), _createBlock(\\"div\\", null, [
       _hoisted_1,
@@ -47,7 +47,7 @@ const _hoisted_1 = _createVNode(\\"span\\", { class: \\"inline\\" }, \\"hello\\"
 
 return function render() {
   with (this) {
-    const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
+    const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
     
     return (_openBlock(), _createBlock(\\"div\\", null, [
       _hoisted_1
@@ -64,7 +64,7 @@ const _hoisted_1 = { id: \\"foo\\" }
 
 return function render() {
   with (this) {
-    const { createVNode: _createVNode, applyDirectives: _applyDirectives, resolveDirective: _resolveDirective, openBlock: _openBlock, createBlock: _createBlock } = _Vue
+    const { createVNode: _createVNode, applyDirectives: _applyDirectives, resolveDirective: _resolveDirective, createBlock: _createBlock, openBlock: _openBlock } = _Vue
     
     const _directive_foo = _resolveDirective(\\"foo\\")
     
@@ -85,7 +85,7 @@ const _hoisted_1 = { id: \\"foo\\" }
 
 return function render() {
   with (this) {
-    const { toString: _toString, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
+    const { toString: _toString, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
     
     return (_openBlock(), _createBlock(\\"div\\", null, [
       _createVNode(\\"div\\", _hoisted_1, _toString(hello), 1 /* TEXT */)
@@ -102,7 +102,7 @@ const _hoisted_1 = { id: \\"foo\\" }
 
 return function render() {
   with (this) {
-    const { resolveComponent: _resolveComponent, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
+    const { resolveComponent: _resolveComponent, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
     
     const _component_Comp = _resolveComponent(\\"Comp\\")
     
@@ -120,7 +120,7 @@ exports[`compiler: hositStatic transform should NOT hoist components 1`] = `
 
 return function render() {
   with (this) {
-    const { resolveComponent: _resolveComponent, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
+    const { resolveComponent: _resolveComponent, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
     
     const _component_Comp = _resolveComponent(\\"Comp\\")
     
@@ -136,7 +136,7 @@ exports[`compiler: hositStatic transform should NOT hoist element with dynamic p
 
 return function render() {
   with (this) {
-    const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
+    const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
     
     return (_openBlock(), _createBlock(\\"div\\", null, [
       _createVNode(\\"div\\", { id: foo }, null, 8 /* PROPS */, [\\"id\\"])
@@ -150,7 +150,7 @@ exports[`compiler: hositStatic transform should NOT hoist root node 1`] = `
 
 return function render() {
   with (this) {
-    const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
+    const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
     
     return (_openBlock(), _createBlock(\\"div\\"))
   }
index a876945f3887c5202d423962e26211caf16bb338..ce5754237515e0e38d6ad6c426a88ccd9ef39d53 100644 (file)
@@ -17,7 +17,7 @@ exports[`compiler: optimize interpolation consecutive text between elements 1`]
 
 return function render() {
   with (this) {
-    const { createVNode: _createVNode, toString: _toString, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock } = _Vue
+    const { createVNode: _createVNode, toString: _toString, createBlock: _createBlock, Fragment: _Fragment, openBlock: _openBlock } = _Vue
     
     return (_openBlock(), _createBlock(_Fragment, null, [
       _createVNode(\\"div\\"),
@@ -33,7 +33,7 @@ exports[`compiler: optimize interpolation consecutive text mixed with elements 1
 
 return function render() {
   with (this) {
-    const { createVNode: _createVNode, toString: _toString, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock } = _Vue
+    const { createVNode: _createVNode, toString: _toString, createBlock: _createBlock, Fragment: _Fragment, openBlock: _openBlock } = _Vue
     
     return (_openBlock(), _createBlock(_Fragment, null, [
       _createVNode(\\"div\\"),
index 02bbbcbb88c2035c0f562a202ea409661d3df859..454a5f43b3d53e337ed168bf71367bf997d302b0 100644 (file)
@@ -132,6 +132,24 @@ return function render() {
 }"
 `;
 
+exports[`compiler: v-for codegen v-for on element with custom directive 1`] = `
+"const _Vue = Vue
+
+return function render() {
+  with (this) {
+    const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, createVNode: _createVNode, applyDirectives: _applyDirectives, resolveDirective: _resolveDirective } = _Vue
+    
+    const _directive_foo = _resolveDirective(\\"foo\\")
+    
+    return (_openBlock(), _createBlock(_Fragment, null, _renderList(list, (i) => {
+      return (_openBlock(), _applyDirectives(_createBlock(\\"div\\", null, null, 32 /* NEED_PATCH */), [
+        [_directive_foo]
+      ]))
+    }), 128 /* UNKEYED_FRAGMENT */))
+  }
+}"
+`;
+
 exports[`compiler: v-for codegen v-if + v-for 1`] = `
 "const _Vue = Vue
 
index 8bbb14f28eb7a5719bfb17c8e347394ce6a8576c..266e2f6ff934f2dad69c04f296fb96765e1d2582 100644 (file)
@@ -1,7 +1,7 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
 exports[`compiler: transform component slots dynamically named slots 1`] = `
-"const { toString, resolveComponent, createVNode, openBlock, createBlock } = Vue
+"const { toString, resolveComponent, createVNode, createBlock, openBlock } = Vue
 
 return function render() {
   const _ctx = this
@@ -16,7 +16,7 @@ return function render() {
 `;
 
 exports[`compiler: transform component slots explicit default slot 1`] = `
-"const { toString, resolveComponent, createVNode, openBlock, createBlock } = Vue
+"const { toString, resolveComponent, createVNode, createBlock, openBlock } = Vue
 
 return function render() {
   const _ctx = this
@@ -30,7 +30,7 @@ return function render() {
 `;
 
 exports[`compiler: transform component slots implicit default slot 1`] = `
-"const { createVNode, resolveComponent, openBlock, createBlock } = Vue
+"const { createVNode, resolveComponent, createBlock, openBlock } = Vue
 
 return function render() {
   const _ctx = this
@@ -46,7 +46,7 @@ return function render() {
 `;
 
 exports[`compiler: transform component slots named slot with v-for w/ prefixIdentifiers: true 1`] = `
-"const { toString, resolveComponent, renderList, createSlots, createVNode, openBlock, createBlock } = Vue
+"const { toString, resolveComponent, renderList, createSlots, createVNode, createBlock, openBlock } = Vue
 
 return function render() {
   const _ctx = this
@@ -64,7 +64,7 @@ return function render() {
 `;
 
 exports[`compiler: transform component slots named slot with v-if + prefixIdentifiers: true 1`] = `
-"const { toString, resolveComponent, createSlots, createVNode, openBlock, createBlock } = Vue
+"const { toString, resolveComponent, createSlots, createVNode, createBlock, openBlock } = Vue
 
 return function render() {
   const _ctx = this
@@ -86,7 +86,7 @@ exports[`compiler: transform component slots named slot with v-if + v-else-if +
 
 return function render() {
   with (this) {
-    const { resolveComponent: _resolveComponent, createSlots: _createSlots, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
+    const { resolveComponent: _resolveComponent, createSlots: _createSlots, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
     
     const _component_Comp = _resolveComponent(\\"Comp\\")
     
@@ -115,7 +115,7 @@ exports[`compiler: transform component slots named slot with v-if 1`] = `
 
 return function render() {
   with (this) {
-    const { resolveComponent: _resolveComponent, createSlots: _createSlots, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
+    const { resolveComponent: _resolveComponent, createSlots: _createSlots, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
     
     const _component_Comp = _resolveComponent(\\"Comp\\")
     
@@ -132,7 +132,7 @@ return function render() {
 `;
 
 exports[`compiler: transform component slots named slots 1`] = `
-"const { toString, resolveComponent, createVNode, openBlock, createBlock } = Vue
+"const { toString, resolveComponent, createVNode, createBlock, openBlock } = Vue
 
 return function render() {
   const _ctx = this
@@ -147,7 +147,7 @@ return function render() {
 `;
 
 exports[`compiler: transform component slots nested slots scoping 1`] = `
-"const { toString, resolveComponent, createVNode, openBlock, createBlock } = Vue
+"const { toString, resolveComponent, createVNode, createBlock, openBlock } = Vue
 
 return function render() {
   const _ctx = this
index 2bd6e186c44ada7a7ac0dea17341524d67d6d676..fb6230aeb82cc5a21d0eac23ee266036b80c73c8 100644 (file)
@@ -1,10 +1,4 @@
-import {
-  ElementNode,
-  CompilerOptions,
-  parse,
-  transform,
-  ErrorCodes
-} from '../../src'
+import { CompilerOptions, parse, transform, ErrorCodes } from '../../src'
 import {
   RESOLVE_COMPONENT,
   CREATE_VNODE,
@@ -35,12 +29,14 @@ function parseWithElementTransform(
   root: RootNode
   node: CallExpression
 } {
-  const ast = parse(template, options)
+  // wrap raw template in an extra div so that it doesn't get turned into a
+  // block as root node
+  const ast = parse(`<div>${template}</div>`, options)
   transform(ast, {
     nodeTransforms: [optimizeText, transformElement],
     ...options
   })
-  const codegenNode = (ast.children[0] as ElementNode)
+  const codegenNode = (ast as any).children[0].children[0]
     .codegenNode as CallExpression
   expect(codegenNode.type).toBe(NodeTypes.JS_CALL_EXPRESSION)
   return {
index ff6a4d4586fc323cddccf0d562ff462d52aa0266..8763d74e3dc2f6991aac9699486055de3454014e 100644 (file)
@@ -22,7 +22,8 @@ import {
   CREATE_BLOCK,
   FRAGMENT,
   RENDER_LIST,
-  RENDER_SLOT
+  RENDER_SLOT,
+  APPLY_DIRECTIVES
 } from '../../src/runtimeHelpers'
 import { PatchFlags } from '@vue/runtime-dom'
 import { createObjectMatcher, genFlagText } from '../testUtils'
@@ -845,5 +846,28 @@ describe('compiler: v-for', () => {
       })
       expect(generate(root).code).toMatchSnapshot()
     })
+
+    test('v-for on element with custom directive', () => {
+      const {
+        root,
+        node: { codegenNode }
+      } = parseWithForTransform('<div v-for="i in list" v-foo/>')
+      const { returns } = assertSharedCodegen(codegenNode, false, true)
+      expect(returns).toMatchObject({
+        type: NodeTypes.JS_SEQUENCE_EXPRESSION,
+        expressions: [
+          { callee: OPEN_BLOCK },
+          // should wrap applyDirectives() around createBlock()
+          {
+            callee: APPLY_DIRECTIVES,
+            arguments: [
+              { callee: CREATE_BLOCK },
+              { type: NodeTypes.JS_ARRAY_EXPRESSION }
+            ]
+          }
+        ]
+      })
+      expect(generate(root).code).toMatchSnapshot()
+    })
   })
 })
index dcfc3732bbef3512593916ea633f9264354f9928..6a915d85c774e479d1eef8335b6480531eaccd25 100644 (file)
@@ -114,19 +114,12 @@ export interface BaseElementNode extends Node {
 
 export interface PlainElementNode extends BaseElementNode {
   tagType: ElementTypes.ELEMENT
-  codegenNode:
-    | ElementCodegenNode
-    | CodegenNodeWithDirective<ElementCodegenNode>
-    | undefined
-  // | SimpleExpressionNode (only when hoisted)
+  codegenNode: ElementCodegenNode | undefined | SimpleExpressionNode // only when hoisted
 }
 
 export interface ComponentNode extends BaseElementNode {
   tagType: ElementTypes.COMPONENT
-  codegenNode:
-    | ComponentCodegenNode
-    | CodegenNodeWithDirective<ComponentCodegenNode>
-    | undefined
+  codegenNode: ComponentCodegenNode | undefined
 }
 
 export interface SlotOutletNode extends BaseElementNode {
@@ -280,8 +273,8 @@ export interface ConditionalExpression extends Node {
 // Codegen Node Types ----------------------------------------------------------
 
 // createVNode(...)
-export interface ElementCodegenNode extends CallExpression {
-  callee: typeof CREATE_VNODE
+export interface PlainElementCodegenNode extends CallExpression {
+  callee: typeof CREATE_VNODE | typeof CREATE_BLOCK
   arguments:  // tag, props, children, patchFlag, dynamicProps
     | [string | RuntimeHelper]
     | [string | RuntimeHelper, PropsExpression]
@@ -301,13 +294,13 @@ export interface ElementCodegenNode extends CallExpression {
       ]
 }
 
-export type ElementCodegenNodeWithDirective = CodegenNodeWithDirective<
-  ElementCodegenNode
->
+export type ElementCodegenNode =
+  | PlainElementCodegenNode
+  | CodegenNodeWithDirective<PlainElementCodegenNode>
 
 // createVNode(...)
-export interface ComponentCodegenNode extends CallExpression {
-  callee: typeof CREATE_VNODE
+export interface PlainComponentCodegenNode extends CallExpression {
+  callee: typeof CREATE_VNODE | typeof CREATE_BLOCK
   arguments:  // Comp, props, slots, patchFlag, dynamicProps
     | [string | RuntimeHelper]
     | [string | RuntimeHelper, PropsExpression]
@@ -327,9 +320,9 @@ export interface ComponentCodegenNode extends CallExpression {
       ]
 }
 
-export type CompoenntCodegenNodeWithDirective = CodegenNodeWithDirective<
-  ComponentCodegenNode
->
+export type ComponentCodegenNode =
+  | PlainComponentCodegenNode
+  | CodegenNodeWithDirective<PlainComponentCodegenNode>
 
 export type SlotsExpression = SlotsObjectExpression | DynamicSlotsExpression
 
@@ -417,6 +410,11 @@ export interface SlotOutletCodegenNode extends CallExpression {
       ]
 }
 
+export type BlockCodegenNode =
+  | ElementCodegenNode
+  | ComponentCodegenNode
+  | SlotOutletCodegenNode
+
 export interface IfCodegenNode extends SequenceExpression {
   expressions: [OpenBlockExpression, IfConditionalExpression]
 }
@@ -449,28 +447,6 @@ export interface OpenBlockExpression extends CallExpression {
   arguments: []
 }
 
-export type BlockCodegenNode =
-  | BlockElementCodegenNode
-  | BlockComponentCodegenNode
-  | BlockElementCodegenNodeWithDirective
-  | BlockComponentCodegenNodeWithDirective
-
-export type BlockElementCodegenNode = ElementCodegenNode & {
-  callee: typeof CREATE_BLOCK
-}
-
-export type BlockComponentCodegenNode = ComponentCodegenNode & {
-  callee: typeof CREATE_BLOCK
-}
-
-export type BlockElementCodegenNodeWithDirective = CodegenNodeWithDirective<
-  BlockElementCodegenNode
->
-
-export type BlockComponentCodegenNodeWithDirective = CodegenNodeWithDirective<
-  BlockComponentCodegenNode
->
-
 // AST Utilities ---------------------------------------------------------------
 
 // Some expressions, e.g. sequence and conditional expressions, are never
@@ -552,13 +528,15 @@ export function createCompoundExpression(
   }
 }
 
-type InferCodegenNodeType<T> = T extends typeof CREATE_VNODE
-  ? ElementCodegenNode | ComponentCodegenNode
-  : T extends typeof CREATE_BLOCK
-    ? BlockElementCodegenNode | BlockComponentCodegenNode
-    : T extends typeof APPLY_DIRECTIVES
-      ? ElementCodegenNodeWithDirective | CompoenntCodegenNodeWithDirective
-      : T extends typeof RENDER_SLOT ? SlotOutletCodegenNode : CallExpression
+type InferCodegenNodeType<T> = T extends
+  | typeof CREATE_VNODE
+  | typeof CREATE_BLOCK
+  ? PlainElementCodegenNode | PlainComponentCodegenNode
+  : T extends typeof APPLY_DIRECTIVES
+    ?
+        | CodegenNodeWithDirective<PlainElementCodegenNode>
+        | CodegenNodeWithDirective<PlainComponentCodegenNode>
+    : T extends typeof RENDER_SLOT ? SlotOutletCodegenNode : CallExpression
 
 export function createCallExpression<T extends CallExpression['callee']>(
   callee: T,
index 54977c35c7bcf1f116c6d6ae4ec7c74c25fff3e0..4e8877f30e5aeb6389e32ccd1f798f68f17932ed 100644 (file)
@@ -10,7 +10,10 @@ import {
   createSimpleExpression,
   JSChildNode,
   SimpleExpressionNode,
-  ElementTypes
+  ElementTypes,
+  ElementCodegenNode,
+  ComponentCodegenNode,
+  createCallExpression
 } from './ast'
 import { isString, isArray } from '@vue/shared'
 import { CompilerError, defaultOnError } from './errors'
@@ -20,7 +23,9 @@ import {
   CREATE_VNODE,
   FRAGMENT,
   RuntimeHelper,
-  helperNameMap
+  helperNameMap,
+  APPLY_DIRECTIVES,
+  CREATE_BLOCK
 } from './runtimeHelpers'
 import { isVSlot, createBlockExpression } from './utils'
 import { hoistStatic, isSingleElementRoot } from './transforms/hoistStatic'
@@ -147,7 +152,7 @@ function createTransformContext(
       }
       const list = context.parent!.children
       const removalIndex = node
-        ? list.indexOf(node as any)
+        ? list.indexOf(node)
         : context.currentNode
           ? context.childIndex
           : -1
@@ -230,24 +235,38 @@ function finalizeRoot(root: RootNode, context: TransformContext) {
   const { helper } = context
   const { children } = root
   const child = children[0]
-  if (isSingleElementRoot(root, child) && child.codegenNode) {
-    // turn root element into a block
-    root.codegenNode = createBlockExpression(
-      child.codegenNode.arguments,
-      context
-    )
-  } else if (children.length === 1) {
-    // - single <slot/>, IfNode, ForNode: already blocks.
-    // - single text node: always patched.
-    // - transform calls without transformElement (only during tests)
-    // Just generate the node as-is
-    root.codegenNode = child
+  if (children.length === 1) {
+    // if the single child is an element, turn it into a block.
+    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
+      if (codegenNode.callee === APPLY_DIRECTIVES) {
+        codegenNode.arguments[0].callee = helper(CREATE_BLOCK)
+      } else {
+        codegenNode.callee = helper(CREATE_BLOCK)
+      }
+      root.codegenNode = createBlockExpression(codegenNode, context)
+    } else {
+      // - single <slot/>, IfNode, ForNode: already blocks.
+      // - single text node: always patched.
+      // root codegen falls through via genNode()
+      root.codegenNode = child
+    }
   } else if (children.length > 1) {
     // root has multiple nodes - return a fragment block.
     root.codegenNode = createBlockExpression(
-      [helper(FRAGMENT), `null`, root.children],
+      createCallExpression(helper(CREATE_BLOCK), [
+        helper(FRAGMENT),
+        `null`,
+        root.children
+      ]),
       context
     )
+  } else {
+    // no children = noop. codegen will return null.
   }
   // finalize meta information
   root.helpers = [...context.helpers]
index b949db22ac43d7ded8bfc76329c523d2b154ba76..6760a439770383eaa3aed56f9124cb4d3ebe0daa 100644 (file)
@@ -2,10 +2,8 @@ import {
   RootNode,
   NodeTypes,
   TemplateChildNode,
-  ElementNode,
   ElementTypes,
   ElementCodegenNode,
-  ElementCodegenNodeWithDirective,
   PlainElementNode,
   ComponentNode,
   TemplateNode
@@ -51,7 +49,7 @@ function walk(
     ) {
       if (!doNotHoistNode && isStaticNode(child, resultCache)) {
         // whole tree is static
-        ;(child as any).codegenNode = context.hoist(child.codegenNode!)
+        child.codegenNode = context.hoist(child.codegenNode!)
         continue
       } else {
         // node may contain dynamic children, but its props may be eligible for
@@ -62,7 +60,7 @@ function walk(
           flag === PatchFlags.NEED_PATCH ||
           flag === PatchFlags.TEXT
         ) {
-          let codegenNode = child.codegenNode!
+          let codegenNode = child.codegenNode as ElementCodegenNode
           if (codegenNode.callee === APPLY_DIRECTIVES) {
             codegenNode = codegenNode.arguments[0]
           }
@@ -88,10 +86,8 @@ function walk(
   }
 }
 
-function getPatchFlag(node: ElementNode): number | undefined {
-  let codegenNode = node.codegenNode as
-    | ElementCodegenNode
-    | ElementCodegenNodeWithDirective
+function getPatchFlag(node: PlainElementNode): number | undefined {
+  let codegenNode = node.codegenNode as ElementCodegenNode
   if (codegenNode.callee === APPLY_DIRECTIVES) {
     codegenNode = codegenNode.arguments[0]
   }
index 4e92e0fb8ce9c9bd0c7b60fb81067d9745470e33..674b43b5bcaddbce32c8a05df3c1c99aa6ad45f2 100644 (file)
@@ -15,7 +15,7 @@ import {
   createObjectExpression,
   createObjectProperty,
   ForCodegenNode,
-  PlainElementNode
+  ElementCodegenNode
 } from '../ast'
 import { createCompilerError, ErrorCodes } from '../errors'
 import {
@@ -30,11 +30,11 @@ import {
   RENDER_LIST,
   OPEN_BLOCK,
   CREATE_BLOCK,
-  FRAGMENT
+  FRAGMENT,
+  APPLY_DIRECTIVES
 } from '../runtimeHelpers'
 import { processExpression } from './transformExpression'
 import { PatchFlags, PatchFlagNames } from '@vue/shared'
-import { PropsExpression } from './transformElement'
 
 export const transformFor = createStructuralDirectiveTransform(
   'for',
@@ -124,34 +124,29 @@ export const transformFor = createStructuralDirectiveTransform(
               // <template v-for="..." :key="..."><slot/></template>
               // we need to inject the key to the renderSlot() call.
               // the props for renderSlot is passed as the 3rd argument.
-              const existingProps = childBlock.arguments[2] as
-                | PropsExpression
-                | undefined
-                | 'null'
-              childBlock.arguments[2] = injectProp(
-                existingProps,
-                keyProperty,
-                context
-              )
+              injectProp(childBlock, keyProperty, context)
             }
           } else if (isTemplate) {
             // <template v-for="...">
             // should generate a fragment block for each loop
             childBlock = createBlockExpression(
-              [
+              createCallExpression(helper(CREATE_BLOCK), [
                 helper(FRAGMENT),
                 keyProperty ? createObjectExpression([keyProperty]) : `null`,
                 node.children
-              ],
+              ]),
               context
             )
           } else {
             // Normal element v-for. Directly use the child's codegenNode
             // arguments, but replace createVNode() with createBlock()
-            childBlock = createBlockExpression(
-              (node as PlainElementNode).codegenNode!.arguments,
-              context
-            )
+            let codegenNode = node.codegenNode as ElementCodegenNode
+            if (codegenNode.callee === APPLY_DIRECTIVES) {
+              codegenNode.arguments[0].callee = helper(CREATE_BLOCK)
+            } else {
+              codegenNode.callee = helper(CREATE_BLOCK)
+            }
+            childBlock = createBlockExpression(codegenNode, context)
           }
 
           renderExp.arguments.push(
index 76df830dc5a23e8e98cb95ac8ed520f8c44a346e..b7268171b3414bb3cff606daf68f7b3c8cc09f20 100644 (file)
@@ -23,9 +23,7 @@ import {
   BlockCodegenNode,
   SlotOutletCodegenNode,
   ElementCodegenNode,
-  ComponentCodegenNode,
-  ElementCodegenNodeWithDirective,
-  CompoenntCodegenNodeWithDirective
+  ComponentCodegenNode
 } from '../ast'
 import { createCompilerError, ErrorCodes } from '../errors'
 import { processExpression } from './transformExpression'
@@ -35,8 +33,7 @@ import {
   EMPTY,
   FRAGMENT,
   APPLY_DIRECTIVES,
-  CREATE_VNODE,
-  RENDER_SLOT
+  CREATE_VNODE
 } from '../runtimeHelpers'
 import { injectProp } from '../utils'
 
@@ -196,8 +193,6 @@ function createChildrenCodegenNode(
     const childCodegen = (child as ElementNode).codegenNode as
       | ElementCodegenNode
       | ComponentCodegenNode
-      | ElementCodegenNodeWithDirective
-      | CompoenntCodegenNodeWithDirective
       | SlotOutletCodegenNode
     let vnodeCall = childCodegen
     // Element with custom directives. Locate the actual createVNode() call.
@@ -206,22 +201,10 @@ function createChildrenCodegenNode(
     }
     // Change createVNode to createBlock.
     if (vnodeCall.callee === CREATE_VNODE) {
-      ;(vnodeCall as any).callee = helper(CREATE_BLOCK)
+      vnodeCall.callee = helper(CREATE_BLOCK)
     }
-    // It's possible to have renderSlot() here as well - which already produces
-    // a block, so no need to change the callee. However it accepts props at
-    // a different arg index so make sure to check for so that the key injection
-    // logic below works for it too.
-    const propsIndex = vnodeCall.callee === RENDER_SLOT ? 2 : 1
     // inject branch key
-    const existingProps = vnodeCall.arguments[
-      propsIndex
-    ] as ElementCodegenNode['arguments'][1]
-    vnodeCall.arguments[propsIndex] = injectProp(
-      existingProps,
-      keyProperty,
-      context
-    )
+    injectProp(vnodeCall, keyProperty, context)
     return childCodegen
   }
 }
index eb8eee27923cfc12dd705cd703df5bd2703134de..6ffae2caa5fef33e5bd47f72a50b2ed016fe193f 100644 (file)
@@ -16,14 +16,17 @@ import {
   JSChildNode,
   createObjectExpression,
   SlotOutletNode,
-  TemplateNode
+  TemplateNode,
+  BlockCodegenNode,
+  ElementCodegenNode,
+  SlotOutletCodegenNode,
+  ComponentCodegenNode
 } from './ast'
 import { parse } from 'acorn'
 import { walk } from 'estree-walker'
 import { TransformContext } from './transform'
-import { OPEN_BLOCK, CREATE_BLOCK, MERGE_PROPS } from './runtimeHelpers'
+import { OPEN_BLOCK, MERGE_PROPS, RENDER_SLOT } from './runtimeHelpers'
 import { isString, isFunction } from '@vue/shared'
-import { PropsExpression } from './transforms/transformElement'
 
 // cache node requires
 // lazy require dependencies so that they don't end up in rollup's dep graph
@@ -168,12 +171,12 @@ export function findProp(
 }
 
 export function createBlockExpression(
-  args: CallExpression['arguments'],
+  blockExp: BlockCodegenNode,
   context: TransformContext
 ): SequenceExpression {
   return createSequenceExpression([
     createCallExpression(context.helper(OPEN_BLOCK)),
-    createCallExpression(context.helper(CREATE_BLOCK), args)
+    blockExp
   ])
 }
 
@@ -191,12 +194,15 @@ export const isSlotOutlet = (
   node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.SLOT
 
 export function injectProp(
-  props: PropsExpression | undefined | 'null',
+  node: ElementCodegenNode | ComponentCodegenNode | SlotOutletCodegenNode,
   prop: Property,
   context: TransformContext
-): ObjectExpression | CallExpression {
-  if (props == null || props === `null`) {
-    return createObjectExpression([prop])
+) {
+  let propsWithInjection: ObjectExpression | CallExpression
+  const props =
+    node.callee === RENDER_SLOT ? node.arguments[2] : node.arguments[1]
+  if (props == null || isString(props)) {
+    propsWithInjection = createObjectExpression([prop])
   } else if (props.type === NodeTypes.JS_CALL_EXPRESSION) {
     // merged props... add ours
     // only inject key to object literal if it's the first argument so that
@@ -207,17 +213,22 @@ export function injectProp(
     } else {
       props.arguments.unshift(createObjectExpression([prop]))
     }
-    return props
+    propsWithInjection = props
   } else if (props.type === NodeTypes.JS_OBJECT_EXPRESSION) {
     props.properties.unshift(prop)
-    return props
+    propsWithInjection = props
   } else {
     // single v-bind with expression, return a merged replacement
-    return createCallExpression(context.helper(MERGE_PROPS), [
+    propsWithInjection = createCallExpression(context.helper(MERGE_PROPS), [
       createObjectExpression([prop]),
       props
     ])
   }
+  if (node.callee === RENDER_SLOT) {
+    node.arguments[2] = propsWithInjection
+  } else {
+    node.arguments[1] = propsWithInjection
+  }
 }
 
 export function toValidAssetId(