fix(compiler): handle block nodes with custom directives + improve ast types

This commit is contained in:
Evan You 2019-10-08 10:50:00 -04:00
parent 1393ee52ca
commit 16da9ae89f
15 changed files with 200 additions and 157 deletions

View File

@ -34,7 +34,7 @@ function createRoot(options: Partial<RootNode> = {}): RootNode {
components: [], components: [],
directives: [], directives: [],
hoists: [], hoists: [],
codegenNode: undefined, codegenNode: createSimpleExpression(`null`, false),
loc: locStub, loc: locStub,
...options ...options
} }

View File

@ -4,7 +4,7 @@ import {
locStub, locStub,
Namespaces, Namespaces,
ElementTypes, ElementTypes,
ElementCodegenNode PlainElementCodegenNode
} from '../src' } from '../src'
import { CREATE_VNODE } from '../src/runtimeHelpers' import { CREATE_VNODE } from '../src/runtimeHelpers'
import { isString, PatchFlags, PatchFlagNames, isArray } from '@vue/shared' import { isString, PatchFlags, PatchFlagNames, isArray } from '@vue/shared'
@ -39,7 +39,7 @@ export function createObjectMatcher(obj: any) {
} }
export function createElementWithCodegen( export function createElementWithCodegen(
args: ElementCodegenNode['arguments'] args: PlainElementCodegenNode['arguments']
): ElementNode { ): ElementNode {
return { return {
type: NodeTypes.ELEMENT, type: NodeTypes.ELEMENT,

View File

@ -14,7 +14,8 @@ import {
OPEN_BLOCK, OPEN_BLOCK,
CREATE_BLOCK, CREATE_BLOCK,
FRAGMENT, FRAGMENT,
RENDER_SLOT RENDER_SLOT,
APPLY_DIRECTIVES
} from '../src/runtimeHelpers' } from '../src/runtimeHelpers'
import { transformIf } from '../src/transforms/vIf' import { transformIf } from '../src/transforms/vIf'
import { transformFor } from '../src/transforms/vFor' 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', () => { test('single text', () => {
const ast = transformWithCodegen(`hello`) const ast = transformWithCodegen(`hello`)
expect(ast.codegenNode).toMatchObject({ expect(ast.codegenNode).toMatchObject({

View File

@ -11,7 +11,7 @@ const _hoisted_1 = _createVNode(\\"p\\", null, [
return function render() { return function render() {
with (this) { with (this) {
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return (_openBlock(), _createBlock(\\"div\\", null, [ return (_openBlock(), _createBlock(\\"div\\", null, [
_hoisted_1 _hoisted_1
@ -29,7 +29,7 @@ const _hoisted_2 = _createVNode(\\"div\\")
return function render() { return function render() {
with (this) { with (this) {
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return (_openBlock(), _createBlock(\\"div\\", null, [ return (_openBlock(), _createBlock(\\"div\\", null, [
_hoisted_1, _hoisted_1,
@ -47,7 +47,7 @@ const _hoisted_1 = _createVNode(\\"span\\", { class: \\"inline\\" }, \\"hello\\"
return function render() { return function render() {
with (this) { with (this) {
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return (_openBlock(), _createBlock(\\"div\\", null, [ return (_openBlock(), _createBlock(\\"div\\", null, [
_hoisted_1 _hoisted_1
@ -64,7 +64,7 @@ const _hoisted_1 = { id: \\"foo\\" }
return function render() { return function render() {
with (this) { 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\\") const _directive_foo = _resolveDirective(\\"foo\\")
@ -85,7 +85,7 @@ const _hoisted_1 = { id: \\"foo\\" }
return function render() { return function render() {
with (this) { 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, [ return (_openBlock(), _createBlock(\\"div\\", null, [
_createVNode(\\"div\\", _hoisted_1, _toString(hello), 1 /* TEXT */) _createVNode(\\"div\\", _hoisted_1, _toString(hello), 1 /* TEXT */)
@ -102,7 +102,7 @@ const _hoisted_1 = { id: \\"foo\\" }
return function render() { return function render() {
with (this) { 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\\") const _component_Comp = _resolveComponent(\\"Comp\\")
@ -120,7 +120,7 @@ exports[`compiler: hositStatic transform should NOT hoist components 1`] = `
return function render() { return function render() {
with (this) { 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\\") const _component_Comp = _resolveComponent(\\"Comp\\")
@ -136,7 +136,7 @@ exports[`compiler: hositStatic transform should NOT hoist element with dynamic p
return function render() { return function render() {
with (this) { with (this) {
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return (_openBlock(), _createBlock(\\"div\\", null, [ return (_openBlock(), _createBlock(\\"div\\", null, [
_createVNode(\\"div\\", { id: foo }, null, 8 /* PROPS */, [\\"id\\"]) _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() { return function render() {
with (this) { with (this) {
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return (_openBlock(), _createBlock(\\"div\\")) return (_openBlock(), _createBlock(\\"div\\"))
} }

View File

@ -17,7 +17,7 @@ exports[`compiler: optimize interpolation consecutive text between elements 1`]
return function render() { return function render() {
with (this) { 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, [ return (_openBlock(), _createBlock(_Fragment, null, [
_createVNode(\\"div\\"), _createVNode(\\"div\\"),
@ -33,7 +33,7 @@ exports[`compiler: optimize interpolation consecutive text mixed with elements 1
return function render() { return function render() {
with (this) { 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, [ return (_openBlock(), _createBlock(_Fragment, null, [
_createVNode(\\"div\\"), _createVNode(\\"div\\"),

View 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`] = ` exports[`compiler: v-for codegen v-if + v-for 1`] = `
"const _Vue = Vue "const _Vue = Vue

View File

@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`compiler: transform component slots dynamically named slots 1`] = ` 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() { return function render() {
const _ctx = this const _ctx = this
@ -16,7 +16,7 @@ return function render() {
`; `;
exports[`compiler: transform component slots explicit default slot 1`] = ` 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() { return function render() {
const _ctx = this const _ctx = this
@ -30,7 +30,7 @@ return function render() {
`; `;
exports[`compiler: transform component slots implicit default slot 1`] = ` exports[`compiler: transform component slots implicit default slot 1`] = `
"const { createVNode, resolveComponent, openBlock, createBlock } = Vue "const { createVNode, resolveComponent, createBlock, openBlock } = Vue
return function render() { return function render() {
const _ctx = this const _ctx = this
@ -46,7 +46,7 @@ return function render() {
`; `;
exports[`compiler: transform component slots named slot with v-for w/ prefixIdentifiers: true 1`] = ` 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() { return function render() {
const _ctx = this const _ctx = this
@ -64,7 +64,7 @@ return function render() {
`; `;
exports[`compiler: transform component slots named slot with v-if + prefixIdentifiers: true 1`] = ` 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() { return function render() {
const _ctx = this const _ctx = this
@ -86,7 +86,7 @@ exports[`compiler: transform component slots named slot with v-if + v-else-if +
return function render() { return function render() {
with (this) { 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\\") const _component_Comp = _resolveComponent(\\"Comp\\")
@ -115,7 +115,7 @@ exports[`compiler: transform component slots named slot with v-if 1`] = `
return function render() { return function render() {
with (this) { 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\\") const _component_Comp = _resolveComponent(\\"Comp\\")
@ -132,7 +132,7 @@ return function render() {
`; `;
exports[`compiler: transform component slots named slots 1`] = ` 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() { return function render() {
const _ctx = this const _ctx = this
@ -147,7 +147,7 @@ return function render() {
`; `;
exports[`compiler: transform component slots nested slots scoping 1`] = ` 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() { return function render() {
const _ctx = this const _ctx = this

View File

@ -1,10 +1,4 @@
import { import { CompilerOptions, parse, transform, ErrorCodes } from '../../src'
ElementNode,
CompilerOptions,
parse,
transform,
ErrorCodes
} from '../../src'
import { import {
RESOLVE_COMPONENT, RESOLVE_COMPONENT,
CREATE_VNODE, CREATE_VNODE,
@ -35,12 +29,14 @@ function parseWithElementTransform(
root: RootNode root: RootNode
node: CallExpression 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, { transform(ast, {
nodeTransforms: [optimizeText, transformElement], nodeTransforms: [optimizeText, transformElement],
...options ...options
}) })
const codegenNode = (ast.children[0] as ElementNode) const codegenNode = (ast as any).children[0].children[0]
.codegenNode as CallExpression .codegenNode as CallExpression
expect(codegenNode.type).toBe(NodeTypes.JS_CALL_EXPRESSION) expect(codegenNode.type).toBe(NodeTypes.JS_CALL_EXPRESSION)
return { return {

View File

@ -22,7 +22,8 @@ import {
CREATE_BLOCK, CREATE_BLOCK,
FRAGMENT, FRAGMENT,
RENDER_LIST, RENDER_LIST,
RENDER_SLOT RENDER_SLOT,
APPLY_DIRECTIVES
} from '../../src/runtimeHelpers' } from '../../src/runtimeHelpers'
import { PatchFlags } from '@vue/runtime-dom' import { PatchFlags } from '@vue/runtime-dom'
import { createObjectMatcher, genFlagText } from '../testUtils' import { createObjectMatcher, genFlagText } from '../testUtils'
@ -845,5 +846,28 @@ describe('compiler: v-for', () => {
}) })
expect(generate(root).code).toMatchSnapshot() 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()
})
}) })
}) })

View File

@ -114,19 +114,12 @@ export interface BaseElementNode extends Node {
export interface PlainElementNode extends BaseElementNode { export interface PlainElementNode extends BaseElementNode {
tagType: ElementTypes.ELEMENT tagType: ElementTypes.ELEMENT
codegenNode: codegenNode: ElementCodegenNode | undefined | SimpleExpressionNode // only when hoisted
| ElementCodegenNode
| CodegenNodeWithDirective<ElementCodegenNode>
| undefined
// | SimpleExpressionNode (only when hoisted)
} }
export interface ComponentNode extends BaseElementNode { export interface ComponentNode extends BaseElementNode {
tagType: ElementTypes.COMPONENT tagType: ElementTypes.COMPONENT
codegenNode: codegenNode: ComponentCodegenNode | undefined
| ComponentCodegenNode
| CodegenNodeWithDirective<ComponentCodegenNode>
| undefined
} }
export interface SlotOutletNode extends BaseElementNode { export interface SlotOutletNode extends BaseElementNode {
@ -280,8 +273,8 @@ export interface ConditionalExpression extends Node {
// Codegen Node Types ---------------------------------------------------------- // Codegen Node Types ----------------------------------------------------------
// createVNode(...) // createVNode(...)
export interface ElementCodegenNode extends CallExpression { export interface PlainElementCodegenNode extends CallExpression {
callee: typeof CREATE_VNODE callee: typeof CREATE_VNODE | typeof CREATE_BLOCK
arguments: // tag, props, children, patchFlag, dynamicProps arguments: // tag, props, children, patchFlag, dynamicProps
| [string | RuntimeHelper] | [string | RuntimeHelper]
| [string | RuntimeHelper, PropsExpression] | [string | RuntimeHelper, PropsExpression]
@ -301,13 +294,13 @@ export interface ElementCodegenNode extends CallExpression {
] ]
} }
export type ElementCodegenNodeWithDirective = CodegenNodeWithDirective< export type ElementCodegenNode =
ElementCodegenNode | PlainElementCodegenNode
> | CodegenNodeWithDirective<PlainElementCodegenNode>
// createVNode(...) // createVNode(...)
export interface ComponentCodegenNode extends CallExpression { export interface PlainComponentCodegenNode extends CallExpression {
callee: typeof CREATE_VNODE callee: typeof CREATE_VNODE | typeof CREATE_BLOCK
arguments: // Comp, props, slots, patchFlag, dynamicProps arguments: // Comp, props, slots, patchFlag, dynamicProps
| [string | RuntimeHelper] | [string | RuntimeHelper]
| [string | RuntimeHelper, PropsExpression] | [string | RuntimeHelper, PropsExpression]
@ -327,9 +320,9 @@ export interface ComponentCodegenNode extends CallExpression {
] ]
} }
export type CompoenntCodegenNodeWithDirective = CodegenNodeWithDirective< export type ComponentCodegenNode =
ComponentCodegenNode | PlainComponentCodegenNode
> | CodegenNodeWithDirective<PlainComponentCodegenNode>
export type SlotsExpression = SlotsObjectExpression | DynamicSlotsExpression 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 { export interface IfCodegenNode extends SequenceExpression {
expressions: [OpenBlockExpression, IfConditionalExpression] expressions: [OpenBlockExpression, IfConditionalExpression]
} }
@ -449,28 +447,6 @@ export interface OpenBlockExpression extends CallExpression {
arguments: [] 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 --------------------------------------------------------------- // AST Utilities ---------------------------------------------------------------
// Some expressions, e.g. sequence and conditional expressions, are never // 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 type InferCodegenNodeType<T> = T extends
? ElementCodegenNode | ComponentCodegenNode | typeof CREATE_VNODE
: T extends typeof CREATE_BLOCK | typeof CREATE_BLOCK
? BlockElementCodegenNode | BlockComponentCodegenNode ? PlainElementCodegenNode | PlainComponentCodegenNode
: T extends typeof APPLY_DIRECTIVES : T extends typeof APPLY_DIRECTIVES
? ElementCodegenNodeWithDirective | CompoenntCodegenNodeWithDirective ?
: T extends typeof RENDER_SLOT ? SlotOutletCodegenNode : CallExpression | CodegenNodeWithDirective<PlainElementCodegenNode>
| CodegenNodeWithDirective<PlainComponentCodegenNode>
: T extends typeof RENDER_SLOT ? SlotOutletCodegenNode : CallExpression
export function createCallExpression<T extends CallExpression['callee']>( export function createCallExpression<T extends CallExpression['callee']>(
callee: T, callee: T,

View File

@ -10,7 +10,10 @@ import {
createSimpleExpression, createSimpleExpression,
JSChildNode, JSChildNode,
SimpleExpressionNode, SimpleExpressionNode,
ElementTypes ElementTypes,
ElementCodegenNode,
ComponentCodegenNode,
createCallExpression
} from './ast' } from './ast'
import { isString, isArray } from '@vue/shared' import { isString, isArray } from '@vue/shared'
import { CompilerError, defaultOnError } from './errors' import { CompilerError, defaultOnError } from './errors'
@ -20,7 +23,9 @@ import {
CREATE_VNODE, CREATE_VNODE,
FRAGMENT, FRAGMENT,
RuntimeHelper, RuntimeHelper,
helperNameMap helperNameMap,
APPLY_DIRECTIVES,
CREATE_BLOCK
} from './runtimeHelpers' } from './runtimeHelpers'
import { isVSlot, createBlockExpression } from './utils' import { isVSlot, createBlockExpression } from './utils'
import { hoistStatic, isSingleElementRoot } from './transforms/hoistStatic' import { hoistStatic, isSingleElementRoot } from './transforms/hoistStatic'
@ -147,7 +152,7 @@ function createTransformContext(
} }
const list = context.parent!.children const list = context.parent!.children
const removalIndex = node const removalIndex = node
? list.indexOf(node as any) ? list.indexOf(node)
: context.currentNode : context.currentNode
? context.childIndex ? context.childIndex
: -1 : -1
@ -230,24 +235,38 @@ function finalizeRoot(root: RootNode, context: TransformContext) {
const { helper } = context const { helper } = context
const { children } = root const { children } = root
const child = children[0] const child = children[0]
if (isSingleElementRoot(root, child) && child.codegenNode) { if (children.length === 1) {
// turn root element into a block // if the single child is an element, turn it into a block.
root.codegenNode = createBlockExpression( if (isSingleElementRoot(root, child) && child.codegenNode) {
child.codegenNode.arguments, // single element root is never hoisted so codegenNode will never be
context // SimpleExpressionNode
) const codegenNode = child.codegenNode as
} else if (children.length === 1) { | ElementCodegenNode
// - single <slot/>, IfNode, ForNode: already blocks. | ComponentCodegenNode
// - single text node: always patched. if (codegenNode.callee === APPLY_DIRECTIVES) {
// - transform calls without transformElement (only during tests) codegenNode.arguments[0].callee = helper(CREATE_BLOCK)
// Just generate the node as-is } else {
root.codegenNode = child 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) { } else if (children.length > 1) {
// root has multiple nodes - return a fragment block. // root has multiple nodes - return a fragment block.
root.codegenNode = createBlockExpression( root.codegenNode = createBlockExpression(
[helper(FRAGMENT), `null`, root.children], createCallExpression(helper(CREATE_BLOCK), [
helper(FRAGMENT),
`null`,
root.children
]),
context context
) )
} else {
// no children = noop. codegen will return null.
} }
// finalize meta information // finalize meta information
root.helpers = [...context.helpers] root.helpers = [...context.helpers]

View File

@ -2,10 +2,8 @@ import {
RootNode, RootNode,
NodeTypes, NodeTypes,
TemplateChildNode, TemplateChildNode,
ElementNode,
ElementTypes, ElementTypes,
ElementCodegenNode, ElementCodegenNode,
ElementCodegenNodeWithDirective,
PlainElementNode, PlainElementNode,
ComponentNode, ComponentNode,
TemplateNode TemplateNode
@ -51,7 +49,7 @@ function walk(
) { ) {
if (!doNotHoistNode && isStaticNode(child, resultCache)) { if (!doNotHoistNode && isStaticNode(child, resultCache)) {
// whole tree is static // whole tree is static
;(child as any).codegenNode = context.hoist(child.codegenNode!) child.codegenNode = context.hoist(child.codegenNode!)
continue continue
} else { } else {
// node may contain dynamic children, but its props may be eligible for // node may contain dynamic children, but its props may be eligible for
@ -62,7 +60,7 @@ function walk(
flag === PatchFlags.NEED_PATCH || flag === PatchFlags.NEED_PATCH ||
flag === PatchFlags.TEXT flag === PatchFlags.TEXT
) { ) {
let codegenNode = child.codegenNode! let codegenNode = child.codegenNode as ElementCodegenNode
if (codegenNode.callee === APPLY_DIRECTIVES) { if (codegenNode.callee === APPLY_DIRECTIVES) {
codegenNode = codegenNode.arguments[0] codegenNode = codegenNode.arguments[0]
} }
@ -88,10 +86,8 @@ function walk(
} }
} }
function getPatchFlag(node: ElementNode): number | undefined { function getPatchFlag(node: PlainElementNode): number | undefined {
let codegenNode = node.codegenNode as let codegenNode = node.codegenNode as ElementCodegenNode
| ElementCodegenNode
| ElementCodegenNodeWithDirective
if (codegenNode.callee === APPLY_DIRECTIVES) { if (codegenNode.callee === APPLY_DIRECTIVES) {
codegenNode = codegenNode.arguments[0] codegenNode = codegenNode.arguments[0]
} }

View File

@ -15,7 +15,7 @@ import {
createObjectExpression, createObjectExpression,
createObjectProperty, createObjectProperty,
ForCodegenNode, ForCodegenNode,
PlainElementNode ElementCodegenNode
} from '../ast' } from '../ast'
import { createCompilerError, ErrorCodes } from '../errors' import { createCompilerError, ErrorCodes } from '../errors'
import { import {
@ -30,11 +30,11 @@ import {
RENDER_LIST, RENDER_LIST,
OPEN_BLOCK, OPEN_BLOCK,
CREATE_BLOCK, CREATE_BLOCK,
FRAGMENT FRAGMENT,
APPLY_DIRECTIVES
} from '../runtimeHelpers' } from '../runtimeHelpers'
import { processExpression } from './transformExpression' import { processExpression } from './transformExpression'
import { PatchFlags, PatchFlagNames } from '@vue/shared' import { PatchFlags, PatchFlagNames } from '@vue/shared'
import { PropsExpression } from './transformElement'
export const transformFor = createStructuralDirectiveTransform( export const transformFor = createStructuralDirectiveTransform(
'for', 'for',
@ -124,34 +124,29 @@ export const transformFor = createStructuralDirectiveTransform(
// <template v-for="..." :key="..."><slot/></template> // <template v-for="..." :key="..."><slot/></template>
// we need to inject the key to the renderSlot() call. // we need to inject the key to the renderSlot() call.
// the props for renderSlot is passed as the 3rd argument. // the props for renderSlot is passed as the 3rd argument.
const existingProps = childBlock.arguments[2] as injectProp(childBlock, keyProperty, context)
| PropsExpression
| undefined
| 'null'
childBlock.arguments[2] = injectProp(
existingProps,
keyProperty,
context
)
} }
} else if (isTemplate) { } else if (isTemplate) {
// <template v-for="..."> // <template v-for="...">
// should generate a fragment block for each loop // should generate a fragment block for each loop
childBlock = createBlockExpression( childBlock = createBlockExpression(
[ createCallExpression(helper(CREATE_BLOCK), [
helper(FRAGMENT), helper(FRAGMENT),
keyProperty ? createObjectExpression([keyProperty]) : `null`, keyProperty ? createObjectExpression([keyProperty]) : `null`,
node.children node.children
], ]),
context context
) )
} else { } else {
// Normal element v-for. Directly use the child's codegenNode // Normal element v-for. Directly use the child's codegenNode
// arguments, but replace createVNode() with createBlock() // arguments, but replace createVNode() with createBlock()
childBlock = createBlockExpression( let codegenNode = node.codegenNode as ElementCodegenNode
(node as PlainElementNode).codegenNode!.arguments, if (codegenNode.callee === APPLY_DIRECTIVES) {
context codegenNode.arguments[0].callee = helper(CREATE_BLOCK)
) } else {
codegenNode.callee = helper(CREATE_BLOCK)
}
childBlock = createBlockExpression(codegenNode, context)
} }
renderExp.arguments.push( renderExp.arguments.push(

View File

@ -23,9 +23,7 @@ import {
BlockCodegenNode, BlockCodegenNode,
SlotOutletCodegenNode, SlotOutletCodegenNode,
ElementCodegenNode, ElementCodegenNode,
ComponentCodegenNode, ComponentCodegenNode
ElementCodegenNodeWithDirective,
CompoenntCodegenNodeWithDirective
} from '../ast' } from '../ast'
import { createCompilerError, ErrorCodes } from '../errors' import { createCompilerError, ErrorCodes } from '../errors'
import { processExpression } from './transformExpression' import { processExpression } from './transformExpression'
@ -35,8 +33,7 @@ import {
EMPTY, EMPTY,
FRAGMENT, FRAGMENT,
APPLY_DIRECTIVES, APPLY_DIRECTIVES,
CREATE_VNODE, CREATE_VNODE
RENDER_SLOT
} from '../runtimeHelpers' } from '../runtimeHelpers'
import { injectProp } from '../utils' import { injectProp } from '../utils'
@ -196,8 +193,6 @@ function createChildrenCodegenNode(
const childCodegen = (child as ElementNode).codegenNode as const childCodegen = (child as ElementNode).codegenNode as
| ElementCodegenNode | ElementCodegenNode
| ComponentCodegenNode | ComponentCodegenNode
| ElementCodegenNodeWithDirective
| CompoenntCodegenNodeWithDirective
| SlotOutletCodegenNode | SlotOutletCodegenNode
let vnodeCall = childCodegen let vnodeCall = childCodegen
// Element with custom directives. Locate the actual createVNode() call. // Element with custom directives. Locate the actual createVNode() call.
@ -206,22 +201,10 @@ function createChildrenCodegenNode(
} }
// Change createVNode to createBlock. // Change createVNode to createBlock.
if (vnodeCall.callee === CREATE_VNODE) { 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 // inject branch key
const existingProps = vnodeCall.arguments[ injectProp(vnodeCall, keyProperty, context)
propsIndex
] as ElementCodegenNode['arguments'][1]
vnodeCall.arguments[propsIndex] = injectProp(
existingProps,
keyProperty,
context
)
return childCodegen return childCodegen
} }
} }

View File

@ -16,14 +16,17 @@ import {
JSChildNode, JSChildNode,
createObjectExpression, createObjectExpression,
SlotOutletNode, SlotOutletNode,
TemplateNode TemplateNode,
BlockCodegenNode,
ElementCodegenNode,
SlotOutletCodegenNode,
ComponentCodegenNode
} from './ast' } from './ast'
import { parse } from 'acorn' import { parse } from 'acorn'
import { walk } from 'estree-walker' import { walk } from 'estree-walker'
import { TransformContext } from './transform' 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 { isString, isFunction } from '@vue/shared'
import { PropsExpression } from './transforms/transformElement'
// cache node requires // cache node requires
// lazy require dependencies so that they don't end up in rollup's dep graph // 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( export function createBlockExpression(
args: CallExpression['arguments'], blockExp: BlockCodegenNode,
context: TransformContext context: TransformContext
): SequenceExpression { ): SequenceExpression {
return createSequenceExpression([ return createSequenceExpression([
createCallExpression(context.helper(OPEN_BLOCK)), 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 node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.SLOT
export function injectProp( export function injectProp(
props: PropsExpression | undefined | 'null', node: ElementCodegenNode | ComponentCodegenNode | SlotOutletCodegenNode,
prop: Property, prop: Property,
context: TransformContext context: TransformContext
): ObjectExpression | CallExpression { ) {
if (props == null || props === `null`) { let propsWithInjection: ObjectExpression | CallExpression
return createObjectExpression([prop]) 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) { } else if (props.type === NodeTypes.JS_CALL_EXPRESSION) {
// merged props... add ours // merged props... add ours
// only inject key to object literal if it's the first argument so that // only inject key to object literal if it's the first argument so that
@ -207,17 +213,22 @@ export function injectProp(
} else { } else {
props.arguments.unshift(createObjectExpression([prop])) props.arguments.unshift(createObjectExpression([prop]))
} }
return props propsWithInjection = props
} else if (props.type === NodeTypes.JS_OBJECT_EXPRESSION) { } else if (props.type === NodeTypes.JS_OBJECT_EXPRESSION) {
props.properties.unshift(prop) props.properties.unshift(prop)
return props propsWithInjection = props
} else { } else {
// single v-bind with expression, return a merged replacement // single v-bind with expression, return a merged replacement
return createCallExpression(context.helper(MERGE_PROPS), [ propsWithInjection = createCallExpression(context.helper(MERGE_PROPS), [
createObjectExpression([prop]), createObjectExpression([prop]),
props props
]) ])
} }
if (node.callee === RENDER_SLOT) {
node.arguments[2] = propsWithInjection
} else {
node.arguments[1] = propsWithInjection
}
} }
export function toValidAssetId( export function toValidAssetId(