refactor(ssr): adjust ssr scope id logic for client-compiled render functions

This commit is contained in:
Evan You 2021-03-30 19:30:05 -04:00
parent 5e54081d5b
commit 58e2376c4d
3 changed files with 57 additions and 49 deletions

View File

@ -13,7 +13,8 @@ import {
Transition, Transition,
watchEffect, watchEffect,
createVNode, createVNode,
resolveDynamicComponent resolveDynamicComponent,
renderSlot
} from 'vue' } from 'vue'
import { escapeHtml } from '@vue/shared' import { escapeHtml } from '@vue/shared'
import { renderToString } from '../src/renderToString' import { renderToString } from '../src/renderToString'
@ -711,11 +712,11 @@ function testRender(type: string, render: typeof renderToString) {
expect(await render(h(Foo))).toBe(`<div data-v-test></div>`) expect(await render(h(Foo))).toBe(`<div data-v-test></div>`)
}) })
test('with slots', async () => { test('with client-compiled vnode slots', async () => {
const Child = { const Child = {
__scopeId: 'data-v-child', __scopeId: 'data-v-child',
render: function(this: any) { render: function(this: any) {
return h('div', this.$slots.default()) return h('div', null, [renderSlot(this.$slots, 'default')])
} }
} }
@ -723,13 +724,15 @@ function testRender(type: string, render: typeof renderToString) {
__scopeId: 'data-v-test', __scopeId: 'data-v-test',
render: () => { render: () => {
return h(Child, null, { return h(Child, null, {
default: withCtx(() => h('span', 'slot')) default: withCtx(() => [h('span', 'slot')])
}) })
} }
} }
expect(await render(h(Parent))).toBe( expect(await render(h(Parent))).toBe(
`<div data-v-child data-v-test><span data-v-test data-v-child-s>slot</span></div>` `<div data-v-child data-v-test>` +
`<!--[--><span data-v-test data-v-child-s>slot</span><!--]-->` +
`</div>`
) )
}) })
}) })

View File

