Skip to content

Commit

Permalink
feat(ssr/suspense): suspense hydration
Browse files Browse the repository at this point in the history
In order to support hydration of async components, server-rendered
fragments must be explicitly marked with comment nodes.
  • Loading branch information
yyx990803 committed Mar 13, 2020
1 parent b3d7d64 commit a3cc970
Show file tree
Hide file tree
Showing 19 changed files with 385 additions and 139 deletions.
6 changes: 4 additions & 2 deletions packages/compiler-core/__tests__/hydration.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ describe('SSR hydration', () => {
const msg = ref('foo')
const fn = jest.fn()
const { vnode, container } = mountWithHydration(
'<div><span>foo</span><span class="foo"></span></div>',
'<div><!----><span>foo</span><!----><span class="foo"></span><!----><!----></div>',
() =>
h('div', [
[h('span', msg.value), [h('span', { class: msg.value, onClick: fn })]]
Expand Down Expand Up @@ -136,7 +136,9 @@ describe('SSR hydration', () => {

msg.value = 'bar'
await nextTick()
expect(vnode.el.innerHTML).toBe(`<span>bar</span><span class="bar"></span>`)
expect(vnode.el.innerHTML).toBe(
`<!----><span>bar</span><!----><span class="bar"></span><!----><!---->`
)
})

test('portal', async () => {
Expand Down
10 changes: 5 additions & 5 deletions packages/compiler-ssr/__tests__/ssrComponent.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,11 +219,11 @@ describe('ssr: components', () => {
foo: ({ list }, _push, _parent, _scopeId) => {
if (_push) {
if (_ctx.ok) {
_push(\`<div\${_scopeId}>\`)
_push(\`<div\${_scopeId}><!--1-->\`)
_ssrRenderList(list, (i) => {
_push(\`<span\${_scopeId}></span>\`)
})
_push(\`</div>\`)
_push(\`<!--0--></div>\`)
} else {
_push(\`<!---->\`)
}
Expand All @@ -242,11 +242,11 @@ describe('ssr: components', () => {
bar: ({ ok }, _push, _parent, _scopeId) => {
if (_push) {
if (ok) {
_push(\`<div\${_scopeId}>\`)
_push(\`<div\${_scopeId}><!--1-->\`)
_ssrRenderList(_ctx.list, (i) => {
_push(\`<span\${_scopeId}></span>\`)
})
_push(\`</div>\`)
_push(\`<!--0--></div>\`)
} else {
_push(\`<!---->\`)
}
Expand Down Expand Up @@ -281,7 +281,7 @@ describe('ssr: components', () => {
.toMatchInlineSnapshot(`
"
return function ssrRender(_ctx, _push, _parent) {
_push(\`<div></div>\`)
_push(\`<!--1--><div></div><!--0-->\`)
}"
`)

Expand Down
24 changes: 19 additions & 5 deletions packages/compiler-ssr/__tests__/ssrVFor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ describe('ssr: v-for', () => {
"const { ssrRenderList: _ssrRenderList } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) {
_push(\`<!--1-->\`)
_ssrRenderList(_ctx.list, (i) => {
_push(\`<div></div>\`)
})
_push(\`<!--0-->\`)
}"
`)
})
Expand All @@ -19,9 +21,11 @@ describe('ssr: v-for', () => {
"const { ssrRenderList: _ssrRenderList } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) {
_push(\`<!--1-->\`)
_ssrRenderList(_ctx.list, (i) => {
_push(\`<div>foo<span>bar</span></div>\`)
})
_push(\`<!--0-->\`)
}"
`)
})
Expand All @@ -37,17 +41,19 @@ describe('ssr: v-for', () => {
"const { ssrInterpolate: _ssrInterpolate, ssrRenderList: _ssrRenderList } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) {
_push(\`<!--1-->\`)
_ssrRenderList(_ctx.list, (row, i) => {
_push(\`<div>\`)
_push(\`<div><!--1-->\`)
_ssrRenderList(row, (j) => {
_push(\`<div>\${
_ssrInterpolate(i)
},\${
_ssrInterpolate(j)
}</div>\`)
})
_push(\`</div>\`)
_push(\`<!--0--></div>\`)
})
_push(\`<!--0-->\`)
}"
`)
})
Expand All @@ -58,9 +64,11 @@ describe('ssr: v-for', () => {
"const { ssrInterpolate: _ssrInterpolate, ssrRenderList: _ssrRenderList } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) {
_push(\`<!--1-->\`)
_ssrRenderList(_ctx.list, (i) => {
_push(\`\${_ssrInterpolate(i)}\`)
_push(\`<!--1-->\${_ssrInterpolate(i)}<!--0-->\`)
})
_push(\`<!--0-->\`)
}"
`)
})
Expand All @@ -73,9 +81,11 @@ describe('ssr: v-for', () => {
"const { ssrInterpolate: _ssrInterpolate, ssrRenderList: _ssrRenderList } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) {
_push(\`<!--1-->\`)
_ssrRenderList(_ctx.list, (i) => {
_push(\`<span>\${_ssrInterpolate(i)}</span>\`)
})
_push(\`<!--0-->\`)
}"
`)
})
Expand All @@ -89,13 +99,15 @@ describe('ssr: v-for', () => {
"const { ssrInterpolate: _ssrInterpolate, ssrRenderList: _ssrRenderList } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) {
_push(\`<!--1-->\`)
_ssrRenderList(_ctx.list, (i) => {
_push(\`<span>\${
_push(\`<!--1--><span>\${
_ssrInterpolate(i)
}</span><span>\${
_ssrInterpolate(i + 1)
}</span>\`)
}</span><!--0-->\`)
})
_push(\`<!--0-->\`)
}"
`)
})
Expand All @@ -111,9 +123,11 @@ describe('ssr: v-for', () => {
"const { ssrInterpolate: _ssrInterpolate, ssrRenderList: _ssrRenderList } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) {
_push(\`<!--1-->\`)
_ssrRenderList(_ctx.list, ({ foo }, index) => {
_push(\`<div>\${_ssrInterpolate(foo + _ctx.bar + index)}</div>\`)
})
_push(\`<!--0-->\`)
}"
`)
})
Expand Down
8 changes: 5 additions & 3 deletions packages/compiler-ssr/__tests__/ssrVIf.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ describe('ssr: v-if', () => {
"
return function ssrRender(_ctx, _push, _parent) {
if (_ctx.foo) {
_push(\`hello\`)
_push(\`<!--1-->hello<!--0-->\`)
} else {
_push(\`<!---->\`)
}
Expand Down Expand Up @@ -110,7 +110,7 @@ describe('ssr: v-if', () => {
"
return function ssrRender(_ctx, _push, _parent) {
if (_ctx.foo) {
_push(\`<div>hi</div><div>ho</div>\`)
_push(\`<!--1--><div>hi</div><div>ho</div><!--0-->\`)
} else {
_push(\`<!---->\`)
}
Expand All @@ -126,9 +126,11 @@ describe('ssr: v-if', () => {
return function ssrRender(_ctx, _push, _parent) {
if (_ctx.foo) {
_push(\`<!--1-->\`)
_ssrRenderList(_ctx.list, (i) => {
_push(\`<div></div>\`)
})
_push(\`<!--0-->\`)
} else {
_push(\`<!---->\`)
}
Expand All @@ -145,7 +147,7 @@ describe('ssr: v-if', () => {
"
return function ssrRender(_ctx, _push, _parent) {
if (_ctx.foo) {
_push(\`<div>hi</div><div>ho</div>\`)
_push(\`<!--1--><div>hi</div><div>ho</div><!--0-->\`)
} else {
_push(\`<div></div>\`)
}
Expand Down
19 changes: 15 additions & 4 deletions packages/compiler-ssr/src/ssrCodegenTransform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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.
Expand Down Expand Up @@ -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) {
Expand All @@ -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)
}
6 changes: 4 additions & 2 deletions packages/compiler-ssr/src/transforms/ssrTransformComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ import {
traverseNode,
ExpressionNode,
TemplateNode,
SUSPENSE
SUSPENSE,
TRANSITION_GROUP
} from '@vue/compiler-dom'
import { SSR_RENDER_COMPONENT } from '../runtimeHelpers'
import {
Expand Down Expand Up @@ -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.
Expand All @@ -167,6 +168,7 @@ export function ssrProcessComponent(
processChildrenAsStatement(
children,
context,
false,
true /* withSlotScopeId */
),
vnodeBranch
Expand Down
14 changes: 12 additions & 2 deletions packages/compiler-ssr/src/transforms/ssrVFor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import {
processFor,
createCallExpression,
createFunctionExpression,
createForLoopParams
createForLoopParams,
NodeTypes
} from '@vue/compiler-dom'
import {
SSRTransformContext,
Expand All @@ -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-->`)
}
24 changes: 18 additions & 6 deletions packages/compiler-ssr/src/transforms/ssrVIf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import {
IfNode,
createIfStatement,
createBlockStatement,
createCallExpression
createCallExpression,
IfBranchNode,
BlockStatement,
NodeTypes
} from '@vue/compiler-dom'
import {
SSRTransformContext,
Expand All @@ -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(
Expand All @@ -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)
}
4 changes: 1 addition & 3 deletions packages/runtime-core/src/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,6 @@ export interface ComponentInternalInstance {

// suspense related
asyncDep: Promise<any> | null
asyncResult: unknown
asyncResolved: boolean

// storage for any extra properties
Expand Down Expand Up @@ -215,7 +214,6 @@ export function createComponentInstance(

// async dependency management
asyncDep: null,
asyncResult: null,
asyncResolved: false,

// user namespace for storing whatever the user assigns to `this`
Expand Down Expand Up @@ -367,7 +365,7 @@ function setupStatefulComponent(
if (isPromise(setupResult)) {
if (isSSR) {
// return the promise so server-renderer can wait on it
return setupResult.then(resolvedResult => {
return setupResult.then((resolvedResult: unknown) => {
handleSetupResult(instance, resolvedResult, parentSuspense, isSSR)
})
} else if (__FEATURE_SUSPENSE__) {
Expand Down
Loading

0 comments on commit a3cc970

Please sign in to comment.