From 58e2376c4d60ef97aeff4f2bc5b8ee87fc62371c Mon Sep 17 00:00:00 2001 From: Evan You Date: Tue, 30 Mar 2021 19:30:05 -0400 Subject: [PATCH] refactor(ssr): adjust ssr scope id logic for client-compiled render functions --- .../server-renderer/__tests__/render.spec.ts | 13 +-- .../src/helpers/ssrRenderSlot.ts | 4 +- packages/server-renderer/src/render.ts | 89 ++++++++++--------- 3 files changed, 57 insertions(+), 49 deletions(-) diff --git a/packages/server-renderer/__tests__/render.spec.ts b/packages/server-renderer/__tests__/render.spec.ts index 67bc2345..54333e8f 100644 --- a/packages/server-renderer/__tests__/render.spec.ts +++ b/packages/server-renderer/__tests__/render.spec.ts @@ -13,7 +13,8 @@ import { Transition, watchEffect, createVNode, - resolveDynamicComponent + resolveDynamicComponent, + renderSlot } from 'vue' import { escapeHtml } from '@vue/shared' import { renderToString } from '../src/renderToString' @@ -711,11 +712,11 @@ function testRender(type: string, render: typeof renderToString) { expect(await render(h(Foo))).toBe(`
`) }) - test('with slots', async () => { + test('with client-compiled vnode slots', async () => { const Child = { __scopeId: 'data-v-child', 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', render: () => { return h(Child, null, { - default: withCtx(() => h('span', 'slot')) + default: withCtx(() => [h('span', 'slot')]) }) } } expect(await render(h(Parent))).toBe( - `
slot
` + `
` + + `slot` + + `
` ) }) }) diff --git a/packages/server-renderer/src/helpers/ssrRenderSlot.ts b/packages/server-renderer/src/helpers/ssrRenderSlot.ts index 3f3589a3..bcb393de 100644 --- a/packages/server-renderer/src/helpers/ssrRenderSlot.ts +++ b/packages/server-renderer/src/helpers/ssrRenderSlot.ts @@ -16,7 +16,7 @@ export function ssrRenderSlot( fallbackRenderFn: (() => void) | null, push: PushFn, parentComponent: ComponentInternalInstance, - slotScopeId?: string | null + slotScopeId?: string ) { // template-compiled slots are always rendered as fragments push(``) @@ -34,7 +34,7 @@ export function ssrRenderSlot( ) if (Array.isArray(ret)) { // normal slot - renderVNodeChildren(push, ret, parentComponent) + renderVNodeChildren(push, ret, parentComponent, slotScopeId) } else { // ssr slot. // check if the slot renders all comments, in which case use the fallback diff --git a/packages/server-renderer/src/render.ts b/packages/server-renderer/src/render.ts index b3002ab2..f820fa30 100644 --- a/packages/server-renderer/src/render.ts +++ b/packages/server-renderer/src/render.ts @@ -111,7 +111,8 @@ function renderComponentSubTree( renderVNode( push, (instance.subTree = renderComponentRoot(instance)), - instance + instance, + slotScopeId ) } else { if ( @@ -174,7 +175,8 @@ function renderComponentSubTree( renderVNode( push, (instance.subTree = renderComponentRoot(instance)), - instance + instance, + slotScopeId ) } else { warn( @@ -191,7 +193,8 @@ function renderComponentSubTree( export function renderVNode( push: PushFn, vnode: VNode, - parentComponent: ComponentInternalInstance + parentComponent: ComponentInternalInstance, + slotScopeId?: string ) { const { type, shapeFlag, children } = vnode switch (type) { @@ -207,19 +210,28 @@ export function renderVNode( push(children as string) break case Fragment: + if (vnode.slotScopeIds) { + slotScopeId = + (slotScopeId ? slotScopeId + ' ' : '') + vnode.slotScopeIds.join(' ') + } push(``) // open - renderVNodeChildren(push, children as VNodeArrayChildren, parentComponent) + renderVNodeChildren( + push, + children as VNodeArrayChildren, + parentComponent, + slotScopeId + ) push(``) // close break default: if (shapeFlag & ShapeFlags.ELEMENT) { - renderElementVNode(push, vnode, parentComponent) + renderElementVNode(push, vnode, parentComponent, slotScopeId) } else if (shapeFlag & ShapeFlags.COMPONENT) { - push(renderComponentVNode(vnode, parentComponent)) + push(renderComponentVNode(vnode, parentComponent, slotScopeId)) } else if (shapeFlag & ShapeFlags.TELEPORT) { - renderTeleportVNode(push, vnode, parentComponent) + renderTeleportVNode(push, vnode, parentComponent, slotScopeId) } else if (shapeFlag & ShapeFlags.SUSPENSE) { - renderVNode(push, vnode.ssContent!, parentComponent) + renderVNode(push, vnode.ssContent!, parentComponent, slotScopeId) } else { warn( '[@vue/server-renderer] Invalid VNode type:', @@ -233,17 +245,19 @@ export function renderVNode( export function renderVNodeChildren( push: PushFn, children: VNodeArrayChildren, - parentComponent: ComponentInternalInstance + parentComponent: ComponentInternalInstance, + slotScopeId: string | undefined ) { for (let i = 0; i < children.length; i++) { - renderVNode(push, normalizeVNode(children[i]), parentComponent) + renderVNode(push, normalizeVNode(children[i]), parentComponent, slotScopeId) } } function renderElementVNode( push: PushFn, vnode: VNode, - parentComponent: ComponentInternalInstance + parentComponent: ComponentInternalInstance, + slotScopeId: string | undefined ) { const tag = vnode.type as string let { props, children, shapeFlag, scopeId, dirs } = vnode @@ -257,7 +271,22 @@ function renderElementVNode( 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 + `>`) if (!isVoidTag(tag)) { @@ -281,7 +310,8 @@ function renderElementVNode( renderVNodeChildren( push, 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( vnode: VNode, rawProps: VNodeProps | null, @@ -338,7 +341,8 @@ function applySSRDirectives( function renderTeleportVNode( push: PushFn, vnode: VNode, - parentComponent: ComponentInternalInstance + parentComponent: ComponentInternalInstance, + slotScopeId: string | undefined ) { const target = vnode.props && vnode.props.to const disabled = vnode.props && vnode.props.disabled @@ -358,7 +362,8 @@ function renderTeleportVNode( renderVNodeChildren( push, vnode.children as VNodeArrayChildren, - parentComponent + parentComponent, + slotScopeId ) }, target,