@ -16,7 +16,7 @@ export function ssrRenderSlot(
fallbackRenderFn: (() => void) | null, fallbackRenderFn: (() => void) | null,
push: PushFn, push: PushFn,
parentComponent: ComponentInternalInstance, parentComponent: ComponentInternalInstance,
slotScopeId?: string | null slotScopeId?: string
) { ) {
// template-compiled slots are always rendered as fragments // template-compiled slots are always rendered as fragments
push(`<!--[-->`) push(`<!--[-->`)
@ -34,7 +34,7 @@ export function ssrRenderSlot(
) )
if (Array.isArray(ret)) { if (Array.isArray(ret)) {
// normal slot // normal slot
renderVNodeChildren(push, ret, parentComponent) renderVNodeChildren(push, ret, parentComponent, slotScopeId)
} else { } else {
// ssr slot. // ssr slot.
// check if the slot renders all comments, in which case use the fallback // check if the slot renders all comments, in which case use the fallback

View File

@ -111,7 +111,8 @@ function renderComponentSubTree(
renderVNode( renderVNode(
push, push,
(instance.subTree = renderComponentRoot(instance)), (instance.subTree = renderComponentRoot(instance)),
instance instance,
slotScopeId
) )
} else { } else {
if ( if (
@ -174,7 +175,8 @@ function renderComponentSubTree(
renderVNode( renderVNode(
push, push,
(instance.subTree = renderComponentRoot(instance)), (instance.subTree = renderComponentRoot(instance)),
instance instance,
slotScopeId
) )
} else { } else {
warn( warn(
@ -191,7 +193,8 @@ function renderComponentSubTree(
export function renderVNode( export function renderVNode(
push: PushFn, push: PushFn,
vnode: VNode, vnode: VNode,
parentComponent: ComponentInternalInstance parentComponent: ComponentInternalInstance,
slotScopeId?: string
) { ) {
const { type, shapeFlag, children } = vnode const { type, shapeFlag, children } = vnode
switch (type) { switch (type) {
@ -207,19 +210,28 @@ export function renderVNode(
push(children as string) push(children as string)
break break
case Fragment: case Fragment:
if (vnode.slotScopeIds) {
slotScopeId =
(slotScopeId ? slotScopeId + ' ' : '') + vnode.slotScopeIds.join(' ')
}
push(`<!--[-->`) // open push(`<!--[-->`) // open
renderVNodeChildren(push, children as VNodeArrayChildren, parentComponent) renderVNodeChildren(
push,
children as VNodeArrayChildren,
parentComponent,
slotScopeId
)
push(`<!--]-->`) // close push(`<!--]-->`) // close
break break
default: default:
if (shapeFlag & ShapeFlags.ELEMENT) { if (shapeFlag & ShapeFlags.ELEMENT) {
renderElementVNode(push, vnode, parentComponent) renderElementVNode(push, vnode, parentComponent, slotScopeId)
} else if (shapeFlag & ShapeFlags.COMPONENT) { } else if (shapeFlag & ShapeFlags.COMPONENT) {
push(renderComponentVNode(vnode, parentComponent)) push(renderComponentVNode(vnode, parentComponent, slotScopeId))
} else if (shapeFlag & ShapeFlags.TELEPORT) { } else if (shapeFlag & ShapeFlags.TELEPORT) {
renderTeleportVNode(push, vnode, parentComponent) renderTeleportVNode(push, vnode, parentComponent, slotScopeId)
} else if (shapeFlag & ShapeFlags.SUSPENSE) { } else if (shapeFlag & ShapeFlags.SUSPENSE) {
renderVNode(push, vnode.ssContent!, parentComponent) renderVNode(push, vnode.ssContent!, parentComponent, slotScopeId)
} else { } else {
warn( warn(
'[@vue/server-renderer] Invalid VNode type:', '[@vue/server-renderer] Invalid VNode type:',
@ -233,17 +245,19 @@ export function renderVNode(
export function renderVNodeChildren( export function renderVNodeChildren(
push: PushFn, push: PushFn,
children: VNodeArrayChildren, children: VNodeArrayChildren,
parentComponent: ComponentInternalInstance parentComponent: ComponentInternalInstance,
slotScopeId: string | undefined
) { ) {
for (let i = 0; i < children.length; i++) { for (let i = 0; i < children.length; i++) {
renderVNode(push, normalizeVNode(children[i]), parentComponent) renderVNode(push, normalizeVNode(children[i]), parentComponent, slotScopeId)
} }
} }
function renderElementVNode( function renderElementVNode(
push: PushFn, push: PushFn,
vnode: VNode, vnode: VNode,
parentComponent: ComponentInternalInstance parentComponent: ComponentInternalInstance,
slotScopeId: string | undefined
) { ) {
const tag = vnode.type as string const tag = vnode.type as string
let { props, children, shapeFlag, scopeId, dirs } = vnode let { props, children, shapeFlag, scopeId, dirs } = vnode
@ -257,7 +271,22 @@ function renderElementVNode(
openTag += ssrRenderAttrs(props, tag) openTag += ssrRenderAttrs(props, tag)
} }
openTag += resolveScopeId(scopeId, vnode, parentComponent) if (scopeId) {
openTag += ` ${scopeId}`
}
// inherit parent chain scope id if this is the root node
let curParent: ComponentInternalInstance | null = parentComponent
let curVnode = vnode
while (curParent && curVnode === curParent.subTree) {
curVnode = curParent.vnode
if (curVnode.scopeId) {
openTag += ` ${curVnode.scopeId}`
}
curParent = curParent.parent
}
if (slotScopeId) {
openTag += ` ${slotScopeId}`
}
push(openTag + `>`) push(openTag + `>`)
if (!isVoidTag(tag)) { if (!isVoidTag(tag)) {
@ -281,7 +310,8 @@ function renderElementVNode(
renderVNodeChildren( renderVNodeChildren(
push, push,
children as VNodeArrayChildren, children as VNodeArrayChildren,
parentComponent parentComponent,
slotScopeId
) )
} }
} }
@ -289,33 +319,6 @@ function renderElementVNode(
} }
} }
function resolveScopeId(
scopeId: string | null,
vnode: VNode,
parentComponent: ComponentInternalInstance | null
) {
let res = ``
if (scopeId) {
res = ` ${scopeId}`
}
if (parentComponent) {
const treeOwnerId = parentComponent.type.__scopeId
// vnode's own scopeId and the current rendering component's scopeId is
// different - this is a slot content node.
if (treeOwnerId && treeOwnerId !== scopeId) {
res += ` ${treeOwnerId}-s`
}
if (vnode === parentComponent.subTree) {
res += resolveScopeId(
parentComponent.vnode.scopeId,
parentComponent.vnode,
parentComponent.parent
)
}
}
return res
}
function applySSRDirectives( function applySSRDirectives(
vnode: VNode, vnode: VNode,
rawProps: VNodeProps | null, rawProps: VNodeProps | null,
@ -338,7 +341,8 @@ function applySSRDirectives(
function renderTeleportVNode( function renderTeleportVNode(
push: PushFn, push: PushFn,
vnode: VNode, vnode: VNode,
parentComponent: ComponentInternalInstance parentComponent: ComponentInternalInstance,
slotScopeId: string | undefined
) { ) {
const target = vnode.props && vnode.props.to const target = vnode.props && vnode.props.to
const disabled = vnode.props && vnode.props.disabled const disabled = vnode.props && vnode.props.disabled
@ -358,7 +362,8 @@ function renderTeleportVNode(
renderVNodeChildren( renderVNodeChildren(
push, push,
vnode.children as VNodeArrayChildren, vnode.children as VNodeArrayChildren,
parentComponent parentComponent,
slotScopeId
) )
}, },
target, target,