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

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