]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(ssr): fix ssr scopeId on component root
authorEvan You <yyx990803@gmail.com>
Sat, 27 Jun 2020 04:25:07 +0000 (00:25 -0400)
committerEvan You <yyx990803@gmail.com>
Sat, 27 Jun 2020 04:27:44 +0000 (00:27 -0400)
packages/compiler-core/src/codegen.ts
packages/compiler-ssr/__tests__/ssrScopeId.spec.ts
packages/runtime-core/src/componentRenderUtils.ts
packages/server-renderer/__tests__/renderToString.spec.ts
packages/server-renderer/__tests__/ssrScopeId.spec.ts [new file with mode: 0644]
packages/server-renderer/src/helpers/ssrRenderSlot.ts
packages/server-renderer/src/render.ts

index e71810fc958823a6d37898104c5f725e2ea0cad6..cb1636fd17e3d8e90200221106c36b80c3405936 100644 (file)
@@ -204,12 +204,15 @@ export function generate(
   }
 
   // enter render function
-  if (genScopeId && !ssr) {
-    push(`const render = ${PURE_ANNOTATION}_withId(`)
-  }
   if (!ssr) {
+    if (genScopeId) {
+      push(`const render = ${PURE_ANNOTATION}_withId(`)
+    }
     push(`function render(_ctx, _cache) {`)
   } else {
+    if (genScopeId) {
+      push(`const ssrRender = ${PURE_ANNOTATION}_withId(`)
+    }
     push(`function ssrRender(_ctx, _push, _parent, _attrs) {`)
   }
   indent()
