fix(ssr): support dynamic components that resolve to element or vnode
fix #1508
This commit is contained in:
parent
d7184c99e6
commit
41db49dfb7
@ -20,21 +20,21 @@ describe('ssr: components', () => {
|
|||||||
test('dynamic component', () => {
|
test('dynamic component', () => {
|
||||||
expect(compile(`<component is="foo" prop="b" />`).code)
|
expect(compile(`<component is="foo" prop="b" />`).code)
|
||||||
.toMatchInlineSnapshot(`
|
.toMatchInlineSnapshot(`
|
||||||
"const { resolveDynamicComponent: _resolveDynamicComponent, mergeProps: _mergeProps } = require(\\"vue\\")
|
"const { resolveDynamicComponent: _resolveDynamicComponent, mergeProps: _mergeProps, createVNode: _createVNode } = require(\\"vue\\")
|
||||||
const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
|
const { ssrRenderVNode: _ssrRenderVNode } = require(\\"@vue/server-renderer\\")
|
||||||
|
|
||||||
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
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)
|
expect(compile(`<component :is="foo" prop="b" />`).code)
|
||||||
.toMatchInlineSnapshot(`
|
.toMatchInlineSnapshot(`
|
||||||
"const { resolveDynamicComponent: _resolveDynamicComponent, mergeProps: _mergeProps } = require(\\"vue\\")
|
"const { resolveDynamicComponent: _resolveDynamicComponent, mergeProps: _mergeProps, createVNode: _createVNode } = require(\\"vue\\")
|
||||||
const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
|
const { ssrRenderVNode: _ssrRenderVNode } = require(\\"@vue/server-renderer\\")
|
||||||
|
|
||||||
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
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)
|
||||||
}"
|
}"
|
||||||
`)
|
`)
|
||||||
})
|
})
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { registerRuntimeHelpers } from '@vue/compiler-dom'
|
import { registerRuntimeHelpers } from '@vue/compiler-dom'
|
||||||
|
|
||||||
export const SSR_INTERPOLATE = Symbol(`ssrInterpolate`)
|
export const SSR_INTERPOLATE = Symbol(`ssrInterpolate`)
|
||||||
|
export const SSR_RENDER_VNODE = Symbol(`ssrRenderVNode`)
|
||||||
export const SSR_RENDER_COMPONENT = Symbol(`ssrRenderComponent`)
|
export const SSR_RENDER_COMPONENT = Symbol(`ssrRenderComponent`)
|
||||||
export const SSR_RENDER_SLOT = Symbol(`ssrRenderSlot`)
|
export const SSR_RENDER_SLOT = Symbol(`ssrRenderSlot`)
|
||||||
export const SSR_RENDER_CLASS = Symbol(`ssrRenderClass`)
|
export const SSR_RENDER_CLASS = Symbol(`ssrRenderClass`)
|
||||||
@ -18,6 +19,7 @@ export const SSR_RENDER_SUSPENSE = Symbol(`ssrRenderSuspense`)
|
|||||||
|
|
||||||
export const ssrHelpers = {
|
export const ssrHelpers = {
|
||||||
[SSR_INTERPOLATE]: `ssrInterpolate`,
|
[SSR_INTERPOLATE]: `ssrInterpolate`,
|
||||||
|
[SSR_RENDER_VNODE]: `ssrRenderVNode`,
|
||||||
[SSR_RENDER_COMPONENT]: `ssrRenderComponent`,
|
[SSR_RENDER_COMPONENT]: `ssrRenderComponent`,
|
||||||
[SSR_RENDER_SLOT]: `ssrRenderSlot`,
|
[SSR_RENDER_SLOT]: `ssrRenderSlot`,
|
||||||
[SSR_RENDER_CLASS]: `ssrRenderClass`,
|
[SSR_RENDER_CLASS]: `ssrRenderClass`,
|
||||||
|
@ -11,7 +11,6 @@ import {
|
|||||||
buildSlots,
|
buildSlots,
|
||||||
FunctionExpression,
|
FunctionExpression,
|
||||||
TemplateChildNode,
|
TemplateChildNode,
|
||||||
TELEPORT,
|
|
||||||
createIfStatement,
|
createIfStatement,
|
||||||
createSimpleExpression,
|
createSimpleExpression,
|
||||||
getBaseTransformPreset,
|
getBaseTransformPreset,
|
||||||
@ -31,9 +30,12 @@ import {
|
|||||||
ExpressionNode,
|
ExpressionNode,
|
||||||
TemplateNode,
|
TemplateNode,
|
||||||
SUSPENSE,
|
SUSPENSE,
|
||||||
TRANSITION_GROUP
|
TELEPORT,
|
||||||
|
TRANSITION_GROUP,
|
||||||
|
CREATE_VNODE,
|
||||||
|
CallExpression
|
||||||
} from '@vue/compiler-dom'
|
} from '@vue/compiler-dom'
|
||||||
import { SSR_RENDER_COMPONENT } from '../runtimeHelpers'
|
import { SSR_RENDER_COMPONENT, SSR_RENDER_VNODE } from '../runtimeHelpers'
|
||||||
import {
|
import {
|
||||||
SSRTransformContext,
|
SSRTransformContext,
|
||||||
processChildren,
|
processChildren,
|
||||||
@ -58,7 +60,10 @@ interface WIPSlotEntry {
|
|||||||
vnodeBranch: ReturnStatement
|
vnodeBranch: ReturnStatement
|
||||||
}
|
}
|
||||||
|
|
||||||
const componentTypeMap = new WeakMap<ComponentNode, symbol>()
|
const componentTypeMap = new WeakMap<
|
||||||
|
ComponentNode,
|
||||||
|
string | symbol | CallExpression
|
||||||
|
>()
|
||||||
|
|
||||||
// ssr component transform is done in two phases:
|
// ssr component transform is done in two phases:
|
||||||
// In phase 1. we use `buildSlot` to analyze the children of the component into
|
// In phase 1. we use `buildSlot` to analyze the children of the component into
|
||||||
@ -75,8 +80,9 @@ export const ssrTransformComponent: NodeTransform = (node, context) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const component = resolveComponentType(node, context, true /* ssr */)
|
const component = resolveComponentType(node, context, true /* ssr */)
|
||||||
|
componentTypeMap.set(node, component)
|
||||||
|
|
||||||
if (isSymbol(component)) {
|
if (isSymbol(component)) {
|
||||||
componentTypeMap.set(node, component)
|
|
||||||
if (component === SUSPENSE) {
|
if (component === SUSPENSE) {
|
||||||
return ssrTransformSuspense(node, context)
|
return ssrTransformSuspense(node, context)
|
||||||
}
|
}
|
||||||
@ -134,10 +140,28 @@ export const ssrTransformComponent: NodeTransform = (node, context) => {
|
|||||||
? buildSlots(node, context, buildSSRSlotFn).slots
|
? buildSlots(node, context, buildSSRSlotFn).slots
|
||||||
: `null`
|
: `null`
|
||||||
|
|
||||||
node.ssrCodegenNode = createCallExpression(
|
if (typeof component !== 'string') {
|
||||||
context.helper(SSR_RENDER_COMPONENT),
|
// dynamic component that resolved to a `resolveDynamicComponent` call
|
||||||
[component, props, slots, `_parent`]
|
// 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`]
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,9 +169,9 @@ export function ssrProcessComponent(
|
|||||||
node: ComponentNode,
|
node: ComponentNode,
|
||||||
context: SSRTransformContext
|
context: SSRTransformContext
|
||||||
) {
|
) {
|
||||||
|
const component = componentTypeMap.get(node)!
|
||||||
if (!node.ssrCodegenNode) {
|
if (!node.ssrCodegenNode) {
|
||||||
// this is a built-in component that fell-through.
|
// this is a built-in component that fell-through.
|
||||||
const component = componentTypeMap.get(node)!
|
|
||||||
if (component === TELEPORT) {
|
if (component === TELEPORT) {
|
||||||
return ssrProcessTeleport(node, context)
|
return ssrProcessTeleport(node, context)
|
||||||
} else if (component === SUSPENSE) {
|
} else if (component === SUSPENSE) {
|
||||||
@ -176,7 +200,16 @@ export function ssrProcessComponent(
|
|||||||
vnodeBranch
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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>`)
|
||||||
|
})
|
||||||
|
})
|
@ -4,6 +4,7 @@ export { renderToString } from './renderToString'
|
|||||||
export { renderToStream } from './renderToStream'
|
export { renderToStream } from './renderToStream'
|
||||||
|
|
||||||
// internal runtime helpers
|
// internal runtime helpers
|
||||||
|
export { renderVNode as ssrRenderVNode } from './render'
|
||||||
export { ssrRenderComponent } from './helpers/ssrRenderComponent'
|
export { ssrRenderComponent } from './helpers/ssrRenderComponent'
|
||||||
export { ssrRenderSlot } from './helpers/ssrRenderSlot'
|
export { ssrRenderSlot } from './helpers/ssrRenderSlot'
|
||||||
export { ssrRenderTeleport } from './helpers/ssrRenderTeleport'
|
export { ssrRenderTeleport } from './helpers/ssrRenderTeleport'
|
||||||
|
@ -142,7 +142,7 @@ function renderComponentSubTree(
|
|||||||
return getBuffer()
|
return getBuffer()
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderVNode(
|
export function renderVNode(
|
||||||
push: PushFn,
|
push: PushFn,
|
||||||
vnode: VNode,
|
vnode: VNode,
|
||||||
parentComponent: ComponentInternalInstance
|
parentComponent: ComponentInternalInstance
|
||||||
|
Loading…
x
Reference in New Issue
Block a user