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: [],
directives: [],
hoists: [],
codegenNode: undefined,
codegenNode: createSimpleExpression(`null`, false),
loc: locStub,
...options
}

View 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,

View 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({

View 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\\"))
}

View 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\\"),

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`] = `
"const _Vue = Vue

View 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

View 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 {

View 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()
})
})
})

View 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,

View 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]

View 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]
}

View 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(

View 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
}
}

View 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(