@@ -272,7 +275,7 @@ export function generate(
   deindent()
   push(`}`)
 
-  if (genScopeId && !ssr) {
+  if (genScopeId) {
     push(`)`)
   }
 
index a2a6452a7411f4f096e8f6a0de3f0f1a96a55b49..03755ab3fb02d51bfc20fe2cebff50d3dd4854f1 100644 (file)
@@ -6,14 +6,17 @@ describe('ssr: scopeId', () => {
   test('basic', () => {
     expect(
       compile(`<div><span>hello</span></div>`, {
-        scopeId
+        scopeId,
+        mode: 'module'
       }).code
     ).toMatchInlineSnapshot(`
-      "const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
+      "import { withScopeId as _withScopeId } from \\"vue\\"
+      import { ssrRenderAttrs as _ssrRenderAttrs } from \\"@vue/server-renderer\\"
+      const _withId = /*#__PURE__*/_withScopeId(\\"data-v-xxxxxxx\\")
 
-      return function ssrRender(_ctx, _push, _parent, _attrs) {
+      export const ssrRender = /*#__PURE__*/_withId(function ssrRender(_ctx, _push, _parent, _attrs) {
         _push(\`<div\${_ssrRenderAttrs(_attrs)} data-v-xxxxxxx><span data-v-xxxxxxx>hello</span></div>\`)
-      }"
+      })"
     `)
   })
 
@@ -21,17 +24,19 @@ describe('ssr: scopeId', () => {
     // should have no branching inside slot
     expect(
       compile(`<foo>foo</foo>`, {
-        scopeId
+        scopeId,
+        mode: 'module'
       }).code
     ).toMatchInlineSnapshot(`
-      "const { resolveComponent: _resolveComponent, withCtx: _withCtx, createTextVNode: _createTextVNode } = require(\\"vue\\")
-      const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
+      "import { resolveComponent as _resolveComponent, withCtx as _withCtx, createTextVNode as _createTextVNode, withScopeId as _withScopeId } from \\"vue\\"
+      import { ssrRenderComponent as _ssrRenderComponent } from \\"@vue/server-renderer\\"
+      const _withId = /*#__PURE__*/_withScopeId(\\"data-v-xxxxxxx\\")
 
-      return function ssrRender(_ctx, _push, _parent, _attrs) {
+      export const ssrRender = /*#__PURE__*/_withId(function ssrRender(_ctx, _push, _parent, _attrs) {
         const _component_foo = _resolveComponent(\\"foo\\")
 
         _push(_ssrRenderComponent(_component_foo, _attrs, {
-          default: _withCtx((_, _push, _parent, _scopeId) => {
+          default: _withId((_, _push, _parent, _scopeId) => {
             if (_push) {
               _push(\`foo\`)
             } else {
@@ -42,24 +47,26 @@ describe('ssr: scopeId', () => {
           }),
           _: 1
         }, _parent))
-      }"
+      })"
     `)
   })
 
   test('inside slots (with elements)', () => {
     expect(
       compile(`<foo><span>hello</span></foo>`, {
-        scopeId
+        scopeId,
+        mode: 'module'
       }).code
     ).toMatchInlineSnapshot(`
-      "const { resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode } = require(\\"vue\\")
-      const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
+      "import { resolveComponent as _resolveComponent, withCtx as _withCtx, createVNode as _createVNode, withScopeId as _withScopeId } from \\"vue\\"
+      import { ssrRenderComponent as _ssrRenderComponent } from \\"@vue/server-renderer\\"
+      const _withId = /*#__PURE__*/_withScopeId(\\"data-v-xxxxxxx\\")
 
-      return function ssrRender(_ctx, _push, _parent, _attrs) {
+      export const ssrRender = /*#__PURE__*/_withId(function ssrRender(_ctx, _push, _parent, _attrs) {
         const _component_foo = _resolveComponent(\\"foo\\")
 
         _push(_ssrRenderComponent(_component_foo, _attrs, {
-          default: _withCtx((_, _push, _parent, _scopeId) => {
+          default: _withId((_, _push, _parent, _scopeId) => {
             if (_push) {
               _push(\`<span data-v-xxxxxxx\${_scopeId}>hello</span>\`)
             } else {
@@ -70,29 +77,31 @@ describe('ssr: scopeId', () => {
           }),
           _: 1
         }, _parent))
-      }"
+      })"
     `)
   })
 
   test('nested slots', () => {
     expect(
       compile(`<foo><span>hello</span><bar><span/></bar></foo>`, {
-        scopeId
+        scopeId,
+        mode: 'module'
       }).code
     ).toMatchInlineSnapshot(`
-      "const { resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode } = require(\\"vue\\")
-      const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
+      "import { resolveComponent as _resolveComponent, withCtx as _withCtx, createVNode as _createVNode, withScopeId as _withScopeId } from \\"vue\\"
+      import { ssrRenderComponent as _ssrRenderComponent } from \\"@vue/server-renderer\\"
+      const _withId = /*#__PURE__*/_withScopeId(\\"data-v-xxxxxxx\\")
 
-      return function ssrRender(_ctx, _push, _parent, _attrs) {
+      export const ssrRender = /*#__PURE__*/_withId(function ssrRender(_ctx, _push, _parent, _attrs) {
         const _component_foo = _resolveComponent(\\"foo\\")
         const _component_bar = _resolveComponent(\\"bar\\")
 
         _push(_ssrRenderComponent(_component_foo, _attrs, {
-          default: _withCtx((_, _push, _parent, _scopeId) => {
+          default: _withId((_, _push, _parent, _scopeId) => {
             if (_push) {
               _push(\`<span data-v-xxxxxxx\${_scopeId}>hello</span>\`)
               _push(_ssrRenderComponent(_component_bar, null, {
-                default: _withCtx((_, _push, _parent, _scopeId) => {
+                default: _withId((_, _push, _parent, _scopeId) => {
                   if (_push) {
                     _push(\`<span data-v-xxxxxxx\${_scopeId}></span>\`)
                   } else {
@@ -107,7 +116,7 @@ describe('ssr: scopeId', () => {
               return [
                 _createVNode(\\"span\\", null, \\"hello\\"),
                 _createVNode(_component_bar, null, {
-                  default: _withCtx(() => [
+                  default: _withId(() => [
                     _createVNode(\\"span\\")
                   ]),
                   _: 1
@@ -117,7 +126,7 @@ describe('ssr: scopeId', () => {
           }),
           _: 1
         }, _parent))
-      }"
+      })"
     `)
   })
 })
index 05813b9c93e50b174d7a72902c53a8f374124218..c158f4ad8634d4a46ad057a7e101225c541507eb 100644 (file)
@@ -150,12 +150,14 @@ export function renderComponentRoot(
 
     // inherit scopeId
     const scopeId = vnode.scopeId
-    if (scopeId) {
-      root = cloneVNode(root, { [scopeId]: '' })
-    }
     const treeOwnerId = parent && parent.type.__scopeId
-    if (treeOwnerId && treeOwnerId !== scopeId) {
-      root = cloneVNode(root, { [treeOwnerId + '-s']: '' })
+    const slotScopeId =
+      treeOwnerId && treeOwnerId !== scopeId ? treeOwnerId + '-s' : null
+    if (scopeId || slotScopeId) {
+      const extras: Data = {}
+      if (scopeId) extras[scopeId] = ''
+      if (slotScopeId) extras[slotScopeId] = ''
+      root = cloneVNode(root, extras)
     }
 
     // inherit directives
index ab210c621ac44df81d59e91b8bf33b9bf3971fe1..f7985632fb3551692f7a502edebf26fbe70dcc7b 100644 (file)
@@ -12,7 +12,7 @@ import {
 } from 'vue'
 import { escapeHtml, mockWarn } from '@vue/shared'
 import { renderToString } from '../src/renderToString'
-import { ssrRenderSlot } from '../src/helpers/ssrRenderSlot'
+import { ssrRenderSlot, SSRSlot } from '../src/helpers/ssrRenderSlot'
 import { ssrRenderComponent } from '../src/helpers/ssrRenderComponent'
 
 mockWarn()
@@ -222,9 +222,9 @@ describe('ssr: renderToString', () => {
                   { msg: 'hello' },
                   {
                     // optimized slot using string push
-                    default: ({ msg }: any, push: any, _p: any) => {
+                    default: (({ msg }, push, _p) => {
                       push(`<span>${msg}</span>`)
-                    },
+                    }) as SSRSlot,
                     // important to avoid slots being normalized
                     _: 1 as any
                   },
diff --git a/packages/server-renderer/__tests__/ssrScopeId.spec.ts b/packages/server-renderer/__tests__/ssrScopeId.spec.ts
new file mode 100644 (file)
index 0000000..8b58fc6
--- /dev/null
@@ -0,0 +1,62 @@
+import { createApp, withScopeId } from 'vue'
+import { renderToString } from '../src/renderToString'
+import { ssrRenderComponent, ssrRenderAttrs, ssrRenderSlot } from '../src'
+
+describe('ssr: scoped id on component root', () => {
+  test('basic', async () => {
+    const withParentId = withScopeId('parent')
+
+    const Child = {
+      ssrRender: (ctx: any, push: any, parent: any, attrs: any) => {
+        push(`<div${ssrRenderAttrs(attrs)}></div>`)
+      }
+    }
+
+    const Comp = {
+      ssrRender: withParentId((ctx: any, push: any, parent: any) => {
+        push(ssrRenderComponent(Child), null, null, parent)
+      })
+    }
+
+    const result = await renderToString(createApp(Comp))
+    expect(result).toBe(`<div parent></div>`)
+  })
+
+  test('inside slot', async () => {
+    const withParentId = withScopeId('parent')
+
+    const Child = {
+      ssrRender: (_: any, push: any, _parent: any, attrs: any) => {
+        push(`<div${ssrRenderAttrs(attrs)} child></div>`)
+      }
+    }
+
+    const Wrapper = {
+      __scopeId: 'wrapper',
+      ssrRender: (ctx: any, push: any, parent: any) => {
+        ssrRenderSlot(ctx.$slots, 'default', {}, null, push, parent)
+      }
+    }
+
+    const Comp = {
+      ssrRender: withParentId((_: any, push: any, parent: any) => {
+        push(
+          ssrRenderComponent(
+            Wrapper,
+            null,
+            {
+              default: withParentId((_: any, push: any, parent: any) => {
+                push(ssrRenderComponent(Child, null, null, parent))
+              }),
+              _: 1
+            } as any,
+            parent
+          )
+        )
+      })
+    }
+
+    const result = await renderToString(createApp(Comp))
+    expect(result).toBe(`<!--[--><div parent wrapper-s child></div><!--]-->`)
+  })
+})
index 8c96322ab0f1b817f44c5f824d0ea6bc9545feba..6231b189525d4e232a2ededce9f2f671849bc02f 100644 (file)
@@ -1,4 +1,4 @@
-import { ComponentInternalInstance, Slot, Slots } from 'vue'
+import { ComponentInternalInstance, Slots } from 'vue'
 import { Props, PushFn, renderVNodeChildren } from '../render'
 
 export type SSRSlots = Record<string, SSRSlot>
@@ -21,13 +21,16 @@ export function ssrRenderSlot(
   push(`<!--[-->`)
   const slotFn = slots[slotName]
   if (slotFn) {
-    if (slotFn.length > 1) {
-      // only ssr-optimized slot fns accept more than 1 arguments
-      const scopeId = parentComponent && parentComponent.type.__scopeId
-      slotFn(slotProps, push, parentComponent, scopeId ? ` ${scopeId}-s` : ``)
-    } else {
+    const scopeId = parentComponent && parentComponent.type.__scopeId
+    const ret = slotFn(
+      slotProps,
+      push,
+      parentComponent,
+      scopeId ? ` ${scopeId}-s` : ``
+    )
+    if (Array.isArray(ret)) {
       // normal slot
-      renderVNodeChildren(push, (slotFn as Slot)(slotProps), parentComponent)
+      renderVNodeChildren(push, ret, parentComponent)
     }
   } else if (fallbackRenderFn) {
     fallbackRenderFn()
index 1846bffceccd22cac22619463ebe0b18a05eb068..55af4ff09bdc04bd9bdcb54026d7e5a80541b7bd 100644 (file)
@@ -109,11 +109,23 @@ function renderComponentSubTree(
 
     if (comp.ssrRender) {
       // optimized
+      // resolve fallthrough attrs
+      let attrs =
+        instance.type.inheritAttrs !== false ? instance.attrs : undefined
+
+      // inherited scopeId
+      const scopeId = instance.vnode.scopeId
+      const treeOwnerId = instance.parent && instance.parent.type.__scopeId
+      const slotScopeId =
+        treeOwnerId && treeOwnerId !== scopeId ? treeOwnerId + '-s' : null
+      if (scopeId || slotScopeId) {
+        attrs = { ...attrs }
+        if (scopeId) attrs[scopeId] = ''
+        if (slotScopeId) attrs[slotScopeId] = ''
+      }
+
       // set current rendering instance for asset resolution
       setCurrentRenderingInstance(instance)
-      // fallthrough attrs
-      const attrs =
-        instance.type.inheritAttrs !== false ? instance.attrs : undefined
       comp.ssrRender(instance.proxy, push, instance, attrs)
       setCurrentRenderingInstance(null)
     } else if (instance.render) {