fix(compiler): handle block nodes with custom directives + improve ast types
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user