feat(compiler-core): support Suspense in templates

This commit is contained in:
Evan You 2019-10-16 17:40:00 -04:00
parent e97951dd2e
commit 4b2b29efa1
3 changed files with 123 additions and 117 deletions

View File

@ -50,7 +50,8 @@ export const enum ElementTypes {
COMPONENT, COMPONENT,
SLOT, SLOT,
TEMPLATE, TEMPLATE,
PORTAL PORTAL,
SUSPENSE
} }
export interface Node { export interface Node {
@ -101,6 +102,7 @@ export type ElementNode =
| SlotOutletNode | SlotOutletNode
| TemplateNode | TemplateNode
| PortalNode | PortalNode
| SuspenseNode
export interface BaseElementNode extends Node { export interface BaseElementNode extends Node {
type: NodeTypes.ELEMENT type: NodeTypes.ELEMENT
@ -141,6 +143,11 @@ export interface PortalNode extends BaseElementNode {
codegenNode: ElementCodegenNode | undefined codegenNode: ElementCodegenNode | undefined
} }
export interface SuspenseNode extends BaseElementNode {
tagType: ElementTypes.SUSPENSE
codegenNode: ElementCodegenNode | undefined
}
export interface TextNode extends Node { export interface TextNode extends Node {
type: NodeTypes.TEXT type: NodeTypes.TEXT
content: string content: string

View File

@ -445,6 +445,8 @@ function parseTag(
if (tag === 'slot') tagType = ElementTypes.SLOT if (tag === 'slot') tagType = ElementTypes.SLOT
else if (tag === 'template') tagType = ElementTypes.TEMPLATE else if (tag === 'template') tagType = ElementTypes.TEMPLATE
else if (tag === 'portal' || tag === 'Portal') tagType = ElementTypes.PORTAL else if (tag === 'portal' || tag === 'Portal') tagType = ElementTypes.PORTAL
else if (tag === 'suspense' || tag === 'Suspense')
tagType = ElementTypes.SUSPENSE
} }
return { return {

View File

@ -24,7 +24,8 @@ import {
RESOLVE_COMPONENT, RESOLVE_COMPONENT,
MERGE_PROPS, MERGE_PROPS,
TO_HANDLERS, TO_HANDLERS,
PORTAL PORTAL,
SUSPENSE
} from '../runtimeHelpers' } from '../runtimeHelpers'
import { getInnerRange, isVSlot, toValidAssetId } from '../utils' import { getInnerRange, isVSlot, toValidAssetId } from '../utils'
import { buildSlots } from './vSlot' import { buildSlots } from './vSlot'
@ -36,130 +37,126 @@ const directiveImportMap = new WeakMap<DirectiveNode, symbol>()
// generate a JavaScript AST for this element's codegen // generate a JavaScript AST for this element's codegen
export const transformElement: NodeTransform = (node, context) => { export const transformElement: NodeTransform = (node, context) => {
if (node.type === NodeTypes.ELEMENT) { if (
if ( node.type !== NodeTypes.ELEMENT ||
node.tagType === ElementTypes.ELEMENT || // handled by transformSlotOutlet
node.tagType === ElementTypes.COMPONENT || node.tagType === ElementTypes.SLOT ||
node.tagType === ElementTypes.PORTAL || // <template v-if/v-for> should have already been replaced
// <template> with v-if or v-for are ignored during traversal. // <templte v-slot> is handled by buildSlots
// <template> without v-slot should be treated as a normal element. (node.tagType === ElementTypes.TEMPLATE && node.props.some(isVSlot))
(node.tagType === ElementTypes.TEMPLATE && !node.props.some(isVSlot)) ) {
) { return
// perform the work on exit, after all child expressions have been }
// processed and merged. // perform the work on exit, after all child expressions have been
return () => { // processed and merged.
const isComponent = node.tagType === ElementTypes.COMPONENT return () => {
const isPortal = node.tagType === ElementTypes.PORTAL const isComponent = node.tagType === ElementTypes.COMPONENT
let hasProps = node.props.length > 0 let hasProps = node.props.length > 0
let patchFlag: number = 0 let patchFlag: number = 0
let runtimeDirectives: DirectiveNode[] | undefined let runtimeDirectives: DirectiveNode[] | undefined
let dynamicPropNames: string[] | undefined let dynamicPropNames: string[] | undefined
if (isComponent) { if (isComponent) {
context.helper(RESOLVE_COMPONENT) context.helper(RESOLVE_COMPONENT)
context.components.add(node.tag) context.components.add(node.tag)
} }
const args: CallExpression['arguments'] = [ const args: CallExpression['arguments'] = [
isComponent isComponent
? toValidAssetId(node.tag, `component`) ? toValidAssetId(node.tag, `component`)
: isPortal : node.tagType === ElementTypes.PORTAL
? context.helper(PORTAL) ? context.helper(PORTAL)
: `"${node.tag}"` : node.tagType === ElementTypes.SUSPENSE
] ? context.helper(SUSPENSE)
// props : `"${node.tag}"`
if (hasProps) { ]
const propsBuildResult = buildProps(node, context) // props
patchFlag = propsBuildResult.patchFlag if (hasProps) {
dynamicPropNames = propsBuildResult.dynamicPropNames const propsBuildResult = buildProps(node, context)
runtimeDirectives = propsBuildResult.directives patchFlag = propsBuildResult.patchFlag
if (!propsBuildResult.props) { dynamicPropNames = propsBuildResult.dynamicPropNames
hasProps = false runtimeDirectives = propsBuildResult.directives
} else { if (!propsBuildResult.props) {
args.push(propsBuildResult.props) hasProps = false
} } else {
args.push(propsBuildResult.props)
}
}
// children
const hasChildren = node.children.length > 0
if (hasChildren) {
if (!hasProps) {
args.push(`null`)
}
if (isComponent) {
const { slots, hasDynamicSlots } = buildSlots(node, context)
args.push(slots)
if (hasDynamicSlots) {
patchFlag |= PatchFlags.DYNAMIC_SLOTS
} }
// children } else if (node.children.length === 1) {
const hasChildren = node.children.length > 0 const child = node.children[0]
if (hasChildren) { const type = child.type
if (!hasProps) { // check for dynamic text children
args.push(`null`) const hasDynamicTextChild =
} type === NodeTypes.INTERPOLATION ||
if (isComponent) { type === NodeTypes.COMPOUND_EXPRESSION
const { slots, hasDynamicSlots } = buildSlots(node, context) if (hasDynamicTextChild && !isStaticNode(child)) {
args.push(slots) patchFlag |= PatchFlags.TEXT
if (hasDynamicSlots) {
patchFlag |= PatchFlags.DYNAMIC_SLOTS
}
} else if (node.children.length === 1) {
const child = node.children[0]
const type = child.type
// check for dynamic text children
const hasDynamicTextChild =
type === NodeTypes.INTERPOLATION ||
type === NodeTypes.COMPOUND_EXPRESSION
if (hasDynamicTextChild && !isStaticNode(child)) {
patchFlag |= PatchFlags.TEXT
}
// pass directly if the only child is a text node
// (plain / interpolation / expression)
if (hasDynamicTextChild || type === NodeTypes.TEXT) {
args.push(child)
} else {
args.push(node.children)
}
} else {
args.push(node.children)
}
} }
// patchFlag & dynamicPropNames // pass directly if the only child is a text node
if (patchFlag !== 0) { // (plain / interpolation / expression)
if (!hasChildren) { if (hasDynamicTextChild || type === NodeTypes.TEXT) {
if (!hasProps) { args.push(child)
args.push(`null`) } else {
} args.push(node.children)
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} */`)
} else {
args.push(patchFlag + '')
}
if (dynamicPropNames && dynamicPropNames.length) {
args.push(
`[${dynamicPropNames.map(n => JSON.stringify(n)).join(`, `)}]`
)
}
} }
} else {
const { loc } = node args.push(node.children)
const vnode = createCallExpression( }
context.helper(CREATE_VNODE), }
args, // patchFlag & dynamicPropNames
loc 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} */`)
} else {
args.push(patchFlag + '')
}
if (dynamicPropNames && dynamicPropNames.length) {
args.push(
`[${dynamicPropNames.map(n => JSON.stringify(n)).join(`, `)}]`
) )
}
}
if (runtimeDirectives && runtimeDirectives.length) { const { loc } = node
node.codegenNode = createCallExpression( const vnode = createCallExpression(context.helper(CREATE_VNODE), args, loc)
context.helper(APPLY_DIRECTIVES),
[ if (runtimeDirectives && runtimeDirectives.length) {
vnode, node.codegenNode = createCallExpression(
createArrayExpression( context.helper(APPLY_DIRECTIVES),
runtimeDirectives.map(dir => buildDirectiveArgs(dir, context)), [
loc vnode,
) createArrayExpression(
], runtimeDirectives.map(dir => buildDirectiveArgs(dir, context)),
loc loc
) )
} else { ],
node.codegenNode = vnode loc
} )
} } else {
node.codegenNode = vnode
} }
} }
} }