Skip to content

Commit 4b2b29e

Browse files
committed
feat(compiler-core): support Suspense in templates
1 parent e97951d commit 4b2b29e

File tree

3 files changed

+123
-117
lines changed

3 files changed

+123
-117
lines changed

packages/compiler-core/src/ast.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ export const enum ElementTypes {
5050
COMPONENT,
5151
SLOT,
5252
TEMPLATE,
53-
PORTAL
53+
PORTAL,
54+
SUSPENSE
5455
}
5556

5657
export interface Node {
@@ -101,6 +102,7 @@ export type ElementNode =
101102
| SlotOutletNode
102103
| TemplateNode
103104
| PortalNode
105+
| SuspenseNode
104106

105107
export interface BaseElementNode extends Node {
106108
type: NodeTypes.ELEMENT
@@ -141,6 +143,11 @@ export interface PortalNode extends BaseElementNode {
141143
codegenNode: ElementCodegenNode | undefined
142144
}
143145

146+
export interface SuspenseNode extends BaseElementNode {
147+
tagType: ElementTypes.SUSPENSE
148+
codegenNode: ElementCodegenNode | undefined
149+
}
150+
144151
export interface TextNode extends Node {
145152
type: NodeTypes.TEXT
146153
content: string

packages/compiler-core/src/parse.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,8 @@ function parseTag(
445445
if (tag === 'slot') tagType = ElementTypes.SLOT
446446
else if (tag === 'template') tagType = ElementTypes.TEMPLATE
447447
else if (tag === 'portal' || tag === 'Portal') tagType = ElementTypes.PORTAL
448+
else if (tag === 'suspense' || tag === 'Suspense')
449+
tagType = ElementTypes.SUSPENSE
448450
}
449451

450452
return {

packages/compiler-core/src/transforms/transformElement.ts

Lines changed: 113 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ import {
2424
RESOLVE_COMPONENT,
2525
MERGE_PROPS,
2626
TO_HANDLERS,
27-
PORTAL
27+
PORTAL,
28+
SUSPENSE
2829
} from '../runtimeHelpers'
2930
import { getInnerRange, isVSlot, toValidAssetId } from '../utils'
3031
import { buildSlots } from './vSlot'
@@ -36,130 +37,126 @@ const directiveImportMap = new WeakMap<DirectiveNode, symbol>()
3637

3738
// generate a JavaScript AST for this element's codegen
3839
export const transformElement: NodeTransform = (node, context) => {
39-
if (node.type === NodeTypes.ELEMENT) {
40-
if (
41-
node.tagType === ElementTypes.ELEMENT ||
42-
node.tagType === ElementTypes.COMPONENT ||
43-
node.tagType === ElementTypes.PORTAL ||
44-
// <template> with v-if or v-for are ignored during traversal.
45-
// <template> without v-slot should be treated as a normal element.
46-
(node.tagType === ElementTypes.TEMPLATE && !node.props.some(isVSlot))
47-
) {
48-
// perform the work on exit, after all child expressions have been
49-
// processed and merged.
50-
return () => {
51-
const isComponent = node.tagType === ElementTypes.COMPONENT
52-
const isPortal = node.tagType === ElementTypes.PORTAL
53-
let hasProps = node.props.length > 0
54-
let patchFlag: number = 0
55-
let runtimeDirectives: DirectiveNode[] | undefined
56-
let dynamicPropNames: string[] | undefined
40+
if (
41+
node.type !== NodeTypes.ELEMENT ||
42+
// handled by transformSlotOutlet
43+
node.tagType === ElementTypes.SLOT ||
44+
// <template v-if/v-for> should have already been replaced
45+
// <templte v-slot> is handled by buildSlots
46+
(node.tagType === ElementTypes.TEMPLATE && node.props.some(isVSlot))
47+
) {
48+
return
49+
}
50+
// perform the work on exit, after all child expressions have been
51+
// processed and merged.
52+
return () => {
53+
const isComponent = node.tagType === ElementTypes.COMPONENT
54+
let hasProps = node.props.length > 0
55+
let patchFlag: number = 0
56+
let runtimeDirectives: DirectiveNode[] | undefined
57+
let dynamicPropNames: string[] | undefined
5758

58-
if (isComponent) {
59-
context.helper(RESOLVE_COMPONENT)
60-
context.components.add(node.tag)
61-
}
59+
if (isComponent) {
60+
context.helper(RESOLVE_COMPONENT)
61+
context.components.add(node.tag)
62+
}
6263

63-
const args: CallExpression['arguments'] = [
64-
isComponent
65-
? toValidAssetId(node.tag, `component`)
66-
: isPortal
67-
? context.helper(PORTAL)
68-
: `"${node.tag}"`
69-
]
70-
// props
71-
if (hasProps) {
72-
const propsBuildResult = buildProps(node, context)
73-
patchFlag = propsBuildResult.patchFlag
74-
dynamicPropNames = propsBuildResult.dynamicPropNames
75-
runtimeDirectives = propsBuildResult.directives
76-
if (!propsBuildResult.props) {
77-
hasProps = false
78-
} else {
79-
args.push(propsBuildResult.props)
80-
}
64+
const args: CallExpression['arguments'] = [
65+
isComponent
66+
? toValidAssetId(node.tag, `component`)
67+
: node.tagType === ElementTypes.PORTAL
68+
? context.helper(PORTAL)
69+
: node.tagType === ElementTypes.SUSPENSE
70+
? context.helper(SUSPENSE)
71+
: `"${node.tag}"`
72+
]
73+
// props
74+
if (hasProps) {
75+
const propsBuildResult = buildProps(node, context)
76+
patchFlag = propsBuildResult.patchFlag
77+
dynamicPropNames = propsBuildResult.dynamicPropNames
78+
runtimeDirectives = propsBuildResult.directives
79+
if (!propsBuildResult.props) {
80+
hasProps = false
81+
} else {
82+
args.push(propsBuildResult.props)
83+
}
84+
}
85+
// children
86+
const hasChildren = node.children.length > 0
87+
if (hasChildren) {
88+
if (!hasProps) {
89+
args.push(`null`)
90+
}
91+
if (isComponent) {
92+
const { slots, hasDynamicSlots } = buildSlots(node, context)
93+
args.push(slots)
94+
if (hasDynamicSlots) {
95+
patchFlag |= PatchFlags.DYNAMIC_SLOTS
8196
}
82-
// children
83-
const hasChildren = node.children.length > 0
84-
if (hasChildren) {
85-
if (!hasProps) {
86-
args.push(`null`)
87-
}
88-
if (isComponent) {
89-
const { slots, hasDynamicSlots } = buildSlots(node, context)
90-
args.push(slots)
91-
if (hasDynamicSlots) {
92-
patchFlag |= PatchFlags.DYNAMIC_SLOTS
93-
}
94-
} else if (node.children.length === 1) {
95-
const child = node.children[0]
96-
const type = child.type
97-
// check for dynamic text children
98-
const hasDynamicTextChild =
99-
type === NodeTypes.INTERPOLATION ||
100-
type === NodeTypes.COMPOUND_EXPRESSION
101-
if (hasDynamicTextChild && !isStaticNode(child)) {
102-
patchFlag |= PatchFlags.TEXT
103-
}
104-
// pass directly if the only child is a text node
105-
// (plain / interpolation / expression)
106-
if (hasDynamicTextChild || type === NodeTypes.TEXT) {
107-
args.push(child)
108-
} else {
109-
args.push(node.children)
110-
}
111-
} else {
112-
args.push(node.children)
113-
}
97+
} else if (node.children.length === 1) {
98+
const child = node.children[0]
99+
const type = child.type
100+
// check for dynamic text children
101+
const hasDynamicTextChild =
102+
type === NodeTypes.INTERPOLATION ||
103+
type === NodeTypes.COMPOUND_EXPRESSION
104+
if (hasDynamicTextChild && !isStaticNode(child)) {
105+
patchFlag |= PatchFlags.TEXT
114106
}
115-
// patchFlag & dynamicPropNames
116-
if (patchFlag !== 0) {
117-
if (!hasChildren) {
118-
if (!hasProps) {
119-
args.push(`null`)
120-
}
121-
args.push(`null`)
122-
}
123-
if (__DEV__) {
124-
const flagNames = Object.keys(PatchFlagNames)
125-
.map(Number)
126-
.filter(n => n > 0 && patchFlag & n)
127-
.map(n => PatchFlagNames[n])
128-
.join(`, `)
129-
args.push(patchFlag + ` /* ${flagNames} */`)
130-
} else {
131-
args.push(patchFlag + '')
132-
}
133-
if (dynamicPropNames && dynamicPropNames.length) {
134-
args.push(
135-
`[${dynamicPropNames.map(n => JSON.stringify(n)).join(`, `)}]`
136-
)
137-
}
107+
// pass directly if the only child is a text node
108+
// (plain / interpolation / expression)
109+
if (hasDynamicTextChild || type === NodeTypes.TEXT) {
110+
args.push(child)
111+
} else {
112+
args.push(node.children)
138113
}
139-
140-
const { loc } = node
141-
const vnode = createCallExpression(
142-
context.helper(CREATE_VNODE),
143-
args,
144-
loc
114+
} else {
115+
args.push(node.children)
116+
}
117+
}
118+
// patchFlag & dynamicPropNames
119+
if (patchFlag !== 0) {
120+
if (!hasChildren) {
121+
if (!hasProps) {
122+
args.push(`null`)
123+
}
124+
args.push(`null`)
125+
}
126+
if (__DEV__) {
127+
const flagNames = Object.keys(PatchFlagNames)
128+
.map(Number)
129+
.filter(n => n > 0 && patchFlag & n)
130+
.map(n => PatchFlagNames[n])
131+
.join(`, `)
132+
args.push(patchFlag + ` /* ${flagNames} */`)
133+
} else {
134+
args.push(patchFlag + '')
135+
}
136+
if (dynamicPropNames && dynamicPropNames.length) {
137+
args.push(
138+
`[${dynamicPropNames.map(n => JSON.stringify(n)).join(`, `)}]`
145139
)
140+
}
141+
}
142+
143+
const { loc } = node
144+
const vnode = createCallExpression(context.helper(CREATE_VNODE), args, loc)
146145

147-
if (runtimeDirectives && runtimeDirectives.length) {
148-
node.codegenNode = createCallExpression(
149-
context.helper(APPLY_DIRECTIVES),
150-
[
151-
vnode,
152-
createArrayExpression(
153-
runtimeDirectives.map(dir => buildDirectiveArgs(dir, context)),
154-
loc
155-
)
156-
],
146+
if (runtimeDirectives && runtimeDirectives.length) {
147+
node.codegenNode = createCallExpression(
148+
context.helper(APPLY_DIRECTIVES),
149+
[
150+
vnode,
151+
createArrayExpression(
152+
runtimeDirectives.map(dir => buildDirectiveArgs(dir, context)),
157153
loc
158154
)
159-
} else {
160-
node.codegenNode = vnode
161-
}
162-
}
155+
],
156+
loc
157+
)
158+
} else {
159+
node.codegenNode = vnode
163160
}
164161
}
165162
}

0 commit comments

Comments
 (0)