feat(ssr): compiler-ssr support for Suspense
This commit is contained in:
@@ -30,17 +30,20 @@ import {
|
||||
traverseNode,
|
||||
ExpressionNode,
|
||||
TemplateNode,
|
||||
findProp,
|
||||
JSChildNode
|
||||
SUSPENSE
|
||||
} from '@vue/compiler-dom'
|
||||
import { SSR_RENDER_COMPONENT, SSR_RENDER_PORTAL } from '../runtimeHelpers'
|
||||
import { SSR_RENDER_COMPONENT } from '../runtimeHelpers'
|
||||
import {
|
||||
SSRTransformContext,
|
||||
processChildren,
|
||||
processChildrenAsStatement
|
||||
} from '../ssrCodegenTransform'
|
||||
import { ssrProcessPortal } from './ssrTransformPortal'
|
||||
import {
|
||||
ssrProcessSuspense,
|
||||
ssrTransformSuspense
|
||||
} from './ssrTransformSuspense'
|
||||
import { isSymbol, isObject, isArray } from '@vue/shared'
|
||||
import { createSSRCompilerError, SSRErrorCodes } from '../errors'
|
||||
|
||||
// We need to construct the slot functions in the 1st pass to ensure proper
|
||||
// scope tracking, but the children of each slot cannot be processed until
|
||||
@@ -56,6 +59,12 @@ interface WIPSlotEntry {
|
||||
|
||||
const componentTypeMap = new WeakMap<ComponentNode, symbol>()
|
||||
|
||||
// ssr component transform is done in two phases:
|
||||
// In phase 1. we use `buildSlot` to analyze the children of the component into
|
||||
// WIP slot functions (it must be done in phase 1 because `buildSlot` relies on
|
||||
// the core transform context).
|
||||
// In phase 2. we convert the WIP slots from phase 1 into ssr-specific codegen
|
||||
// nodes.
|
||||
export const ssrTransformComponent: NodeTransform = (node, context) => {
|
||||
if (
|
||||
node.type !== NodeTypes.ELEMENT ||
|
||||
@@ -67,6 +76,9 @@ export const ssrTransformComponent: NodeTransform = (node, context) => {
|
||||
const component = resolveComponentType(node, context, true /* ssr */)
|
||||
if (isSymbol(component)) {
|
||||
componentTypeMap.set(node, component)
|
||||
if (component === SUSPENSE) {
|
||||
return ssrTransformSuspense(node, context)
|
||||
}
|
||||
return // built-in component: fallthrough
|
||||
}
|
||||
|
||||
@@ -132,12 +144,15 @@ export function ssrProcessComponent(
|
||||
) {
|
||||
if (!node.ssrCodegenNode) {
|
||||
// this is a built-in component that fell-through.
|
||||
// just render its children.
|
||||
const component = componentTypeMap.get(node)!
|
||||
if (component === PORTAL) {
|
||||
return ssrProcessPortal(node, context)
|
||||
} else if (component === SUSPENSE) {
|
||||
return ssrProcessSuspense(node, context)
|
||||
} else {
|
||||
// real fall-through (e.g. KeepAlive): just render its children.
|
||||
processChildren(node.children, context)
|
||||
}
|
||||
processChildren(node.children, context)
|
||||
} else {
|
||||
// finish up slot function expressions from the 1st pass.
|
||||
const wipEntries = wipMap.get(node) || []
|
||||
@@ -161,47 +176,6 @@ export function ssrProcessComponent(
|
||||
}
|
||||
}
|
||||
|
||||
function ssrProcessPortal(node: ComponentNode, context: SSRTransformContext) {
|
||||
const targetProp = findProp(node, 'target')
|
||||
if (!targetProp) {
|
||||
context.onError(
|
||||
createSSRCompilerError(SSRErrorCodes.X_SSR_NO_PORTAL_TARGET, node.loc)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
let target: JSChildNode
|
||||
if (targetProp.type === NodeTypes.ATTRIBUTE && targetProp.value) {
|
||||
target = createSimpleExpression(targetProp.value.content, true)
|
||||
} else if (targetProp.type === NodeTypes.DIRECTIVE && targetProp.exp) {
|
||||
target = targetProp.exp
|
||||
} else {
|
||||
context.onError(
|
||||
createSSRCompilerError(
|
||||
SSRErrorCodes.X_SSR_NO_PORTAL_TARGET,
|
||||
targetProp.loc
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const contentRenderFn = createFunctionExpression(
|
||||
[`_push`],
|
||||
undefined, // Body is added later
|
||||
true, // newline
|
||||
false, // isSlot
|
||||
node.loc
|
||||
)
|
||||
contentRenderFn.body = processChildrenAsStatement(node.children, context)
|
||||
context.pushStatement(
|
||||
createCallExpression(context.helper(SSR_RENDER_PORTAL), [
|
||||
contentRenderFn,
|
||||
target,
|
||||
`_parent`
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
export const rawOptionsMap = new WeakMap<RootNode, CompilerOptions>()
|
||||
|
||||
const [baseNodeTransforms, baseDirectiveTransforms] = getBaseTransformPreset(
|
||||
|
||||
60
packages/compiler-ssr/src/transforms/ssrTransformPortal.ts
Normal file
60
packages/compiler-ssr/src/transforms/ssrTransformPortal.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import {
|
||||
ComponentNode,
|
||||
findProp,
|
||||
JSChildNode,
|
||||
NodeTypes,
|
||||
createSimpleExpression,
|
||||
createFunctionExpression,
|
||||
createCallExpression
|
||||
} from '@vue/compiler-dom'
|
||||
import {
|
||||
SSRTransformContext,
|
||||
processChildrenAsStatement
|
||||
} from '../ssrCodegenTransform'
|
||||
import { createSSRCompilerError, SSRErrorCodes } from '../errors'
|
||||
import { SSR_RENDER_PORTAL } from '../runtimeHelpers'
|
||||
|
||||
// Note: this is a 2nd-pass codegen transform.
|
||||
export function ssrProcessPortal(
|
||||
node: ComponentNode,
|
||||
context: SSRTransformContext
|
||||
) {
|
||||
const targetProp = findProp(node, 'target')
|
||||
if (!targetProp) {
|
||||
context.onError(
|
||||
createSSRCompilerError(SSRErrorCodes.X_SSR_NO_PORTAL_TARGET, node.loc)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
let target: JSChildNode
|
||||
if (targetProp.type === NodeTypes.ATTRIBUTE && targetProp.value) {
|
||||
target = createSimpleExpression(targetProp.value.content, true)
|
||||
} else if (targetProp.type === NodeTypes.DIRECTIVE && targetProp.exp) {
|
||||
target = targetProp.exp
|
||||
} else {
|
||||
context.onError(
|
||||
createSSRCompilerError(
|
||||
SSRErrorCodes.X_SSR_NO_PORTAL_TARGET,
|
||||
targetProp.loc
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const contentRenderFn = createFunctionExpression(
|
||||
[`_push`],
|
||||
undefined, // Body is added later
|
||||
true, // newline
|
||||
false, // isSlot
|
||||
node.loc
|
||||
)
|
||||
contentRenderFn.body = processChildrenAsStatement(node.children, context)
|
||||
context.pushStatement(
|
||||
createCallExpression(context.helper(SSR_RENDER_PORTAL), [
|
||||
contentRenderFn,
|
||||
target,
|
||||
`_parent`
|
||||
])
|
||||
)
|
||||
}
|
||||
78
packages/compiler-ssr/src/transforms/ssrTransformSuspense.ts
Normal file
78
packages/compiler-ssr/src/transforms/ssrTransformSuspense.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import {
|
||||
ComponentNode,
|
||||
TransformContext,
|
||||
buildSlots,
|
||||
createFunctionExpression,
|
||||
FunctionExpression,
|
||||
TemplateChildNode,
|
||||
createCallExpression,
|
||||
SlotsExpression
|
||||
} from '@vue/compiler-dom'
|
||||
import {
|
||||
SSRTransformContext,
|
||||
processChildrenAsStatement
|
||||
} from '../ssrCodegenTransform'
|
||||
import { SSR_RENDER_SUSPENSE } from '../runtimeHelpers'
|
||||
|
||||
const wipMap = new WeakMap<ComponentNode, WIPEntry>()
|
||||
|
||||
interface WIPEntry {
|
||||
slotsExp: SlotsExpression
|
||||
wipSlots: Array<{
|
||||
fn: FunctionExpression
|
||||
children: TemplateChildNode[]
|
||||
}>
|
||||
}
|
||||
|
||||
// phase 1
|
||||
export function ssrTransformSuspense(
|
||||
node: ComponentNode,
|
||||
context: TransformContext
|
||||
) {
|
||||
return () => {
|
||||
if (node.children.length) {
|
||||
const wipEntry: WIPEntry = {
|
||||
slotsExp: null as any,
|
||||
wipSlots: []
|
||||
}
|
||||
wipMap.set(node, wipEntry)
|
||||
wipEntry.slotsExp = buildSlots(node, context, (_props, children, loc) => {
|
||||
const fn = createFunctionExpression(
|
||||
[`_push`],
|
||||
undefined, // no return, assign body later
|
||||
true, // newline
|
||||
false, // suspense slots are not treated as normal slots
|
||||
loc
|
||||
)
|
||||
wipEntry.wipSlots.push({
|
||||
fn,
|
||||
children
|
||||
})
|
||||
return fn
|
||||
}).slots
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// phase 2
|
||||
export function ssrProcessSuspense(
|
||||
node: ComponentNode,
|
||||
context: SSRTransformContext
|
||||
) {
|
||||
// complete wip slots with ssr code
|
||||
const wipEntry = wipMap.get(node)
|
||||
if (!wipEntry) {
|
||||
return
|
||||
}
|
||||
const { slotsExp, wipSlots } = wipEntry
|
||||
for (let i = 0; i < wipSlots.length; i++) {
|
||||
const { fn, children } = wipSlots[i]
|
||||
fn.body = processChildrenAsStatement(children, context)
|
||||
}
|
||||
// _push(ssrRenderSuspense(slots))
|
||||
context.pushStatement(
|
||||
createCallExpression(`_push`, [
|
||||
createCallExpression(context.helper(SSR_RENDER_SUSPENSE), [slotsExp])
|
||||
])
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user