]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
refactor(ssr): adjust ssr scope id logic for client-compiled render functions
authorEvan You <yyx990803@gmail.com>
Tue, 30 Mar 2021 23:30:05 +0000 (19:30 -0400)
committerEvan You <yyx990803@gmail.com>
Tue, 30 Mar 2021 23:30:05 +0000 (19:30 -0400)
packages/server-renderer/__tests__/render.spec.ts
packages/server-renderer/src/helpers/ssrRenderSlot.ts
packages/server-renderer/src/render.ts

index 67bc23450b8719a0684408aaddfee596293b57ad..54333e8f524396f2727b11215f8916581b19c84e 100644 (file)
@@ -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(`<div data-v-test></div>`)
       })
 
-      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(
-          `<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>`
         )
       })
     })
index 3f3589a3b3fc9e3c74a4311a2b37a642f4cd5e62..bcb393dea04e9eb7dd0058688a0ea9628a5a632a 100644 (file)
@@ -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
index b3002ab2acfaead7126e0472c07bbc5940f7b0fa..f820fa305af63c3f2aa2855195587fad49ad4989 100644 (file)
@@ -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,