feat(ssr/suspense): suspense hydration

In order to support hydration of async components, server-rendered
fragments must be explicitly marked with comment nodes.
This commit is contained in:
Evan You
2020-03-12 22:19:41 -04:00
parent b3d7d64931
commit a3cc970030
19 changed files with 385 additions and 139 deletions

View File

@@ -10,7 +10,8 @@ import {
createBlockStatement,
CompilerOptions,
IfStatement,
CallExpression
CallExpression,
isText
} from '@vue/compiler-dom'
import { isString, escapeHtml } from '@vue/shared'
import { SSR_INTERPOLATE, ssrHelpers } from './runtimeHelpers'
@@ -28,7 +29,9 @@ import { ssrProcessElement } from './transforms/ssrTransformElement'
export function ssrCodegenTransform(ast: RootNode, options: CompilerOptions) {
const context = createSSRTransformContext(ast, options)
processChildren(ast.children, context)
const isFragment =
ast.children.length > 1 && ast.children.some(c => !isText(c))
processChildren(ast.children, context, isFragment)
ast.codegenNode = createBlockStatement(context.body)
// Finalize helpers.
@@ -104,8 +107,12 @@ function createChildContext(
export function processChildren(
children: TemplateChildNode[],
context: SSRTransformContext
context: SSRTransformContext,
asFragment = false
) {
if (asFragment) {
context.pushStringPart(`<!--1-->`)
}
for (let i = 0; i < children.length; i++) {
const child = children[i]
if (child.type === NodeTypes.ELEMENT) {
@@ -128,14 +135,18 @@ export function processChildren(
ssrProcessFor(child, context)
}
}
if (asFragment) {
context.pushStringPart(`<!--0-->`)
}
}
export function processChildrenAsStatement(
children: TemplateChildNode[],
parentContext: SSRTransformContext,
asFragment = false,
withSlotScopeId = parentContext.withSlotScopeId
): BlockStatement {
const childContext = createChildContext(parentContext, withSlotScopeId)
processChildren(children, childContext)
processChildren(children, childContext, asFragment)
return createBlockStatement(childContext.body)
}

View File

@@ -30,7 +30,8 @@ import {
traverseNode,
ExpressionNode,
TemplateNode,
SUSPENSE
SUSPENSE,
TRANSITION_GROUP
} from '@vue/compiler-dom'
import { SSR_RENDER_COMPONENT } from '../runtimeHelpers'
import {
@@ -151,7 +152,7 @@ export function ssrProcessComponent(
return ssrProcessSuspense(node, context)
} else {
// real fall-through (e.g. KeepAlive): just render its children.
processChildren(node.children, context)
processChildren(node.children, context, component === TRANSITION_GROUP)
}
} else {
// finish up slot function expressions from the 1st pass.
@@ -167,6 +168,7 @@ export function ssrProcessComponent(
processChildrenAsStatement(
children,
context,
false,
true /* withSlotScopeId */
),
vnodeBranch

View File

@@ -4,7 +4,8 @@ import {
processFor,
createCallExpression,
createFunctionExpression,
createForLoopParams
createForLoopParams,
NodeTypes
} from '@vue/compiler-dom'
import {
SSRTransformContext,
@@ -21,14 +22,23 @@ export const ssrTransformFor = createStructuralDirectiveTransform(
// This is called during the 2nd transform pass to construct the SSR-sepcific
// codegen nodes.
export function ssrProcessFor(node: ForNode, context: SSRTransformContext) {
const needFragmentWrapper =
node.children.length !== 1 || node.children[0].type !== NodeTypes.ELEMENT
const renderLoop = createFunctionExpression(
createForLoopParams(node.parseResult)
)
renderLoop.body = processChildrenAsStatement(node.children, context)
renderLoop.body = processChildrenAsStatement(
node.children,
context,
needFragmentWrapper
)
// v-for always renders a fragment
context.pushStringPart(`<!--1-->`)
context.pushStatement(
createCallExpression(context.helper(SSR_RENDER_LIST), [
node.source,
renderLoop
])
)
context.pushStringPart(`<!--0-->`)
}

View File

@@ -4,7 +4,10 @@ import {
IfNode,
createIfStatement,
createBlockStatement,
createCallExpression
createCallExpression,
IfBranchNode,
BlockStatement,
NodeTypes
} from '@vue/compiler-dom'
import {
SSRTransformContext,
@@ -23,17 +26,14 @@ export function ssrProcessIf(node: IfNode, context: SSRTransformContext) {
const [rootBranch] = node.branches
const ifStatement = createIfStatement(
rootBranch.condition!,
processChildrenAsStatement(rootBranch.children, context)
processIfBranch(rootBranch, context)
)
context.pushStatement(ifStatement)
let currentIf = ifStatement
for (let i = 1; i < node.branches.length; i++) {
const branch = node.branches[i]
const branchBlockStatement = processChildrenAsStatement(
branch.children,
context
)
const branchBlockStatement = processIfBranch(branch, context)
if (branch.condition) {
// else-if
currentIf = currentIf.alternate = createIfStatement(
@@ -52,3 +52,15 @@ export function ssrProcessIf(node: IfNode, context: SSRTransformContext) {
])
}
}
function processIfBranch(
branch: IfBranchNode,
context: SSRTransformContext
): BlockStatement {
const { children } = branch
const needFragmentWrapper =
(children.length !== 1 || children[0].type !== NodeTypes.ELEMENT) &&
// optimize away nested fragments when the only child is a ForNode
!(children.length === 1 && children[0].type === NodeTypes.FOR)
return processChildrenAsStatement(children, context, needFragmentWrapper)
}