Skip to content

Commit

Permalink
fix(ssr): support dynamic components that resolve to element or vnode
Browse files Browse the repository at this point in the history
fix #1508
  • Loading branch information
yyx990803 committed Jul 7, 2020
1 parent d7184c9 commit 41db49d
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 18 deletions.
12 changes: 6 additions & 6 deletions packages/compiler-ssr/__tests__/ssrComponent.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,21 @@ describe('ssr: components', () => {
test('dynamic component', () => {
expect(compile(`<component is="foo" prop="b" />`).code)
.toMatchInlineSnapshot(`
"const { resolveDynamicComponent: _resolveDynamicComponent, mergeProps: _mergeProps } = require(\\"vue\\")
const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
"const { resolveDynamicComponent: _resolveDynamicComponent, mergeProps: _mergeProps, createVNode: _createVNode } = require(\\"vue\\")
const { ssrRenderVNode: _ssrRenderVNode } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(_ssrRenderComponent(_resolveDynamicComponent(\\"foo\\"), _mergeProps({ prop: \\"b\\" }, _attrs), null, _parent))
_ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent(\\"foo\\"), _mergeProps({ prop: \\"b\\" }, _attrs), null), _parent)
}"
`)

expect(compile(`<component :is="foo" prop="b" />`).code)
.toMatchInlineSnapshot(`
"const { resolveDynamicComponent: _resolveDynamicComponent, mergeProps: _mergeProps } = require(\\"vue\\")
const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
"const { resolveDynamicComponent: _resolveDynamicComponent, mergeProps: _mergeProps, createVNode: _createVNode } = require(\\"vue\\")
const { ssrRenderVNode: _ssrRenderVNode } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(_ssrRenderComponent(_resolveDynamicComponent(_ctx.foo), _mergeProps({ prop: \\"b\\" }, _attrs), null, _parent))
_ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent(_ctx.foo), _mergeProps({ prop: \\"b\\" }, _attrs), null), _parent)
}"
`)
})
Expand Down
2 changes: 2 additions & 0 deletions packages/compiler-ssr/src/runtimeHelpers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { registerRuntimeHelpers } from '@vue/compiler-dom'

export const SSR_INTERPOLATE = Symbol(`ssrInterpolate`)
export const SSR_RENDER_VNODE = Symbol(`ssrRenderVNode`)
export const SSR_RENDER_COMPONENT = Symbol(`ssrRenderComponent`)
export const SSR_RENDER_SLOT = Symbol(`ssrRenderSlot`)
export const SSR_RENDER_CLASS = Symbol(`ssrRenderClass`)
Expand All @@ -18,6 +19,7 @@ export const SSR_RENDER_SUSPENSE = Symbol(`ssrRenderSuspense`)

export const ssrHelpers = {
[SSR_INTERPOLATE]: `ssrInterpolate`,
[SSR_RENDER_VNODE]: `ssrRenderVNode`,
[SSR_RENDER_COMPONENT]: `ssrRenderComponent`,
[SSR_RENDER_SLOT]: `ssrRenderSlot`,
[SSR_RENDER_CLASS]: `ssrRenderClass`,
Expand Down
55 changes: 44 additions & 11 deletions packages/compiler-ssr/src/transforms/ssrTransformComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
buildSlots,
FunctionExpression,
TemplateChildNode,
TELEPORT,
createIfStatement,
createSimpleExpression,
getBaseTransformPreset,
Expand All @@ -31,9 +30,12 @@ import {
ExpressionNode,
TemplateNode,
SUSPENSE,
TRANSITION_GROUP
TELEPORT,
TRANSITION_GROUP,
CREATE_VNODE,
CallExpression
} from '@vue/compiler-dom'
import { SSR_RENDER_COMPONENT } from '../runtimeHelpers'
import { SSR_RENDER_COMPONENT, SSR_RENDER_VNODE } from '../runtimeHelpers'
import {
SSRTransformContext,
processChildren,
Expand All @@ -58,7 +60,10 @@ interface WIPSlotEntry {
vnodeBranch: ReturnStatement
}

const componentTypeMap = new WeakMap<ComponentNode, symbol>()
const componentTypeMap = new WeakMap<
ComponentNode,
string | symbol | CallExpression
>()

// ssr component transform is done in two phases:
// In phase 1. we use `buildSlot` to analyze the children of the component into
Expand All @@ -75,8 +80,9 @@ export const ssrTransformComponent: NodeTransform = (node, context) => {
}

const component = resolveComponentType(node, context, true /* ssr */)
componentTypeMap.set(node, component)

if (isSymbol(component)) {
componentTypeMap.set(node, component)
if (component === SUSPENSE) {
return ssrTransformSuspense(node, context)
}
Expand Down Expand Up @@ -134,20 +140,38 @@ export const ssrTransformComponent: NodeTransform = (node, context) => {
? buildSlots(node, context, buildSSRSlotFn).slots
: `null`

node.ssrCodegenNode = createCallExpression(
context.helper(SSR_RENDER_COMPONENT),
[component, props, slots, `_parent`]
)
if (typeof component !== 'string') {
// dynamic component that resolved to a `resolveDynamicComponent` call
// expression - since the reoslved result may be a plain element (string)
// or a VNode, handle it with `renderVNode`.
node.ssrCodegenNode = createCallExpression(
context.helper(SSR_RENDER_VNODE),
[
`_push`,
createCallExpression(context.helper(CREATE_VNODE), [
component,
props,
slots
]),
`_parent`
]
)
} else {
node.ssrCodegenNode = createCallExpression(
context.helper(SSR_RENDER_COMPONENT),
[component, props, slots, `_parent`]
)
}
}
}

export function ssrProcessComponent(
node: ComponentNode,
context: SSRTransformContext
) {
const component = componentTypeMap.get(node)!
if (!node.ssrCodegenNode) {
// this is a built-in component that fell-through.
const component = componentTypeMap.get(node)!
if (component === TELEPORT) {
return ssrProcessTeleport(node, context)
} else if (component === SUSPENSE) {
Expand Down Expand Up @@ -176,7 +200,16 @@ export function ssrProcessComponent(
vnodeBranch
)
}
context.pushStatement(createCallExpression(`_push`, [node.ssrCodegenNode]))
if (typeof component === 'string') {
// static component
context.pushStatement(
createCallExpression(`_push`, [node.ssrCodegenNode])
)
} else {
// dynamic component (`resolveDynamicComponent` call)
// the codegen node is a `renderVNode` call
context.pushStatement(node.ssrCodegenNode)
}
}
}

Expand Down
63 changes: 63 additions & 0 deletions packages/server-renderer/__tests__/ssrDynamicComponent.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { createApp, createVNode } from 'vue'
import { renderToString } from '../src/renderToString'

describe('ssr: dynamic component', () => {
test('resolved to component', async () => {
expect(
await renderToString(
createApp({
components: {
one: {
template: `<div><slot/></div>`
}
},
template: `<component :is="'one'"><span>slot</span></component>`
})
)
).toBe(`<div><!--[--><span>slot</span><!--]--></div>`)
})

test('resolve to element', async () => {
expect(
await renderToString(
createApp({
template: `<component :is="'p'"><span>slot</span></component>`
})
)
).toBe(`<p><span>slot</span></p>`)
})

test('resolve to component vnode', async () => {
const Child = {
props: ['id'],
template: `<div>{{ id }}<slot/></div>`
}
expect(
await renderToString(
createApp({
setup() {
return {
vnode: createVNode(Child, { id: 'test' })
}
},
template: `<component :is="vnode"><span>slot</span></component>`
})
)
).toBe(`<div>test<!--[--><span>slot</span><!--]--></div>`)
})

test('resolve to element vnode', async () => {
expect(
await renderToString(
createApp({
setup() {
return {
vnode: createVNode('div', { id: 'test' })
}
},
template: `<component :is="vnode"><span>slot</span></component>`
})
)
).toBe(`<div id="test"><span>slot</span></div>`)
})
})
1 change: 1 addition & 0 deletions packages/server-renderer/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export { renderToString } from './renderToString'
export { renderToStream } from './renderToStream'

// internal runtime helpers
export { renderVNode as ssrRenderVNode } from './render'
export { ssrRenderComponent } from './helpers/ssrRenderComponent'
export { ssrRenderSlot } from './helpers/ssrRenderSlot'
export { ssrRenderTeleport } from './helpers/ssrRenderTeleport'
Expand Down
2 changes: 1 addition & 1 deletion packages/server-renderer/src/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ function renderComponentSubTree(
return getBuffer()
}

function renderVNode(
export function renderVNode(
push: PushFn,
vnode: VNode,
parentComponent: ComponentInternalInstance
Expand Down

0 comments on commit 41db49d

Please sign in to comment.