refactor(compiler-core): use dedicated node type for element codegen

Previously codegen node for elements and components used raw expressions,
which leads to multiple permutations of AST shapes based on whether the
node is a block or has directives. The complexity is spread across the
entire compiler and occurs whenever a transform needs to deal with
element codegen nodes.

This refactor centralizes the handling of all possible permutations
into the codegen phase, so that all elements/components will have a
consistent node type throughout the transform phase.

The refactor is split into two commits (with test updates in a separate
one) so changes can be easier to inspect.
This commit is contained in:
Evan You 2020-02-11 18:12:56 -05:00
parent fe9da2d0e4
commit e3988b40d8
10 changed files with 340 additions and 410 deletions

View File

@ -1,17 +1,17 @@
import { isString } from '@vue/shared'
import { ForParseResult } from './transforms/vFor'
import {
CREATE_VNODE,
WITH_DIRECTIVES,
RENDER_SLOT,
CREATE_SLOTS,
RENDER_LIST,
OPEN_BLOCK,
CREATE_BLOCK,
FRAGMENT
FRAGMENT,
CREATE_VNODE,
WITH_DIRECTIVES
} from './runtimeHelpers'
import { PropsExpression } from './transforms/transformElement'
import { ImportItem } from './transform'
import { ImportItem, TransformContext } from './transform'
// Vue template is a platform-agnostic superset of HTML (syntax only).
// More namespaces like SVG and MathML are declared by platform specific
@ -38,12 +38,12 @@ export const enum NodeTypes {
FOR,
TEXT_CALL,
// codegen
VNODE_CALL,
JS_CALL_EXPRESSION,
JS_OBJECT_EXPRESSION,
JS_PROPERTY,
JS_ARRAY_EXPRESSION,
JS_FUNCTION_EXPRESSION,
JS_SEQUENCE_EXPRESSION,
JS_CONDITIONAL_EXPRESSION,
JS_CACHE_EXPRESSION,
@ -123,21 +123,14 @@ export interface BaseElementNode extends Node {
isSelfClosing: boolean
props: Array<AttributeNode | DirectiveNode>
children: TemplateChildNode[]
codegenNode:
| CallExpression
| SimpleExpressionNode
| CacheExpression
| SequenceExpression
| undefined
}
export interface PlainElementNode extends BaseElementNode {
tagType: ElementTypes.ELEMENT
codegenNode:
| ElementCodegenNode
| VNodeCall
| SimpleExpressionNode // when hoisted
| CacheExpression // when cached by v-once
| SequenceExpression // when turned into a block
| undefined
ssrCodegenNode?: TemplateLiteral
}
@ -145,7 +138,7 @@ export interface PlainElementNode extends BaseElementNode {
export interface ComponentNode extends BaseElementNode {
tagType: ElementTypes.COMPONENT
codegenNode:
| ComponentCodegenNode
| VNodeCall
| CacheExpression // when cached by v-once
| undefined
ssrCodegenNode?: CallExpression
@ -153,13 +146,17 @@ export interface ComponentNode extends BaseElementNode {
export interface SlotOutletNode extends BaseElementNode {
tagType: ElementTypes.SLOT
codegenNode: SlotOutletCodegenNode | undefined | CacheExpression // when cached by v-once
codegenNode:
| RenderSlotCall
| CacheExpression // when cached by v-once
| undefined
ssrCodegenNode?: CallExpression
}
export interface TemplateNode extends BaseElementNode {
tagType: ElementTypes.TEMPLATE
// TemplateNode is a container type that always gets compiled away
codegenNode: undefined
}
export interface TextNode extends Node {
@ -220,7 +217,7 @@ export interface CompoundExpressionNode extends Node {
export interface IfNode extends Node {
type: NodeTypes.IF
branches: IfBranchNode[]
codegenNode?: IfCodegenNode
codegenNode?: IfConditionalExpression
}
export interface IfBranchNode extends Node {
@ -246,6 +243,28 @@ export interface TextCallNode extends Node {
codegenNode: CallExpression | SimpleExpressionNode // when hoisted
}
export type TemplateTextChildNode =
| TextNode
| InterpolationNode
| CompoundExpressionNode
export interface VNodeCall extends Node {
type: NodeTypes.VNODE_CALL
tag: string | symbol | CallExpression
props: PropsExpression | undefined
children:
| TemplateChildNode[] // multiple children
| TemplateTextChildNode // single text child
| SlotsExpression // component slots
| ForRenderListExpression // v-for fragment call
| undefined
patchFlag: string | undefined
dynamicProps: string | undefined
directives: DirectiveArguments | undefined
isBlock: boolean
isForBlock: boolean
}
// JS Node Types ---------------------------------------------------------------
// We also include a number of JavaScript AST nodes for code generation.
@ -253,13 +272,13 @@ export interface TextCallNode extends Node {
// Vue render function generation.
export type JSChildNode =
| VNodeCall
| CallExpression
| ObjectExpression
| ArrayExpression
| ExpressionNode
| FunctionExpression
| ConditionalExpression
| SequenceExpression
| CacheExpression
| AssignmentExpression
@ -301,11 +320,6 @@ export interface FunctionExpression extends Node {
isSlot: boolean
}
export interface SequenceExpression extends Node {
type: NodeTypes.JS_SEQUENCE_EXPRESSION
expressions: JSChildNode[]
}
export interface ConditionalExpression extends Node {
type: NodeTypes.JS_CONDITIONAL_EXPRESSION
test: JSChildNode
@ -360,57 +374,31 @@ export interface ReturnStatement extends Node {
// Codegen Node Types ----------------------------------------------------------
// createVNode(...)
export interface PlainElementCodegenNode extends CallExpression {
callee: typeof CREATE_VNODE | typeof CREATE_BLOCK
arguments: // tag, props, children, patchFlag, dynamicProps
| [string | symbol]
| [string | symbol, PropsExpression]
| [string | symbol, 'null' | PropsExpression, TemplateChildNode[]]
| [
string | symbol,
'null' | PropsExpression,
'null' | TemplateChildNode[],
string
]
| [
string | symbol,
'null' | PropsExpression,
'null' | TemplateChildNode[],
string,
string
]
export interface DirectiveArguments extends ArrayExpression {
elements: DirectiveArgumentNode[]
}
export type ElementCodegenNode =
| PlainElementCodegenNode
| CodegenNodeWithDirective<PlainElementCodegenNode>
// createVNode(...)
export interface PlainComponentCodegenNode extends CallExpression {
callee: typeof CREATE_VNODE | typeof CREATE_BLOCK
arguments: // Comp, props, slots, patchFlag, dynamicProps
| [string | symbol]
| [string | symbol, PropsExpression]
| [string | symbol, 'null' | PropsExpression, SlotsExpression]
| [
string | symbol,
'null' | PropsExpression,
'null' | SlotsExpression,
string
]
| [
string | symbol,
'null' | PropsExpression,
'null' | SlotsExpression,
string,
string
]
export interface DirectiveArgumentNode extends ArrayExpression {
elements: // dir, exp, arg, modifiers
| [string]
| [string, ExpressionNode]
| [string, ExpressionNode, ExpressionNode]
| [string, ExpressionNode, ExpressionNode, ObjectExpression]
}
export type ComponentCodegenNode =
| PlainComponentCodegenNode
| CodegenNodeWithDirective<PlainComponentCodegenNode>
// renderSlot(...)
export interface RenderSlotCall extends CallExpression {
callee: typeof RENDER_SLOT
arguments: // $slots, name, props, fallback
| [string, string | ExpressionNode]
| [string, string | ExpressionNode, PropsExpression]
| [
string,
string | ExpressionNode,
PropsExpression | '{}',
TemplateChildNode[]
]
}
export type SlotsExpression = SlotsObjectExpression | DynamicSlotsExpression
@ -462,63 +450,20 @@ export interface DynamicSlotFnProperty extends Property {
value: SlotFunctionExpression
}
// withDirectives(createVNode(...), [
// [_directive_foo, someValue],
// [_directive_bar, someValue, "arg", { mod: true }]
// ])
export interface CodegenNodeWithDirective<T extends CallExpression>
extends CallExpression {
callee: typeof WITH_DIRECTIVES
arguments: [T, DirectiveArguments]
}
export interface DirectiveArguments extends ArrayExpression {
elements: DirectiveArgumentNode[]
}
export interface DirectiveArgumentNode extends ArrayExpression {
elements: // dir, exp, arg, modifiers
| [string]
| [string, ExpressionNode]
| [string, ExpressionNode, ExpressionNode]
| [string, ExpressionNode, ExpressionNode, ObjectExpression]
}
// renderSlot(...)
export interface SlotOutletCodegenNode extends CallExpression {
callee: typeof RENDER_SLOT
arguments: // $slots, name, props, fallback
| [string, string | ExpressionNode]
| [string, string | ExpressionNode, PropsExpression]
| [
string,
string | ExpressionNode,
PropsExpression | '{}',
TemplateChildNode[]
]
}
export type BlockCodegenNode =
| ElementCodegenNode
| ComponentCodegenNode
| SlotOutletCodegenNode
export interface IfCodegenNode extends SequenceExpression {
expressions: [OpenBlockExpression, IfConditionalExpression]
}
export type BlockCodegenNode = VNodeCall | RenderSlotCall
export interface IfConditionalExpression extends ConditionalExpression {
consequent: BlockCodegenNode
alternate: BlockCodegenNode | IfConditionalExpression
}
export interface ForCodegenNode extends SequenceExpression {
expressions: [OpenBlockExpression, ForBlockCodegenNode]
}
export interface ForBlockCodegenNode extends CallExpression {
callee: typeof CREATE_BLOCK
arguments: [typeof FRAGMENT, 'null', ForRenderListExpression, string]
export interface ForCodegenNode extends VNodeCall {
isBlock: true
tag: typeof FRAGMENT
props: undefined
children: ForRenderListExpression
patchFlag: string
isForBlock: true
}
export interface ForRenderListExpression extends CallExpression {
@ -530,11 +475,6 @@ export interface ForIteratorExpression extends FunctionExpression {
returns: BlockCodegenNode
}
export interface OpenBlockExpression extends CallExpression {
callee: typeof OPEN_BLOCK
arguments: []
}
// AST Utilities ---------------------------------------------------------------
// Some expressions, e.g. sequence and conditional expressions, are never
@ -565,6 +505,42 @@ export function createRoot(
}
}
export function createVNodeCall(
context: TransformContext,
tag: VNodeCall['tag'],
props?: VNodeCall['props'],
children?: VNodeCall['children'],
patchFlag?: VNodeCall['patchFlag'],
dynamicProps?: VNodeCall['dynamicProps'],
directives?: VNodeCall['directives'],
isBlock: VNodeCall['isBlock'] = false,
isForBlock: VNodeCall['isForBlock'] = false,
loc = locStub
): VNodeCall {
if (isBlock) {
context.helper(OPEN_BLOCK)
context.helper(CREATE_BLOCK)
} else {
context.helper(CREATE_VNODE)
}
if (directives) {
context.helper(WITH_DIRECTIVES)
}
return {
type: NodeTypes.VNODE_CALL,
tag,
props,
children,
patchFlag,
dynamicProps,
directives,
isBlock,
isForBlock,
loc
}
}
export function createArrayExpression(
elements: ArrayExpression['elements'],
loc: SourceLocation = locStub
@ -638,15 +614,9 @@ export function createCompoundExpression(
}
}
type InferCodegenNodeType<T> = T extends
| typeof CREATE_VNODE
| typeof CREATE_BLOCK
? PlainElementCodegenNode | PlainComponentCodegenNode
: T extends typeof WITH_DIRECTIVES
?
| CodegenNodeWithDirective<PlainElementCodegenNode>
| CodegenNodeWithDirective<PlainComponentCodegenNode>
: T extends typeof RENDER_SLOT ? SlotOutletCodegenNode : CallExpression
type InferCodegenNodeType<T> = T extends typeof RENDER_SLOT
? RenderSlotCall
: CallExpression
export function createCallExpression<T extends CallExpression['callee']>(
callee: T,
@ -678,16 +648,6 @@ export function createFunctionExpression(
}
}
export function createSequenceExpression(
expressions: SequenceExpression['expressions']
): SequenceExpression {
return {
type: NodeTypes.JS_SEQUENCE_EXPRESSION,
expressions,
loc: locStub
}
}
export function createConditionalExpression(
test: ConditionalExpression['test'],
consequent: ConditionalExpression['consequent'],

View File

@ -15,7 +15,6 @@ import {
CompoundExpressionNode,
SimpleExpressionNode,
FunctionExpression,
SequenceExpression,
ConditionalExpression,
CacheExpression,
locStub,
@ -23,7 +22,8 @@ import {
TemplateLiteral,
IfStatement,
AssignmentExpression,
ReturnStatement
ReturnStatement,
VNodeCall
} from './ast'
import { SourceMapGenerator, RawSourceMap } from 'source-map'
import {
@ -45,7 +45,10 @@ import {
CREATE_TEXT,
PUSH_SCOPE_ID,
POP_SCOPE_ID,
WITH_SCOPE_ID
WITH_SCOPE_ID,
WITH_DIRECTIVES,
CREATE_BLOCK,
OPEN_BLOCK
} from './runtimeHelpers'
import { ImportItem } from './transform'
@ -547,6 +550,10 @@ function genNode(node: CodegenNode | symbol | string, context: CodegenContext) {
case NodeTypes.COMMENT:
genComment(node, context)
break
case NodeTypes.VNODE_CALL:
genVNodeCall(node, context)
break
case NodeTypes.JS_CALL_EXPRESSION:
genCallExpression(node, context)
break
@ -559,9 +566,6 @@ function genNode(node: CodegenNode | symbol | string, context: CodegenContext) {
case NodeTypes.JS_FUNCTION_EXPRESSION:
genFunctionExpression(node, context)
break
case NodeTypes.JS_SEQUENCE_EXPRESSION:
genSequenceExpression(node, context)
break
case NodeTypes.JS_CONDITIONAL_EXPRESSION:
genConditionalExpression(node, context)
break
@ -657,6 +661,48 @@ function genComment(node: CommentNode, context: CodegenContext) {
}
}
function genVNodeCall(node: VNodeCall, context: CodegenContext) {
const { push, helper } = context
const {
tag,
props,
children,
patchFlag,
dynamicProps,
directives,
isBlock,
isForBlock
} = node
if (directives) {
push(helper(WITH_DIRECTIVES) + `(`)
}
if (isBlock) {
push(`(${helper(OPEN_BLOCK)}(${isForBlock ? `true` : ``}), `)
}
push(helper(isBlock ? CREATE_BLOCK : CREATE_VNODE) + `(`, node)
genNodeList(
genNullableArgs([tag, props, children, patchFlag, dynamicProps]),
context
)
push(`)`)
if (isBlock) {
push(`)`)
}
if (directives) {
push(`, `)
genNode(directives, context)
push(`)`)
}
}
function genNullableArgs(args: any[]): CallExpression['arguments'] {
let i = args.length
while (i--) {
if (args[i] != null) break
}
return args.slice(0, i + 1).map(arg => arg || `null`)
}
// JavaScript
function genCallExpression(node: CallExpression, context: CodegenContext) {
const callee = isString(node.callee)
@ -782,15 +828,6 @@ function genConditionalExpression(
needNewline && deindent(true /* without newline */)
}
function genSequenceExpression(
node: SequenceExpression,
context: CodegenContext
) {
context.push(`(`)
genNodeList(node.expressions, context)
context.push(`)`)
}
function genCacheExpression(node: CacheExpression, context: CodegenContext) {
const { push, helper, indent, deindent, newline } = context
push(`_cache[${node.index}] || (`)

View File

@ -12,12 +12,10 @@ import {
JSChildNode,
SimpleExpressionNode,
ElementTypes,
ElementCodegenNode,
ComponentCodegenNode,
createCallExpression,
CacheExpression,
createCacheExpression,
TemplateLiteral
TemplateLiteral,
createVNodeCall
} from './ast'
import {
isString,
@ -31,11 +29,11 @@ import {
TO_DISPLAY_STRING,
FRAGMENT,
helperNameMap,
WITH_DIRECTIVES,
CREATE_BLOCK,
CREATE_COMMENT
CREATE_COMMENT,
OPEN_BLOCK
} from './runtimeHelpers'
import { isVSlot, createBlockExpression } from './utils'
import { isVSlot } from './utils'
import { hoistStatic, isSingleElementRoot } from './transforms/hoistStatic'
// There are two types of transforms:
@ -286,20 +284,13 @@ function createRootCodegen(root: RootNode, context: TransformContext) {
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
| CacheExpression
if (codegenNode.type !== NodeTypes.JS_CACHE_EXPRESSION) {
if (codegenNode.callee === WITH_DIRECTIVES) {
codegenNode.arguments[0].callee = helper(CREATE_BLOCK)
} else {
codegenNode.callee = helper(CREATE_BLOCK)
}
root.codegenNode = createBlockExpression(codegenNode, context)
} else {
root.codegenNode = codegenNode
const codegenNode = child.codegenNode
if (codegenNode.type === NodeTypes.VNODE_CALL) {
codegenNode.isBlock = true
helper(OPEN_BLOCK)
helper(CREATE_BLOCK)
}
root.codegenNode = codegenNode
} else {
// - single <slot/>, IfNode, ForNode: already blocks.
// - single text node: always patched.
@ -308,16 +299,17 @@ function createRootCodegen(root: RootNode, context: TransformContext) {
}
} else if (children.length > 1) {
// root has multiple nodes - return a fragment block.
root.codegenNode = createBlockExpression(
createCallExpression(helper(CREATE_BLOCK), [
helper(FRAGMENT),
`null`,
root.children,
`${PatchFlags.STABLE_FRAGMENT} /* ${
PatchFlagNames[PatchFlags.STABLE_FRAGMENT]
} */`
]),
context
root.codegenNode = createVNodeCall(
context,
helper(FRAGMENT),
undefined,
root.children,
`${PatchFlags.STABLE_FRAGMENT} /* ${
PatchFlagNames[PatchFlags.STABLE_FRAGMENT]
} */`,
undefined,
undefined,
true
)
} else {
// no children = noop. codegen will return null.

View File

@ -8,11 +8,9 @@ import {
ComponentNode,
TemplateNode,
ElementNode,
PlainElementCodegenNode,
CodegenNodeWithDirective
VNodeCall
} from '../ast'
import { TransformContext } from '../transform'
import { WITH_DIRECTIVES } from '../runtimeHelpers'
import { PatchFlags, isString, isSymbol } from '@vue/shared'
import { isSlotOutlet, findProp } from '../utils'
@ -60,7 +58,7 @@ function walk(
// node may contain dynamic children, but its props may be eligible for
// hoisting.
const codegenNode = child.codegenNode!
if (codegenNode.type === NodeTypes.JS_CALL_EXPRESSION) {
if (codegenNode.type === NodeTypes.VNODE_CALL) {
const flag = getPatchFlag(codegenNode)
if (
(!flag ||
@ -70,8 +68,8 @@ function walk(
!hasCachedProps(child)
) {
const props = getNodeProps(child)
if (props && props !== `null`) {
getVNodeCall(codegenNode).arguments[1] = context.hoist(props)
if (props) {
codegenNode.props = context.hoist(props)
}
}
}
@ -111,7 +109,7 @@ export function isStaticNode(
return cached
}
const codegenNode = node.codegenNode!
if (codegenNode.type !== NodeTypes.JS_CALL_EXPRESSION) {
if (codegenNode.type !== NodeTypes.VNODE_CALL) {
return false
}
const flag = getPatchFlag(codegenNode)
@ -123,6 +121,12 @@ export function isStaticNode(
return false
}
}
// only svg/foeignObject could be block here, however if they are static
// then they don't need to be blocks since there will be no nested
// udpates.
if (codegenNode.isBlock) {
codegenNode.isBlock = false
}
resultCache.set(node, true)
return true
} else {
@ -164,11 +168,7 @@ function hasCachedProps(node: PlainElementNode): boolean {
return false
}
const props = getNodeProps(node)
if (
props &&
props !== 'null' &&
props.type === NodeTypes.JS_OBJECT_EXPRESSION
) {
if (props && props.type === NodeTypes.JS_OBJECT_EXPRESSION) {
const { properties } = props
for (let i = 0; i < properties.length; i++) {
if (properties[i].value.type === NodeTypes.JS_CACHE_EXPRESSION) {
@ -181,30 +181,12 @@ function hasCachedProps(node: PlainElementNode): boolean {
function getNodeProps(node: PlainElementNode) {
const codegenNode = node.codegenNode!
if (codegenNode.type === NodeTypes.JS_CALL_EXPRESSION) {
return getVNodeArgAt(
codegenNode,
1
) as PlainElementCodegenNode['arguments'][1]
if (codegenNode.type === NodeTypes.VNODE_CALL) {
return codegenNode.props
}
}
type NonCachedCodegenNode =
| PlainElementCodegenNode
| CodegenNodeWithDirective<PlainElementCodegenNode>
function getVNodeArgAt(
node: NonCachedCodegenNode,
index: number
): PlainElementCodegenNode['arguments'][number] {
return getVNodeCall(node).arguments[index]
}
function getVNodeCall(node: NonCachedCodegenNode) {
return node.callee === WITH_DIRECTIVES ? node.arguments[0] : node
}
function getPatchFlag(node: NonCachedCodegenNode): number | undefined {
const flag = getVNodeArgAt(node, 3) as string
function getPatchFlag(node: VNodeCall): number | undefined {
const flag = node.patchFlag
return flag ? parseInt(flag, 10) : undefined
}

View File

@ -14,23 +14,22 @@ import {
createSimpleExpression,
createObjectExpression,
Property,
createSequenceExpression,
ComponentNode
ComponentNode,
VNodeCall,
TemplateTextChildNode,
DirectiveArguments,
createVNodeCall
} from '../ast'
import { PatchFlags, PatchFlagNames, isSymbol } from '@vue/shared'
import { createCompilerError, ErrorCodes } from '../errors'
import {
CREATE_VNODE,
WITH_DIRECTIVES,
RESOLVE_DIRECTIVE,
RESOLVE_COMPONENT,
RESOLVE_DYNAMIC_COMPONENT,
MERGE_PROPS,
TO_HANDLERS,
PORTAL,
KEEP_ALIVE,
OPEN_BLOCK,
CREATE_BLOCK
KEEP_ALIVE
} from '../runtimeHelpers'
import {
getInnerRange,
@ -63,6 +62,20 @@ export const transformElement: NodeTransform = (node, context) => {
const { tag, props } = node
const isComponent = node.tagType === ElementTypes.COMPONENT
// The goal of the transform is to create a codegenNode implementing the
// VNodeCall interface.
const vnodeTag = isComponent
? resolveComponentType(node as ComponentNode, context)
: `"${tag}"`
let vnodeProps: VNodeCall['props']
let vnodeChildren: VNodeCall['children']
let vnodePatchFlag: VNodeCall['patchFlag']
let patchFlag: number = 0
let vnodeDynamicProps: VNodeCall['dynamicProps']
let dynamicPropNames: string[] | undefined
let vnodeDirectives: VNodeCall['directives']
// <svg> and <foreignObject> must be forced into blocks so that block
// updates inside get proper isSVG flag at runtime. (#639, #643)
// This is technically web-specific, but splitting the logic out of core
@ -70,38 +83,24 @@ export const transformElement: NodeTransform = (node, context) => {
let shouldUseBlock =
!isComponent && (tag === 'svg' || tag === 'foreignObject')
const nodeType = isComponent
? resolveComponentType(node as ComponentNode, context)
: `"${tag}"`
const args: CallExpression['arguments'] = [nodeType]
let hasProps = props.length > 0
let patchFlag: number = 0
let runtimeDirectives: DirectiveNode[] | undefined
let dynamicPropNames: string[] | undefined
// props
if (hasProps) {
if (props.length > 0) {
const propsBuildResult = buildProps(node, context)
vnodeProps = propsBuildResult.props
patchFlag = propsBuildResult.patchFlag
dynamicPropNames = propsBuildResult.dynamicPropNames
runtimeDirectives = propsBuildResult.directives
if (!propsBuildResult.props) {
hasProps = false
} else {
args.push(propsBuildResult.props)
}
const directives = propsBuildResult.directives
vnodeDirectives =
directives && directives.length
? (createArrayExpression(
directives.map(dir => buildDirectiveArgs(dir, context))
) as DirectiveArguments)
: undefined
}
// children
const hasChildren = node.children.length > 0
if (hasChildren) {
if (!hasProps) {
args.push(`null`)
}
if (nodeType === KEEP_ALIVE) {
if (node.children.length > 0) {
if (vnodeTag === KEEP_ALIVE) {
// Although a built-in component, we compile KeepAlive with raw children
// instead of slot functions so that it can be used inside Transition
// or other Transition-wrapping HOCs.
@ -125,13 +124,13 @@ export const transformElement: NodeTransform = (node, context) => {
const shouldBuildAsSlots =
isComponent &&
// Portal is not a real component has dedicated handling in the renderer
nodeType !== PORTAL &&
vnodeTag !== PORTAL &&
// explained above.
nodeType !== KEEP_ALIVE
vnodeTag !== KEEP_ALIVE
if (shouldBuildAsSlots) {
const { slots, hasDynamicSlots } = buildSlots(node, context)
args.push(slots)
vnodeChildren = slots
if (hasDynamicSlots) {
patchFlag |= PatchFlags.DYNAMIC_SLOTS
}
@ -148,60 +147,44 @@ export const transformElement: NodeTransform = (node, context) => {
// pass directly if the only child is a text node
// (plain / interpolation / expression)
if (hasDynamicTextChild || type === NodeTypes.TEXT) {
args.push(child)
vnodeChildren = child as TemplateTextChildNode
} else {
args.push(node.children)
vnodeChildren = node.children
}
} else {
args.push(node.children)
vnodeChildren = node.children
}
}
// patchFlag & dynamicPropNames
if (patchFlag !== 0) {
if (!hasChildren) {
if (!hasProps) {
args.push(`null`)
}
args.push(`null`)
}
if (__DEV__) {
const flagNames = Object.keys(PatchFlagNames)
.map(Number)
.filter(n => n > 0 && patchFlag & n)
.map(n => PatchFlagNames[n])
.join(`, `)
args.push(patchFlag + ` /* ${flagNames} */`)
vnodePatchFlag = patchFlag + ` /* ${flagNames} */`
} else {
args.push(patchFlag + '')
vnodePatchFlag = String(patchFlag)
}
if (dynamicPropNames && dynamicPropNames.length) {
args.push(stringifyDynamicPropNames(dynamicPropNames))
vnodeDynamicProps = stringifyDynamicPropNames(dynamicPropNames)
}
}
const { loc } = node
const vnode = shouldUseBlock
? createSequenceExpression([
createCallExpression(context.helper(OPEN_BLOCK)),
createCallExpression(context.helper(CREATE_BLOCK), args, loc)
])
: createCallExpression(context.helper(CREATE_VNODE), args, loc)
if (runtimeDirectives && runtimeDirectives.length) {
node.codegenNode = createCallExpression(
context.helper(WITH_DIRECTIVES),
[
vnode,
createArrayExpression(
runtimeDirectives.map(dir => buildDirectiveArgs(dir, context)),
loc
)
],
loc
)
} else {
node.codegenNode = vnode
}
node.codegenNode = createVNodeCall(
context,
vnodeTag,
vnodeProps,
vnodeChildren,
vnodePatchFlag,
vnodeDynamicProps,
vnodeDirectives,
shouldUseBlock,
false /* isForBlock */,
node.loc
)
}
}

View File

@ -8,26 +8,28 @@ import {
createSimpleExpression,
SourceLocation,
SimpleExpressionNode,
createSequenceExpression,
createCallExpression,
createFunctionExpression,
ElementTypes,
createObjectExpression,
createObjectProperty,
ForCodegenNode,
ElementCodegenNode,
SlotOutletCodegenNode,
RenderSlotCall,
SlotOutletNode,
ElementNode,
DirectiveNode,
ForNode,
PlainElementNode
PlainElementNode,
createVNodeCall,
VNodeCall,
ForRenderListExpression,
BlockCodegenNode,
ForIteratorExpression
} from '../ast'
import { createCompilerError, ErrorCodes } from '../errors'
import {
getInnerRange,
findProp,
createBlockExpression,
isTemplateNode,
isSlotOutlet,
injectProp
@ -36,8 +38,7 @@ import {
RENDER_LIST,
OPEN_BLOCK,
CREATE_BLOCK,
FRAGMENT,
WITH_DIRECTIVES
FRAGMENT
} from '../runtimeHelpers'
import { processExpression } from './transformExpression'
import { PatchFlags, PatchFlagNames } from '@vue/shared'
@ -51,26 +52,27 @@ export const transformFor = createStructuralDirectiveTransform(
// iterator on exit after all children have been traversed
const renderExp = createCallExpression(helper(RENDER_LIST), [
forNode.source
])
]) as ForRenderListExpression
const keyProp = findProp(node, `key`)
const fragmentFlag = keyProp
? PatchFlags.KEYED_FRAGMENT
: PatchFlags.UNKEYED_FRAGMENT
forNode.codegenNode = createSequenceExpression([
// v-for fragment blocks disable tracking since they always diff their
// children
createCallExpression(helper(OPEN_BLOCK), [`true`]),
createCallExpression(helper(CREATE_BLOCK), [
helper(FRAGMENT),
`null`,
renderExp,
`${fragmentFlag} /* ${PatchFlagNames[fragmentFlag]} */`
])
]) as ForCodegenNode
forNode.codegenNode = createVNodeCall(
context,
helper(FRAGMENT),
undefined,
renderExp,
`${fragmentFlag} /* ${PatchFlagNames[fragmentFlag]} */`,
undefined,
undefined,
true /* isBlock */,
true /* isForBlock */,
node.loc
) as ForCodegenNode
return () => {
// finish the codegen now that all children have been traversed
let childBlock
let childBlock: BlockCodegenNode
const isTemplate = isTemplateNode(node)
const { children } = forNode
const needFragmentWrapper =
@ -92,7 +94,7 @@ export const transformFor = createStructuralDirectiveTransform(
: null
if (slotOutlet) {
// <slot v-for="..."> or <template v-for="..."><slot/></template>
childBlock = slotOutlet.codegenNode as SlotOutletCodegenNode
childBlock = slotOutlet.codegenNode as RenderSlotCall
if (isTemplate && keyProperty) {
// <template v-for="..." :key="..."><slot/></template>
// we need to inject the key to the renderSlot() call.
@ -102,37 +104,33 @@ export const transformFor = createStructuralDirectiveTransform(
} else if (needFragmentWrapper) {
// <template v-for="..."> with text or multi-elements
// should generate a fragment block for each loop
childBlock = createBlockExpression(
createCallExpression(helper(CREATE_BLOCK), [
helper(FRAGMENT),
keyProperty ? createObjectExpression([keyProperty]) : `null`,
node.children,
`${PatchFlags.STABLE_FRAGMENT} /* ${
PatchFlagNames[PatchFlags.STABLE_FRAGMENT]
} */`
]),
context
childBlock = createVNodeCall(
context,
helper(FRAGMENT),
keyProperty ? createObjectExpression([keyProperty]) : undefined,
node.children,
`${PatchFlags.STABLE_FRAGMENT} /* ${
PatchFlagNames[PatchFlags.STABLE_FRAGMENT]
} */`,
undefined,
undefined,
true
)
} else {
// Normal element v-for. Directly use the child's codegenNode
// arguments, but replace createVNode() with createBlock()
let codegenNode = (children[0] as PlainElementNode)
.codegenNode as ElementCodegenNode
if (codegenNode.callee === WITH_DIRECTIVES) {
codegenNode.arguments[0].callee = helper(CREATE_BLOCK)
} else {
codegenNode.callee = helper(CREATE_BLOCK)
}
childBlock = createBlockExpression(codegenNode, context)
// but mark it as a block.
childBlock = (children[0] as PlainElementNode)
.codegenNode as VNodeCall
childBlock.isBlock = true
helper(OPEN_BLOCK)
helper(CREATE_BLOCK)
}
renderExp.arguments.push(
createFunctionExpression(
createForLoopParams(forNode.parseResult),
childBlock,
true /* force newline */
)
)
renderExp.arguments.push(createFunctionExpression(
createForLoopParams(forNode.parseResult),
childBlock,
true /* force newline */
) as ForIteratorExpression)
}
})
}

View File

@ -10,31 +10,23 @@ import {
DirectiveNode,
IfBranchNode,
SimpleExpressionNode,
createSequenceExpression,
createCallExpression,
createConditionalExpression,
ConditionalExpression,
CallExpression,
createSimpleExpression,
createObjectProperty,
createObjectExpression,
IfCodegenNode,
IfConditionalExpression,
BlockCodegenNode,
SlotOutletCodegenNode,
ElementCodegenNode,
ComponentCodegenNode,
IfNode
IfNode,
createVNodeCall
} from '../ast'
import { createCompilerError, ErrorCodes } from '../errors'
import { processExpression } from './transformExpression'
import {
OPEN_BLOCK,
CREATE_BLOCK,
FRAGMENT,
WITH_DIRECTIVES,
CREATE_VNODE,
CREATE_COMMENT
CREATE_COMMENT,
OPEN_BLOCK
} from '../runtimeHelpers'
import { injectProp } from '../utils'
@ -46,14 +38,14 @@ export const transformIf = createStructuralDirectiveTransform(
// transformed.
return () => {
if (isRoot) {
ifNode.codegenNode = createSequenceExpression([
createCallExpression(context.helper(OPEN_BLOCK)),
createCodegenNodeForBranch(branch, 0, context)
]) as IfCodegenNode
ifNode.codegenNode = createCodegenNodeForBranch(
branch,
0,
context
) as IfConditionalExpression
} else {
// attach this branch's codegen node to the v-if root.
let parentCondition = ifNode.codegenNode!
.expressions[1] as ConditionalExpression
while (
parentCondition.alternate.type ===
NodeTypes.JS_CONDITIONAL_EXPRESSION
@ -175,7 +167,7 @@ function createCodegenNodeForBranch(
])
) as IfConditionalExpression
} else {
return createChildrenCodegenNode(branch, index, context) as BlockCodegenNode
return createChildrenCodegenNode(branch, index, context)
}
}
@ -183,7 +175,7 @@ function createChildrenCodegenNode(
branch: IfBranchNode,
index: number,
context: TransformContext
): CallExpression {
): BlockCodegenNode {
const { helper } = context
const keyProperty = createObjectProperty(
`key`,
@ -194,35 +186,36 @@ function createChildrenCodegenNode(
const needFragmentWrapper =
children.length !== 1 || firstChild.type !== NodeTypes.ELEMENT
if (needFragmentWrapper) {
const blockArgs: CallExpression['arguments'] = [
helper(FRAGMENT),
createObjectExpression([keyProperty]),
children
]
if (children.length === 1 && firstChild.type === NodeTypes.FOR) {
// optimize away nested fragments when child is a ForNode
const forBlockArgs = firstChild.codegenNode!.expressions[1].arguments
// directly use the for block's children and patchFlag
blockArgs[2] = forBlockArgs[2]
blockArgs[3] = forBlockArgs[3]
const vnodeCall = firstChild.codegenNode!
injectProp(vnodeCall, keyProperty, context)
return vnodeCall
} else {
return createVNodeCall(
context,
helper(FRAGMENT),
createObjectExpression([keyProperty]),
children,
undefined,
undefined,
undefined,
true,
false,
branch.loc
)
}
return createCallExpression(helper(CREATE_BLOCK), blockArgs)
} else {
const childCodegen = (firstChild as ElementNode).codegenNode as
| ElementCodegenNode
| ComponentCodegenNode
| SlotOutletCodegenNode
let vnodeCall = childCodegen
// Element with custom directives. Locate the actual createVNode() call.
if (vnodeCall.callee === WITH_DIRECTIVES) {
vnodeCall = vnodeCall.arguments[0]
}
const vnodeCall = (firstChild as ElementNode)
.codegenNode as BlockCodegenNode
// Change createVNode to createBlock.
if (vnodeCall.callee === CREATE_VNODE) {
vnodeCall.callee = helper(CREATE_BLOCK)
if (vnodeCall.type === NodeTypes.VNODE_CALL) {
vnodeCall.isBlock = true
helper(OPEN_BLOCK)
helper(CREATE_BLOCK)
}
// inject branch key
injectProp(vnodeCall, keyProperty, context)
return childCodegen
return vnodeCall
}
}

View File

@ -19,7 +19,8 @@ import {
FunctionExpression,
CallExpression,
createCallExpression,
createArrayExpression
createArrayExpression,
SlotsExpression
} from '../ast'
import { TransformContext, NodeTransform } from '../transform'
import { createCompilerError, ErrorCodes } from '../errors'
@ -115,7 +116,7 @@ export function buildSlots(
context: TransformContext,
buildSlotFn: SlotFnBuilder = buildClientSlotFn
): {
slots: ObjectExpression | CallExpression
slots: SlotsExpression
hasDynamicSlots: boolean
} {
const { children, loc } = node
@ -312,17 +313,17 @@ export function buildSlots(
}
}
let slots: ObjectExpression | CallExpression = createObjectExpression(
let slots = createObjectExpression(
slotsProperties.concat(
createObjectProperty(`_compiled`, createSimpleExpression(`true`, false))
),
loc
)
) as SlotsExpression
if (dynamicSlots.length) {
slots = createCallExpression(context.helper(CREATE_SLOTS), [
slots,
createArrayExpression(dynamicSlots)
])
]) as SlotsExpression
}
return {

View File

@ -4,8 +4,6 @@ import {
ElementNode,
NodeTypes,
CallExpression,
SequenceExpression,
createSequenceExpression,
createCallExpression,
DirectiveNode,
ElementTypes,
@ -17,22 +15,18 @@ import {
createObjectExpression,
SlotOutletNode,
TemplateNode,
BlockCodegenNode,
ElementCodegenNode,
SlotOutletCodegenNode,
ComponentCodegenNode,
RenderSlotCall,
ExpressionNode,
IfBranchNode,
TextNode,
InterpolationNode
InterpolationNode,
VNodeCall
} from './ast'
import { parse } from 'acorn'
import { walk } from 'estree-walker'
import { TransformContext } from './transform'
import {
OPEN_BLOCK,
MERGE_PROPS,
RENDER_SLOT,
PORTAL,
SUSPENSE,
KEEP_ALIVE,
@ -218,16 +212,6 @@ export function hasDynamicKeyVBind(node: ElementNode): boolean {
)
}
export function createBlockExpression(
blockExp: BlockCodegenNode,
context: TransformContext
): SequenceExpression {
return createSequenceExpression([
createCallExpression(context.helper(OPEN_BLOCK)),
blockExp
])
}
export function isText(
node: TemplateChildNode
): node is TextNode | InterpolationNode {
@ -253,13 +237,13 @@ export function isSlotOutlet(
}
export function injectProp(
node: ElementCodegenNode | ComponentCodegenNode | SlotOutletCodegenNode,
node: VNodeCall | RenderSlotCall,
prop: Property,
context: TransformContext
) {
let propsWithInjection: ObjectExpression | CallExpression
const props =
node.callee === RENDER_SLOT ? node.arguments[2] : node.arguments[1]
node.type === NodeTypes.VNODE_CALL ? node.props : node.arguments[2]
if (props == null || isString(props)) {
propsWithInjection = createObjectExpression([prop])
} else if (props.type === NodeTypes.JS_CALL_EXPRESSION) {
@ -295,10 +279,10 @@ export function injectProp(
props
])
}
if (node.callee === RENDER_SLOT) {
node.arguments[2] = propsWithInjection
if (node.type === NodeTypes.VNODE_CALL) {
node.props = propsWithInjection
} else {
node.arguments[1] = propsWithInjection
node.arguments[2] = propsWithInjection
}
}

View File

@ -335,7 +335,7 @@ export function createCommentVNode(
asBlock: boolean = false
): VNode {
return asBlock
? createBlock(Comment, null, text)
? (openBlock(), createBlock(Comment, null, text))
: createVNode(Comment, null, text)
}