]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
refactor: fix implementation of SFC :slotted id handling
authorEvan You <yyx990803@gmail.com>
Fri, 5 Mar 2021 16:10:06 +0000 (11:10 -0500)
committerEvan You <yyx990803@gmail.com>
Fri, 5 Mar 2021 23:28:12 +0000 (18:28 -0500)
fix #2892

36 files changed:
packages/compiler-core/__tests__/__snapshots__/scopeId.spec.ts.snap
packages/compiler-core/__tests__/scopeId.spec.ts
packages/compiler-core/src/codegen.ts
packages/compiler-core/src/runtimeHelpers.ts
packages/compiler-ssr/__tests__/ssrScopeId.spec.ts
packages/compiler-ssr/__tests__/ssrSlotOutlet.spec.ts
packages/compiler-ssr/src/transforms/ssrTransformComponent.ts
packages/compiler-ssr/src/transforms/ssrTransformSlotOutlet.ts
packages/runtime-core/__tests__/components/KeepAlive.spec.ts
packages/runtime-core/__tests__/helpers/renderSlot.spec.ts
packages/runtime-core/__tests__/helpers/scopeId.spec.ts [deleted file]
packages/runtime-core/__tests__/scopeId.spec.ts [new file with mode: 0644]
packages/runtime-core/__tests__/vnode.spec.ts
packages/runtime-core/src/apiInject.ts
packages/runtime-core/src/component.ts
packages/runtime-core/src/componentPublicInstance.ts
packages/runtime-core/src/componentRenderContext.ts [new file with mode: 0644]
packages/runtime-core/src/componentRenderUtils.ts
packages/runtime-core/src/componentSlots.ts
packages/runtime-core/src/components/KeepAlive.ts
packages/runtime-core/src/components/Suspense.ts
packages/runtime-core/src/components/Teleport.ts
packages/runtime-core/src/directives.ts
packages/runtime-core/src/helpers/renderSlot.ts
packages/runtime-core/src/helpers/resolveAssets.ts
packages/runtime-core/src/helpers/scopeId.ts [deleted file]
packages/runtime-core/src/helpers/withRenderContext.ts [deleted file]
packages/runtime-core/src/hydration.ts
packages/runtime-core/src/index.ts
packages/runtime-core/src/renderer.ts
packages/runtime-core/src/vnode.ts
packages/server-renderer/__tests__/render.spec.ts
packages/server-renderer/__tests__/ssrScopeId.spec.ts
packages/server-renderer/src/helpers/ssrRenderComponent.ts
packages/server-renderer/src/helpers/ssrRenderSlot.ts
packages/server-renderer/src/render.ts

index 7af32808b1725cb41af16fe0d10823617c5bb7d8..4076e61db6a57c7d5fdab2cee0058468e5384fbe 100644 (file)
@@ -1,51 +1,48 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
 exports[`scopeId compiler support should push scopeId for hoisted nodes 1`] = `
-"import { createVNode as _createVNode, toDisplayString as _toDisplayString, createTextVNode as _createTextVNode, openBlock as _openBlock, createBlock as _createBlock, withScopeId as _withScopeId, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \\"vue\\"
-const _withId = /*#__PURE__*/_withScopeId(\\"test\\")
+"import { createVNode as _createVNode, toDisplayString as _toDisplayString, createTextVNode as _createTextVNode, openBlock as _openBlock, createBlock as _createBlock, setScopeId as _setScopeId } from \\"vue\\"
 
-_pushScopeId(\\"test\\")
+_setScopeId(\\"test\\")
 const _hoisted_1 = /*#__PURE__*/_createVNode(\\"div\\", null, \\"hello\\", -1 /* HOISTED */)
 const _hoisted_2 = /*#__PURE__*/_createVNode(\\"div\\", null, \\"world\\", -1 /* HOISTED */)
-_popScopeId()
+_setScopeId(null)
 
-export const render = /*#__PURE__*/_withId((_ctx, _cache) => {
+export function render(_ctx, _cache) {
   return (_openBlock(), _createBlock(\\"div\\", null, [
     _hoisted_1,
     _createTextVNode(_toDisplayString(_ctx.foo), 1 /* TEXT */),
     _hoisted_2
   ]))
-})"
+}"
 `;
 
 exports[`scopeId compiler support should wrap default slot 1`] = `
-"import { createVNode as _createVNode, resolveComponent as _resolveComponent, withCtx as _withCtx, openBlock as _openBlock, createBlock as _createBlock, withScopeId as _withScopeId } from \\"vue\\"
-const _withId = /*#__PURE__*/_withScopeId(\\"test\\")
+"import { createVNode as _createVNode, resolveComponent as _resolveComponent, withCtx as _withCtx, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\"
 
-export const render = /*#__PURE__*/_withId((_ctx, _cache) => {
+export function render(_ctx, _cache) {
   const _component_Child = _resolveComponent(\\"Child\\")
 
   return (_openBlock(), _createBlock(_component_Child, null, {
-    default: _withId(() => [
+    default: _withCtx(() => [
       _createVNode(\\"div\\")
     ]),
     _: 1 /* STABLE */
   }))
-})"
+}"
 `;
 
 exports[`scopeId compiler support should wrap dynamic slots 1`] = `
-"import { createVNode as _createVNode, resolveComponent as _resolveComponent, withCtx as _withCtx, renderList as _renderList, createSlots as _createSlots, openBlock as _openBlock, createBlock as _createBlock, withScopeId as _withScopeId } from \\"vue\\"
-const _withId = /*#__PURE__*/_withScopeId(\\"test\\")
+"import { createVNode as _createVNode, resolveComponent as _resolveComponent, withCtx as _withCtx, renderList as _renderList, createSlots as _createSlots, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\"
 
-export const render = /*#__PURE__*/_withId((_ctx, _cache) => {
+export function render(_ctx, _cache) {
   const _component_Child = _resolveComponent(\\"Child\\")
 
   return (_openBlock(), _createBlock(_component_Child, null, _createSlots({ _: 2 /* DYNAMIC */ }, [
     (_ctx.ok)
       ? {
           name: \\"foo\\",
-          fn: _withId(() => [
+          fn: _withCtx(() => [
             _createVNode(\\"div\\")
           ])
         }
@@ -53,39 +50,29 @@ export const render = /*#__PURE__*/_withId((_ctx, _cache) => {
     _renderList(_ctx.list, (i) => {
       return {
         name: i,
-        fn: _withId(() => [
+        fn: _withCtx(() => [
           _createVNode(\\"div\\")
         ])
       }
     })
   ]), 1024 /* DYNAMIC_SLOTS */))
-})"
+}"
 `;
 
 exports[`scopeId compiler support should wrap named slots 1`] = `
-"import { toDisplayString as _toDisplayString, createTextVNode as _createTextVNode, createVNode as _createVNode, resolveComponent as _resolveComponent, withCtx as _withCtx, openBlock as _openBlock, createBlock as _createBlock, withScopeId as _withScopeId } from \\"vue\\"
-const _withId = /*#__PURE__*/_withScopeId(\\"test\\")
+"import { toDisplayString as _toDisplayString, createTextVNode as _createTextVNode, createVNode as _createVNode, resolveComponent as _resolveComponent, withCtx as _withCtx, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\"
 
-export const render = /*#__PURE__*/_withId((_ctx, _cache) => {
+export function render(_ctx, _cache) {
   const _component_Child = _resolveComponent(\\"Child\\")
 
   return (_openBlock(), _createBlock(_component_Child, null, {
-    foo: _withId(({ msg }) => [
+    foo: _withCtx(({ msg }) => [
       _createTextVNode(_toDisplayString(msg), 1 /* TEXT */)
     ]),
-    bar: _withId(() => [
+    bar: _withCtx(() => [
       _createVNode(\\"div\\")
     ]),
     _: 1 /* STABLE */
   }))
-})"
-`;
-
-exports[`scopeId compiler support should wrap render function 1`] = `
-"import { createVNode as _createVNode, openBlock as _openBlock, createBlock as _createBlock, withScopeId as _withScopeId } from \\"vue\\"
-const _withId = /*#__PURE__*/_withScopeId(\\"test\\")
-
-export const render = /*#__PURE__*/_withId((_ctx, _cache) => {
-  return (_openBlock(), _createBlock(\\"div\\"))
-})"
+}"
 `;
index 710c65bff8d27da334ab4b124d122f9a7c1df14d..3c1d6d554b76b23ae0ea8abb8543ef0767b6db3f 100644 (file)
@@ -1,12 +1,13 @@
 import { baseCompile } from '../src/compile'
-import {
-  WITH_SCOPE_ID,
-  PUSH_SCOPE_ID,
-  POP_SCOPE_ID
-} from '../src/runtimeHelpers'
+import { SET_SCOPE_ID } from '../src/runtimeHelpers'
 import { PatchFlags } from '@vue/shared'
 import { genFlagText } from './testUtils'
 
+/**
+ * Ensure all slot functions are wrapped with _withCtx
+ * which sets the currentRenderingInstance and currentScopeId when rendering
+ * the slot.
+ */
 describe('scopeId compiler support', () => {
   test('should only work in module mode', () => {
     expect(() => {
@@ -14,25 +15,12 @@ describe('scopeId compiler support', () => {
     }).toThrow(`"scopeId" option is only supported in module mode`)
   })
 
-  test('should wrap render function', () => {
-    const { ast, code } = baseCompile(`<div/>`, {
-      mode: 'module',
-      scopeId: 'test'
-    })
-    expect(ast.helpers).toContain(WITH_SCOPE_ID)
-    expect(code).toMatch(`const _withId = /*#__PURE__*/_withScopeId("test")`)
-    expect(code).toMatch(
-      `export const render = /*#__PURE__*/_withId((_ctx, _cache) => {`
-    )
-    expect(code).toMatchSnapshot()
-  })
-
   test('should wrap default slot', () => {
     const { code } = baseCompile(`<Child><div/></Child>`, {
       mode: 'module',
       scopeId: 'test'
     })
-    expect(code).toMatch(`default: _withId(() => [`)
+    expect(code).toMatch(`default: _withCtx(() => [`)
     expect(code).toMatchSnapshot()
   })
 
@@ -48,8 +36,8 @@ describe('scopeId compiler support', () => {
         scopeId: 'test'
       }
     )
-    expect(code).toMatch(`foo: _withId(({ msg }) => [`)
-    expect(code).toMatch(`bar: _withId(() => [`)
+    expect(code).toMatch(`foo: _withCtx(({ msg }) => [`)
+    expect(code).toMatch(`bar: _withCtx(() => [`)
     expect(code).toMatchSnapshot()
   })
 
@@ -65,8 +53,8 @@ describe('scopeId compiler support', () => {
         scopeId: 'test'
       }
     )
-    expect(code).toMatch(/name: "foo",\s+fn: _withId\(/)
-    expect(code).toMatch(/name: i,\s+fn: _withId\(/)
+    expect(code).toMatch(/name: "foo",\s+fn: _withCtx\(/)
+    expect(code).toMatch(/name: i,\s+fn: _withCtx\(/)
     expect(code).toMatchSnapshot()
   })
 
@@ -79,19 +67,18 @@ describe('scopeId compiler support', () => {
         hoistStatic: true
       }
     )
-    expect(ast.helpers).toContain(PUSH_SCOPE_ID)
-    expect(ast.helpers).toContain(POP_SCOPE_ID)
+    expect(ast.helpers).toContain(SET_SCOPE_ID)
     expect(ast.hoists.length).toBe(2)
     expect(code).toMatch(
       [
-        `_pushScopeId("test")`,
+        `_setScopeId("test")`,
         `const _hoisted_1 = /*#__PURE__*/_createVNode("div", null, "hello", ${genFlagText(
           PatchFlags.HOISTED
         )})`,
         `const _hoisted_2 = /*#__PURE__*/_createVNode("div", null, "world", ${genFlagText(
           PatchFlags.HOISTED
         )})`,
-        `_popScopeId()`
+        `_setScopeId(null)`
       ].join('\n')
     )
     expect(code).toMatchSnapshot()
index 20f7e33ec880a97d5022d617540f91243cf92b5d..cd572313286abed05f0020e1ace39eac375f4c66 100644 (file)
@@ -43,9 +43,7 @@ import {
   SET_BLOCK_TRACKING,
   CREATE_COMMENT,
   CREATE_TEXT,
-  PUSH_SCOPE_ID,
-  POP_SCOPE_ID,
-  WITH_SCOPE_ID,
+  SET_SCOPE_ID,
   WITH_DIRECTIVES,
   CREATE_BLOCK,
   OPEN_BLOCK,
@@ -197,12 +195,11 @@ export function generate(
     indent,
     deindent,
     newline,
-    scopeId,
     ssr
   } = context
+
   const hasHelpers = ast.helpers.length > 0
   const useWithBlock = !prefixIdentifiers && mode !== 'module'
-  const genScopeId = !__BROWSER__ && scopeId != null && mode === 'module'
   const isSetupInlined = !__BROWSER__ && !!options.inline
 
   // preambles
@@ -212,7 +209,7 @@ export function generate(
     ? createCodegenContext(ast, options)
     : context
   if (!__BROWSER__ && mode === 'module') {
-    genModulePreamble(ast, preambleContext, genScopeId, isSetupInlined)
+    genModulePreamble(ast, preambleContext, isSetupInlined)
   } else {
     genFunctionPreamble(ast, preambleContext)
   }
@@ -229,14 +226,7 @@ export function generate(
       ? args.map(arg => `${arg}: any`).join(',')
       : args.join(', ')
 
-  if (genScopeId) {
-    if (isSetupInlined) {
-      push(`${PURE_ANNOTATION}_withId(`)
-    } else {
-      push(`const ${functionName} = ${PURE_ANNOTATION}_withId(`)
-    }
-  }
-  if (isSetupInlined || genScopeId) {
+  if (isSetupInlined) {
     push(`(${signature}) => {`)
   } else {
     push(`function ${functionName}(${signature}) {`)
@@ -301,10 +291,6 @@ export function generate(
   deindent()
   push(`}`)
 
-  if (genScopeId) {
-    push(`)`)
-  }
-
   return {
     ast,
     code: context.code,
@@ -375,23 +361,20 @@ function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
 function genModulePreamble(
   ast: RootNode,
   context: CodegenContext,
-  genScopeId: boolean,
   inline?: boolean
 ) {
   const {
     push,
-    helper,
     newline,
-    scopeId,
     optimizeImports,
-    runtimeModuleName
+    runtimeModuleName,
+    scopeId,
+    mode
   } = context
 
-  if (genScopeId) {
-    ast.helpers.push(WITH_SCOPE_ID)
-    if (ast.hoists.length) {
-      ast.helpers.push(PUSH_SCOPE_ID, POP_SCOPE_ID)
-    }
+  const genScopeId = !__BROWSER__ && scopeId != null && mode === 'module'
+  if (genScopeId && ast.hoists.length) {
+    ast.helpers.push(SET_SCOPE_ID)
   }
 
   // generate import statements for helpers
@@ -434,13 +417,6 @@ function genModulePreamble(
     newline()
   }
 
-  if (genScopeId) {
-    push(
-      `const _withId = ${PURE_ANNOTATION}${helper(WITH_SCOPE_ID)}("${scopeId}")`
-    )
-    newline()
-  }
-
   genHoists(ast.hoists, context)
   newline()
 
@@ -480,7 +456,7 @@ function genHoists(hoists: (JSChildNode | null)[], context: CodegenContext) {
   // push scope Id before initializing hoisted vnodes so that these vnodes
   // get the proper scopeId as well.
   if (genScopeId) {
-    push(`${helper(PUSH_SCOPE_ID)}("${scopeId}")`)
+    push(`${helper(SET_SCOPE_ID)}("${scopeId}")`)
     newline()
   }
 
@@ -493,7 +469,7 @@ function genHoists(hoists: (JSChildNode | null)[], context: CodegenContext) {
   })
 
   if (genScopeId) {
-    push(`${helper(POP_SCOPE_ID)}()`)
+    push(`${helper(SET_SCOPE_ID)}(null)`)
     newline()
   }
   context.pure = false
@@ -817,15 +793,11 @@ function genFunctionExpression(
   node: FunctionExpression,
   context: CodegenContext
 ) {
-  const { push, indent, deindent, scopeId, mode } = context
+  const { push, indent, deindent } = context
   const { params, returns, body, newline, isSlot } = node
-  // slot functions also need to push scopeId before rendering its content
-  const genScopeId =
-    !__BROWSER__ && isSlot && scopeId != null && mode !== 'function'
 
-  if (genScopeId) {
-    push(`_withId(`)
-  } else if (isSlot) {
+  if (isSlot) {
+    // wrap slot functions with owner context
     push(`_${helperNameMap[WITH_CTX]}(`)
   }
   push(`(`, node)
@@ -855,7 +827,7 @@ function genFunctionExpression(
     deindent()
     push(`}`)
   }
-  if (genScopeId || isSlot) {
+  if (isSlot) {
     push(`)`)
   }
 }
index f40c94c3d89efe7ae312c8d642e4e749a694c47a..5172b8c7f95823d27189069f9eadd0f0b1c9cedb 100644 (file)
@@ -25,9 +25,7 @@ export const CAMELIZE = Symbol(__DEV__ ? `camelize` : ``)
 export const CAPITALIZE = Symbol(__DEV__ ? `capitalize` : ``)
 export const TO_HANDLER_KEY = Symbol(__DEV__ ? `toHandlerKey` : ``)
 export const SET_BLOCK_TRACKING = Symbol(__DEV__ ? `setBlockTracking` : ``)
-export const PUSH_SCOPE_ID = Symbol(__DEV__ ? `pushScopeId` : ``)
-export const POP_SCOPE_ID = Symbol(__DEV__ ? `popScopeId` : ``)
-export const WITH_SCOPE_ID = Symbol(__DEV__ ? `withScopeId` : ``)
+export const SET_SCOPE_ID = Symbol(__DEV__ ? `setScopeId` : ``)
 export const WITH_CTX = Symbol(__DEV__ ? `withCtx` : ``)
 export const UNREF = Symbol(__DEV__ ? `unref` : ``)
 export const IS_REF = Symbol(__DEV__ ? `isRef` : ``)
@@ -61,9 +59,7 @@ export const helperNameMap: any = {
   [CAPITALIZE]: `capitalize`,
   [TO_HANDLER_KEY]: `toHandlerKey`,
   [SET_BLOCK_TRACKING]: `setBlockTracking`,
-  [PUSH_SCOPE_ID]: `pushScopeId`,
-  [POP_SCOPE_ID]: `popScopeId`,
-  [WITH_SCOPE_ID]: `withScopeId`,
+  [SET_SCOPE_ID]: `setScopeId`,
   [WITH_CTX]: `withCtx`,
   [UNREF]: `unref`,
   [IS_REF]: `isRef`
index 5d7951c1f4825f90a15245b2bfd6e2169896e3a3..954a7d44fbdc5cf56e49d5c585d1b55a61a77fa0 100644 (file)
@@ -10,13 +10,11 @@ describe('ssr: scopeId', () => {
         mode: 'module'
       }).code
     ).toMatchInlineSnapshot(`
-      "import { withScopeId as _withScopeId } from \\"vue\\"
-      import { ssrRenderAttrs as _ssrRenderAttrs } from \\"@vue/server-renderer\\"
-      const _withId = /*#__PURE__*/_withScopeId(\\"data-v-xxxxxxx\\")
+      "import { ssrRenderAttrs as _ssrRenderAttrs } from \\"@vue/server-renderer\\"
 
-      export const ssrRender = /*#__PURE__*/_withId((_ctx, _push, _parent, _attrs) => {
+      export function ssrRender(_ctx, _push, _parent, _attrs) {
         _push(\`<div\${_ssrRenderAttrs(_attrs)} data-v-xxxxxxx><span data-v-xxxxxxx>hello</span></div>\`)
-      })"
+      }"
     `)
   })
 
@@ -28,15 +26,14 @@ describe('ssr: scopeId', () => {
         mode: 'module'
       }).code
     ).toMatchInlineSnapshot(`
-      "import { resolveComponent as _resolveComponent, withCtx as _withCtx, createTextVNode as _createTextVNode, withScopeId as _withScopeId } from \\"vue\\"
+      "import { resolveComponent as _resolveComponent, withCtx as _withCtx, createTextVNode as _createTextVNode } from \\"vue\\"
       import { ssrRenderComponent as _ssrRenderComponent } from \\"@vue/server-renderer\\"
-      const _withId = /*#__PURE__*/_withScopeId(\\"data-v-xxxxxxx\\")
 
-      export const ssrRender = /*#__PURE__*/_withId((_ctx, _push, _parent, _attrs) => {
+      export function ssrRender(_ctx, _push, _parent, _attrs) {
         const _component_foo = _resolveComponent(\\"foo\\")
 
         _push(_ssrRenderComponent(_component_foo, _attrs, {
-          default: _withId((_, _push, _parent, _scopeId) => {
+          default: _withCtx((_, _push, _parent, _scopeId) => {
             if (_push) {
               _push(\`foo\`)
             } else {
@@ -47,7 +44,7 @@ describe('ssr: scopeId', () => {
           }),
           _: 1 /* STABLE */
         }, _parent))
-      })"
+      }"
     `)
   })
 
@@ -58,15 +55,14 @@ describe('ssr: scopeId', () => {
         mode: 'module'
       }).code
     ).toMatchInlineSnapshot(`
-      "import { resolveComponent as _resolveComponent, withCtx as _withCtx, createVNode as _createVNode, withScopeId as _withScopeId } from \\"vue\\"
+      "import { resolveComponent as _resolveComponent, withCtx as _withCtx, createVNode as _createVNode } from \\"vue\\"
       import { ssrRenderComponent as _ssrRenderComponent } from \\"@vue/server-renderer\\"
-      const _withId = /*#__PURE__*/_withScopeId(\\"data-v-xxxxxxx\\")
 
-      export const ssrRender = /*#__PURE__*/_withId((_ctx, _push, _parent, _attrs) => {
+      export function ssrRender(_ctx, _push, _parent, _attrs) {
         const _component_foo = _resolveComponent(\\"foo\\")
 
         _push(_ssrRenderComponent(_component_foo, _attrs, {
-          default: _withId((_, _push, _parent, _scopeId) => {
+          default: _withCtx((_, _push, _parent, _scopeId) => {
             if (_push) {
               _push(\`<span data-v-xxxxxxx\${_scopeId}>hello</span>\`)
             } else {
@@ -77,7 +73,7 @@ describe('ssr: scopeId', () => {
           }),
           _: 1 /* STABLE */
         }, _parent))
-      })"
+      }"
     `)
   })
 
@@ -88,20 +84,19 @@ describe('ssr: scopeId', () => {
         mode: 'module'
       }).code
     ).toMatchInlineSnapshot(`
-      "import { resolveComponent as _resolveComponent, withCtx as _withCtx, createVNode as _createVNode, withScopeId as _withScopeId } from \\"vue\\"
+      "import { resolveComponent as _resolveComponent, withCtx as _withCtx, createVNode as _createVNode } from \\"vue\\"
       import { ssrRenderComponent as _ssrRenderComponent } from \\"@vue/server-renderer\\"
-      const _withId = /*#__PURE__*/_withScopeId(\\"data-v-xxxxxxx\\")
 
-      export const ssrRender = /*#__PURE__*/_withId((_ctx, _push, _parent, _attrs) => {
+      export function ssrRender(_ctx, _push, _parent, _attrs) {
         const _component_foo = _resolveComponent(\\"foo\\")
         const _component_bar = _resolveComponent(\\"bar\\")
 
         _push(_ssrRenderComponent(_component_foo, _attrs, {
-          default: _withId((_, _push, _parent, _scopeId) => {
+          default: _withCtx((_, _push, _parent, _scopeId) => {
             if (_push) {
               _push(\`<span data-v-xxxxxxx\${_scopeId}>hello</span>\`)
               _push(_ssrRenderComponent(_component_bar, null, {
-                default: _withId((_, _push, _parent, _scopeId) => {
+                default: _withCtx((_, _push, _parent, _scopeId) => {
                   if (_push) {
                     _push(\`<span data-v-xxxxxxx\${_scopeId}></span>\`)
                   } else {
@@ -111,12 +106,12 @@ describe('ssr: scopeId', () => {
                   }
                 }),
                 _: 1 /* STABLE */
-              }, _parent))
+              }, _parent, _scopeId))
             } else {
               return [
                 _createVNode(\\"span\\", null, \\"hello\\"),
                 _createVNode(_component_bar, null, {
-                  default: _withId(() => [
+                  default: _withCtx(() => [
                     _createVNode(\\"span\\")
                   ]),
                   _: 1 /* STABLE */
@@ -126,7 +121,7 @@ describe('ssr: scopeId', () => {
           }),
           _: 1 /* STABLE */
         }, _parent))
-      })"
+      }"
     `)
   })
 })
index 668a80e878994ed7a8ba2f997fba4f3fd1ed90cc..2219ff07725934775506aa4052056c15abd8a4e4 100644 (file)
@@ -6,7 +6,7 @@ describe('ssr: <slot>', () => {
       "const { ssrRenderSlot: _ssrRenderSlot } = require(\\"@vue/server-renderer\\")
 
       return function ssrRender(_ctx, _push, _parent, _attrs) {
-        _ssrRenderSlot(_ctx.$slots, \\"default\\", {}, null, _push, _parent)
+        _ssrRenderSlot(_ctx.$slots, \\"default\\", {}, null, _push, _parent, null)
       }"
     `)
   })
@@ -16,7 +16,7 @@ describe('ssr: <slot>', () => {
       "const { ssrRenderSlot: _ssrRenderSlot } = require(\\"@vue/server-renderer\\")
 
       return function ssrRender(_ctx, _push, _parent, _attrs) {
-        _ssrRenderSlot(_ctx.$slots, \\"foo\\", {}, null, _push, _parent)
+        _ssrRenderSlot(_ctx.$slots, \\"foo\\", {}, null, _push, _parent, null)
       }"
     `)
   })
@@ -26,7 +26,7 @@ describe('ssr: <slot>', () => {
       "const { ssrRenderSlot: _ssrRenderSlot } = require(\\"@vue/server-renderer\\")
 
       return function ssrRender(_ctx, _push, _parent, _attrs) {
-        _ssrRenderSlot(_ctx.$slots, _ctx.bar.baz, {}, null, _push, _parent)
+        _ssrRenderSlot(_ctx.$slots, _ctx.bar.baz, {}, null, _push, _parent, null)
       }"
     `)
   })
@@ -40,7 +40,7 @@ describe('ssr: <slot>', () => {
         _ssrRenderSlot(_ctx.$slots, \\"foo\\", {
           p: 1,
           bar: \\"2\\"
-        }, null, _push, _parent)
+        }, null, _push, _parent, null)
       }"
     `)
   })
@@ -53,7 +53,49 @@ describe('ssr: <slot>', () => {
       return function ssrRender(_ctx, _push, _parent, _attrs) {
         _ssrRenderSlot(_ctx.$slots, \\"default\\", {}, () => {
           _push(\`some \${_ssrInterpolate(_ctx.fallback)} content\`)
-        }, _push, _parent)
+        }, _push, _parent, null)
+      }"
+    `)
+  })
+
+  test('with scopeId', async () => {
+    expect(
+      compile(`<slot/>`, {
+        scopeId: 'hello'
+      }).code
+    ).toMatchInlineSnapshot(`
+      "const { ssrRenderSlot: _ssrRenderSlot } = require(\\"@vue/server-renderer\\")
+
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
+        _ssrRenderSlot(_ctx.$slots, \\"default\\", {}, null, _push, _parent, \\"hello-s\\")
+      }"
+    `)
+  })
+
+  test('with forwarded scopeId', async () => {
+    expect(
+      compile(`<Comp><slot/></Comp>`, {
+        scopeId: 'hello'
+      }).code
+    ).toMatchInlineSnapshot(`
+      "const { resolveComponent: _resolveComponent, withCtx: _withCtx, renderSlot: _renderSlot } = require(\\"vue\\")
+      const { ssrRenderSlot: _ssrRenderSlot, ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
+
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
+        const _component_Comp = _resolveComponent(\\"Comp\\")
+
+        _push(_ssrRenderComponent(_component_Comp, _attrs, {
+          default: _withCtx((_, _push, _parent, _scopeId) => {
+            if (_push) {
+              _ssrRenderSlot(_ctx.$slots, \\"default\\", {}, null, _push, _parent, \\"hello-s\\" + _scopeId)
+            } else {
+              return [
+                _renderSlot(_ctx.$slots, \\"default\\")
+              ]
+            }
+          }),
+          _: 3 /* FORWARDED */
+        }, _parent))
       }"
     `)
   })
index 5acf8000b17a0de36c0691a9c58fac18acc4f547..de96b7efc677d0515eadbbedfb4f36f2737ff459 100644 (file)
@@ -203,6 +203,12 @@ export function ssrProcessComponent(
         vnodeBranch
       )
     }
+
+    // component is inside a slot, inherit slot scope Id
+    if (context.withSlotScopeId) {
+      node.ssrCodegenNode!.arguments.push(`_scopeId`)
+    }
+
     if (typeof component === 'string') {
       // static component
       context.pushStatement(
index b40d17ab4787c2035f96b67690bd1743a7987793..b2b2de4f5fdaaba7d67a007bfff390d7a5a63ef5 100644 (file)
@@ -21,9 +21,11 @@ export const ssrTransformSlotOutlet: NodeTransform = (node, context) => {
         `_ctx.$slots`,
         slotName,
         slotProps || `{}`,
-        `null`, // fallback content placeholder.
+        // fallback content placeholder. will be replaced in the process phase
+        `null`,
         `_push`,
-        `_parent`
+        `_parent`,
+        context.scopeId ? `"${context.scopeId}-s"` : `null`
       ]
     )
   }
@@ -34,6 +36,7 @@ export function ssrProcessSlotOutlet(
   context: SSRTransformContext
 ) {
   const renderCall = node.ssrCodegenNode!
+
   // has fallback content
   if (node.children.length) {
     const fallbackRenderFn = createFunctionExpression([])
@@ -41,5 +44,13 @@ export function ssrProcessSlotOutlet(
     // _renderSlot(slots, name, props, fallback, ...)
     renderCall.arguments[3] = fallbackRenderFn
   }
+
+  // Forwarded <slot/>. Add slot scope id
+  if (context.withSlotScopeId) {
+    const scopeId = renderCall.arguments[6] as string
+    renderCall.arguments[6] =
+      scopeId === `null` ? `_scopeId` : `${scopeId} + _scopeId`
+  }
+
   context.pushStatement(node.ssrCodegenNode!)
 }
index 0b3e7f5938acc4adb9b1519f59759fc4c6524d7d..1cc7fe01effce8c33af4ea85ac021d7da4d94c48 100644 (file)
@@ -14,8 +14,7 @@ import {
   ComponentPublicInstance,
   Ref,
   cloneVNode,
-  provide,
-  withScopeId
+  provide
 } from '@vue/runtime-test'
 import { KeepAliveProps } from '../../src/components/KeepAlive'
 
@@ -804,14 +803,13 @@ describe('KeepAlive', () => {
   test('should work with cloned root due to scopeId / fallthrough attrs', async () => {
     const viewRef = ref('one')
     const instanceRef = ref<any>(null)
-    const withId = withScopeId('foo')
     const App = {
       __scopeId: 'foo',
-      render: withId(() => {
+      render: () => {
         return h(KeepAlive, null, {
           default: () => h(views[viewRef.value], { ref: instanceRef })
         })
-      })
+      }
     }
     render(h(App), root)
     expect(serializeInner(root)).toBe(`<div foo>one</div>`)
index 99f2292b771cd23744f72e17b97732e047b14d43..0cc8f3babf533b2632c7f8a787fc1117cd460e03 100644 (file)
@@ -8,7 +8,7 @@ import {
   Fragment,
   createCommentVNode
 } from '../../src'
-import { PatchFlags } from '@vue/shared/src'
+import { PatchFlags } from '@vue/shared'
 
 describe('renderSlot', () => {
   it('should render slot', () => {
@@ -37,7 +37,7 @@ describe('renderSlot', () => {
         return [createVNode('div', null, 'foo', PatchFlags.TEXT)]
       },
       // mock instance
-      {} as any
+      { type: {} } as any
     )
 
     // manual invocation should not track
diff --git a/packages/runtime-core/__tests__/helpers/scopeId.spec.ts b/packages/runtime-core/__tests__/helpers/scopeId.spec.ts
deleted file mode 100644 (file)
index f570c7f..0000000
+++ /dev/null
@@ -1,105 +0,0 @@
-import { withScopeId } from '../../src/helpers/scopeId'
-import { h, render, nodeOps, serializeInner } from '@vue/runtime-test'
-
-describe('scopeId runtime support', () => {
-  const withParentId = withScopeId('parent')
-  const withChildId = withScopeId('child')
-
-  test('should attach scopeId', () => {
-    const App = {
-      __scopeId: 'parent',
-      render: withParentId(() => {
-        return h('div', [h('div')])
-      })
-    }
-    const root = nodeOps.createElement('div')
-    render(h(App), root)
-    expect(serializeInner(root)).toBe(`<div parent><div parent></div></div>`)
-  })
-
-  test('should attach scopeId to components in parent component', () => {
-    const Child = {
-      __scopeId: 'child',
-      render: withChildId(() => {
-        return h('div')
-      })
-    }
-    const App = {
-      __scopeId: 'parent',
-      render: withParentId(() => {
-        return h('div', [h(Child)])
-      })
-    }
-
-    const root = nodeOps.createElement('div')
-    render(h(App), root)
-    expect(serializeInner(root)).toBe(
-      `<div parent><div child parent></div></div>`
-    )
-  })
-
-  test('should work on slots', () => {
-    const Child = {
-      __scopeId: 'child',
-      render: withChildId(function(this: any) {
-        return h('div', this.$slots.default())
-      })
-    }
-    const withChild2Id = withScopeId('child2')
-    const Child2 = {
-      __scopeId: 'child2',
-      render: withChild2Id(() => h('span'))
-    }
-    const App = {
-      __scopeId: 'parent',
-      render: withParentId(() => {
-        return h(
-          Child,
-          withParentId(() => {
-            return [h('div'), h(Child2)]
-          })
-        )
-      })
-    }
-    const root = nodeOps.createElement('div')
-    render(h(App), root)
-    // slot content should have:
-    // - scopeId from parent
-    // - slotted scopeId (with `-s` postfix) from child (the tree owner)
-    expect(serializeInner(root)).toBe(
-      `<div child parent>` +
-        `<div parent child-s></div>` +
-        // component inside slot should have:
-        // - scopeId from template context
-        // - slotted scopeId from slot owner
-        // - its own scopeId
-        `<span child2 parent child-s></span>` +
-        `</div>`
-    )
-  })
-
-  // #1988
-  test('should inherit scopeId through nested HOCs with inheritAttrs: false', () => {
-    const withParentId = withScopeId('parent')
-    const App = {
-      __scopeId: 'parent',
-      render: withParentId(() => {
-        return h(Child)
-      })
-    }
-
-    function Child() {
-      return h(Child2, { class: 'foo' })
-    }
-
-    function Child2() {
-      return h('div')
-    }
-    Child2.inheritAttrs = false
-
-    const root = nodeOps.createElement('div')
-    render(h(App), root)
-
-    expect(serializeInner(root)).toBe(`<div parent></div>`)
-  })
-})
diff --git a/packages/runtime-core/__tests__/scopeId.spec.ts b/packages/runtime-core/__tests__/scopeId.spec.ts
new file mode 100644 (file)
index 0000000..e81af6c
--- /dev/null
@@ -0,0 +1,178 @@
+import {
+  h,
+  render,
+  nodeOps,
+  serializeInner,
+  renderSlot
+} from '@vue/runtime-test'
+import { setScopeId, withCtx } from '../src/componentRenderContext'
+
+describe('scopeId runtime support', () => {
+  test('should attach scopeId', () => {
+    const App = {
+      __scopeId: 'parent',
+      render: () => h('div', [h('div')])
+    }
+    const root = nodeOps.createElement('div')
+    render(h(App), root)
+    expect(serializeInner(root)).toBe(`<div parent><div parent></div></div>`)
+  })
+
+  test('should attach scopeId to components in parent component', () => {
+    const Child = {
+      __scopeId: 'child',
+      render: () => h('div')
+    }
+    const App = {
+      __scopeId: 'parent',
+      render: () => h('div', [h(Child)])
+    }
+
+    const root = nodeOps.createElement('div')
+    render(h(App), root)
+    expect(serializeInner(root)).toBe(
+      `<div parent><div child parent></div></div>`
+    )
+  })
+
+  // :slotted basic
+  test('should work on slots', () => {
+    const Child = {
+      __scopeId: 'child',
+      render(this: any) {
+        return h('div', renderSlot(this.$slots, 'default'))
+      }
+    }
+    const Child2 = {
+      __scopeId: 'child2',
+      render: () => h('span')
+    }
+    const App = {
+      __scopeId: 'parent',
+      render: () => {
+        return h(
+          Child,
+          withCtx(() => {
+            return [h('div'), h(Child2)]
+          })
+        )
+      }
+    }
+    const root = nodeOps.createElement('div')
+    render(h(App), root)
+    // slot content should have:
+    // - scopeId from parent
+    // - slotted scopeId (with `-s` postfix) from child (the tree owner)
+    expect(serializeInner(root)).toBe(
+      `<div child parent>` +
+        `<div parent child-s></div>` +
+        // component inside slot should have:
+        // - scopeId from template context
+        // - slotted scopeId from slot owner
+        // - its own scopeId
+        `<span child2 parent child-s></span>` +
+        `</div>`
+    )
+  })
+
+  // #2892
+  test(':slotted on forwarded slots', async () => {
+    const Wrapper = {
+      __scopeId: 'wrapper',
+      render(this: any) {
+        // <div class="wrapper"><slot/></div>
+        return h('div', { class: 'wrapper' }, [
+          renderSlot(this.$slots, 'default')
+        ])
+      }
+    }
+
+    const Slotted = {
+      __scopeId: 'slotted',
+      render(this: any) {
+        // <Wrapper><slot/></Wrapper>
+        return h(Wrapper, null, {
+          default: withCtx(() => [renderSlot(this.$slots, 'default')])
+        })
+      }
+    }
+
+    // simulate hoisted node
+    setScopeId('root')
+    const hoisted = h('div', 'hoisted')
+    setScopeId(null)
+
+    const Root = {
+      __scopeId: 'root',
+      render(this: any) {
+        // <Slotted><div>hoisted</div><div>{{ dynamic }}</div></Slotted>
+        return h(Slotted, null, {
+          default: withCtx(() => {
+            return [hoisted, h('div', 'dynamic')]
+          })
+        })
+      }
+    }
+
+    const root = nodeOps.createElement('div')
+    render(h(Root), root)
+    expect(serializeInner(root)).toBe(
+      `<div class="wrapper" wrapper slotted root>` +
+        `<div root wrapper-s slotted-s>hoisted</div>` +
+        `<div root wrapper-s slotted-s>dynamic</div>` +
+        `</div>`
+    )
+
+    const Root2 = {
+      __scopeId: 'root',
+      render(this: any) {
+        // <Slotted>
+        //  <Wrapper>
+        //    <div>hoisted</div><div>{{ dynamic }}</div>
+        //  </Wrapper>
+        // </Slotted>
+        return h(Slotted, null, {
+          default: withCtx(() => [
+            h(Wrapper, null, {
+              default: withCtx(() => [hoisted, h('div', 'dynamic')])
+            })
+          ])
+        })
+      }
+    }
+    const root2 = nodeOps.createElement('div')
+    render(h(Root2), root2)
+    expect(serializeInner(root2)).toBe(
+      `<div class="wrapper" wrapper slotted root>` +
+        `<div class="wrapper" wrapper root wrapper-s slotted-s>` +
+        `<div root wrapper-s>hoisted</div>` +
+        `<div root wrapper-s>dynamic</div>` +
+        `</div>` +
+        `</div>`
+    )
+  })
+
+  // #1988
+  test('should inherit scopeId through nested HOCs with inheritAttrs: false', () => {
+    const App = {
+      __scopeId: 'parent',
+      render: () => {
+        return h(Child)
+      }
+    }
+
+    function Child() {
+      return h(Child2, { class: 'foo' })
+    }
+
+    function Child2() {
+      return h('div')
+    }
+    Child2.inheritAttrs = false
+
+    const root = nodeOps.createElement('div')
+    render(h(App), root)
+
+    expect(serializeInner(root)).toBe(`<div parent></div>`)
+  })
+})
index 56f1e7b430c2ad8a9f5eefb74d14336d7ecb5565..b5a501053174fb3b5e4b83b7ba3835dbc0f5a851 100644 (file)
@@ -14,7 +14,7 @@ import { Data } from '../src/component'
 import { ShapeFlags, PatchFlags } from '@vue/shared'
 import { h, reactive, isReactive, setBlockTracking } from '../src'
 import { createApp, nodeOps, serializeInner } from '@vue/runtime-test'
-import { setCurrentRenderingInstance } from '../src/componentRenderUtils'
+import { setCurrentRenderingInstance } from '../src/componentRenderContext'
 
 describe('vnode', () => {
   test('create with just tag', () => {
@@ -231,8 +231,8 @@ describe('vnode', () => {
 
   // ref normalizes to [currentRenderingInstance, ref]
   test('cloneVNode ref normalization', () => {
-    const mockInstance1 = {} as any
-    const mockInstance2 = {} as any
+    const mockInstance1 = { type: {} } as any
+    const mockInstance2 = { type: {} } as any
 
     setCurrentRenderingInstance(mockInstance1)
     const original = createVNode('div', { ref: 'foo' })
@@ -272,8 +272,8 @@ describe('vnode', () => {
   })
 
   test('cloneVNode ref merging', () => {
-    const mockInstance1 = {} as any
-    const mockInstance2 = {} as any
+    const mockInstance1 = { type: {} } as any
+    const mockInstance2 = { type: {} } as any
 
     setCurrentRenderingInstance(mockInstance1)
     const original = createVNode('div', { ref: 'foo' })
index 402113cd1b1c7d446b80bebff223493435827bc4..a1ec6126be68cf65a6bb3e2132daaf978b102b09 100644 (file)
@@ -1,6 +1,6 @@
 import { isFunction } from '@vue/shared'
 import { currentInstance } from './component'
-import { currentRenderingInstance } from './componentRenderUtils'
+import { currentRenderingInstance } from './componentRenderContext'
 import { warn } from './warning'
 
 export interface InjectionKey<T> extends Symbol {}
index 9b142036219f66311f98fdd3fcfe8544b4bb6fc0..bfb7736b4109cc1a641cbaec7ce40b330f14ae29 100644 (file)
@@ -51,10 +51,8 @@ import {
 } from '@vue/shared'
 import { SuspenseBoundary } from './components/Suspense'
 import { CompilerOptions } from '@vue/compiler-core'
-import {
-  currentRenderingInstance,
-  markAttrsAccessed
-} from './componentRenderUtils'
+import { markAttrsAccessed } from './componentRenderUtils'
+import { currentRenderingInstance } from './componentRenderContext'
 import { startMeasure, endMeasure } from './profiling'
 
 export type Data = Record<string, unknown>
index c493fea0b908bf2fa951de4b9250f170ca4ec719..bbdb033609267c65eb98de6a916d70d4d15c1b3a 100644 (file)
@@ -35,10 +35,8 @@ import {
 } from './componentOptions'
 import { EmitsOptions, EmitFn } from './componentEmits'
 import { Slots } from './componentSlots'
-import {
-  currentRenderingInstance,
-  markAttrsAccessed
-} from './componentRenderUtils'
+import { markAttrsAccessed } from './componentRenderUtils'
+import { currentRenderingInstance } from './componentRenderContext'
 import { warn } from './warning'
 import { UnionToIntersection } from './helpers/typeUtils'
 
diff --git a/packages/runtime-core/src/componentRenderContext.ts b/packages/runtime-core/src/componentRenderContext.ts
new file mode 100644 (file)
index 0000000..78297dc
--- /dev/null
@@ -0,0 +1,57 @@
+import { ComponentInternalInstance } from './component'
+import { isRenderingCompiledSlot } from './helpers/renderSlot'
+import { closeBlock, openBlock } from './vnode'
+
+/**
+ * mark the current rendering instance for asset resolution (e.g.
+ * resolveComponent, resolveDirective) during render
+ */
+export let currentRenderingInstance: ComponentInternalInstance | null = null
+export let currentScopeId: string | null = null
+
+export function setCurrentRenderingInstance(
+  instance: ComponentInternalInstance | null
+) {
+  currentRenderingInstance = instance
+  currentScopeId = (instance && instance.type.__scopeId) || null
+}
+
+/**
+ * Set scope id when creating hoisted vnodes.
+ * @private compiler helper
+ */
+export function setScopeId(id: string | null) {
+  currentScopeId = id
+}
+
+/**
+ * Wrap a slot function to memoize current rendering instance
+ * @private compiler helper
+ */
+export function withCtx(
+  fn: Function,
+  ctx: ComponentInternalInstance | null = currentRenderingInstance
+) {
+  if (!ctx) return fn
+  const renderFnWithContext = (...args: any[]) => {
+    // If a user calls a compiled slot inside a template expression (#1745), it
+    // can mess up block tracking, so by default we need to push a null block to
+    // avoid that. This isn't necessary if rendering a compiled `<slot>`.
+    if (!isRenderingCompiledSlot) {
+      openBlock(true /* null block that disables tracking */)
+    }
+    const prevInstance = currentRenderingInstance
+    setCurrentRenderingInstance(ctx)
+    const res = fn(...args)
+    setCurrentRenderingInstance(prevInstance)
+    if (!isRenderingCompiledSlot) {
+      closeBlock()
+    }
+    return res
+  }
+  // mark this as a compiled slot function.
+  // this is used in vnode.ts -> normalizeChildren() to set the slot
+  // rendering flag.
+  renderFnWithContext._c = true
+  return renderFnWithContext
+}
index c1454e96c1e87fec8816b503722887637c9751c2..89cdcf19c95b37efb27829133119f3be6995d7bb 100644 (file)
@@ -18,18 +18,7 @@ import { warn } from './warning'
 import { isHmrUpdating } from './hmr'
 import { NormalizedProps } from './componentProps'
 import { isEmitListener } from './componentEmits'
-
-/**
- * mark the current rendering instance for asset resolution (e.g.
- * resolveComponent, resolveDirective) during render
- */
-export let currentRenderingInstance: ComponentInternalInstance | null = null
-
-export function setCurrentRenderingInstance(
-  instance: ComponentInternalInstance | null
-) {
-  currentRenderingInstance = instance
-}
+import { setCurrentRenderingInstance } from './componentRenderContext'
 
 /**
  * dev only flag to track whether $attrs was used during render.
@@ -63,7 +52,7 @@ export function renderComponentRoot(
   } = instance
 
   let result
-  currentRenderingInstance = instance
+  setCurrentRenderingInstance(instance)
   if (__DEV__) {
     accessedAttrs = false
   }
@@ -215,8 +204,8 @@ export function renderComponentRoot(
     handleError(err, instance, ErrorCodes.RENDER_FUNCTION)
     result = createVNode(Comment)
   }
-  currentRenderingInstance = null
 
+  setCurrentRenderingInstance(null)
   return result
 }
 
index 4a6a1b75375932fb394804a66d742ed80471fb48..fbb009697479146a1f29c49443644f8dfd445d27 100644 (file)
@@ -17,7 +17,7 @@ import {
 } from '@vue/shared'
 import { warn } from './warning'
 import { isKeepAlive } from './components/KeepAlive'
-import { withCtx } from './helpers/withRenderContext'
+import { withCtx } from './componentRenderContext'
 import { isHmrUpdating } from './hmr'
 
 export type Slot = (...args: any[]) => VNode[]
index 6ad53fda72ee08d5ee132d6748fab431597dcba3..b6d04cbbac36f253800f00d6658cf8df7363138e 100644 (file)
@@ -112,6 +112,7 @@ const KeepAliveImpl = {
         instance,
         parentSuspense,
         isSVG,
+        vnode.slotScopeIds,
         optimized
       )
       queuePostRenderEffect(() => {
index 1b5adfed3505e52423e29ceeb249d6c12b8fa985..0ec78125b20566b8f480a13d74ab6c2965765b44 100644 (file)
@@ -46,6 +46,7 @@ export const SuspenseImpl = {
     parentComponent: ComponentInternalInstance | null,
     parentSuspense: SuspenseBoundary | null,
     isSVG: boolean,
+    slotScopeIds: string[] | null,
     optimized: boolean,
     // platform-specific impl passed from renderer
     rendererInternals: RendererInternals
@@ -58,6 +59,7 @@ export const SuspenseImpl = {
         parentComponent,
         parentSuspense,
         isSVG,
+        slotScopeIds,
         optimized,
         rendererInternals
       )
@@ -69,6 +71,8 @@ export const SuspenseImpl = {
         anchor,
         parentComponent,
         isSVG,
+        slotScopeIds,
+        optimized,
         rendererInternals
       )
     }
@@ -92,6 +96,7 @@ function mountSuspense(
   parentComponent: ComponentInternalInstance | null,
   parentSuspense: SuspenseBoundary | null,
   isSVG: boolean,
+  slotScopeIds: string[] | null,
   optimized: boolean,
   rendererInternals: RendererInternals
 ) {
@@ -108,6 +113,7 @@ function mountSuspense(
     hiddenContainer,
     anchor,
     isSVG,
+    slotScopeIds,
     optimized,
     rendererInternals
   ))
@@ -120,7 +126,8 @@ function mountSuspense(
     null,
     parentComponent,
     suspense,
-    isSVG
+    isSVG,
+    slotScopeIds
   )
   // now check if we have encountered any async deps
   if (suspense.deps > 0) {
@@ -133,7 +140,8 @@ function mountSuspense(
       anchor,
       parentComponent,
       null, // fallback tree will not have suspense context
-      isSVG
+      isSVG,
+      slotScopeIds
     )
     setActiveBranch(suspense, vnode.ssFallback!)
   } else {
@@ -149,6 +157,8 @@ function patchSuspense(
   anchor: RendererNode | null,
   parentComponent: ComponentInternalInstance | null,
   isSVG: boolean,
+  slotScopeIds: string[] | null,
+  optimized: boolean,
   { p: patch, um: unmount, o: { createElement } }: RendererInternals
 ) {
   const suspense = (n2.suspense = n1.suspense)!
@@ -169,7 +179,9 @@ function patchSuspense(
         null,
         parentComponent,
         suspense,
-        isSVG
+        isSVG,
+        slotScopeIds,
+        optimized
       )
       if (suspense.deps <= 0) {
         suspense.resolve()
@@ -181,7 +193,9 @@ function patchSuspense(
           anchor,
           parentComponent,
           null, // fallback tree will not have suspense context
-          isSVG
+          isSVG,
+          slotScopeIds,
+          optimized
         )
         setActiveBranch(suspense, newFallback)
       }
@@ -214,7 +228,9 @@ function patchSuspense(
           null,
           parentComponent,
           suspense,
-          isSVG
+          isSVG,
+          slotScopeIds,
+          optimized
         )
         if (suspense.deps <= 0) {
           suspense.resolve()
@@ -226,7 +242,9 @@ function patchSuspense(
             anchor,
             parentComponent,
             null, // fallback tree will not have suspense context
-            isSVG
+            isSVG,
+            slotScopeIds,
+            optimized
           )
           setActiveBranch(suspense, newFallback)
         }
@@ -239,7 +257,9 @@ function patchSuspense(
           anchor,
           parentComponent,
           suspense,
-          isSVG
+          isSVG,
+          slotScopeIds,
+          optimized
         )
         // force resolve
         suspense.resolve(true)
@@ -252,7 +272,9 @@ function patchSuspense(
           null,
           parentComponent,
           suspense,
-          isSVG
+          isSVG,
+          slotScopeIds,
+          optimized
         )
         if (suspense.deps <= 0) {
           suspense.resolve()
@@ -269,7 +291,9 @@ function patchSuspense(
         anchor,
         parentComponent,
         suspense,
-        isSVG
+        isSVG,
+        slotScopeIds,
+        optimized
       )
       setActiveBranch(suspense, newBranch)
     } else {
@@ -289,7 +313,9 @@ function patchSuspense(
         null,
         parentComponent,
         suspense,
-        isSVG
+        isSVG,
+        slotScopeIds,
+        optimized
       )
       if (suspense.deps <= 0) {
         // incoming branch has no async deps, resolve now.
@@ -352,6 +378,7 @@ function createSuspenseBoundary(
   hiddenContainer: RendererElement,
   anchor: RendererNode | null,
   isSVG: boolean,
+  slotScopeIds: string[] | null,
   optimized: boolean,
   rendererInternals: RendererInternals,
   isHydrating = false
@@ -507,7 +534,9 @@ function createSuspenseBoundary(
           anchor,
           parentComponent,
           null, // fallback tree will not have suspense context
-          isSVG
+          isSVG,
+          slotScopeIds,
+          optimized
         )
         setActiveBranch(suspense, fallbackVNode)
       }
@@ -632,6 +661,7 @@ function hydrateSuspense(
   parentComponent: ComponentInternalInstance | null,
   parentSuspense: SuspenseBoundary | null,
   isSVG: boolean,
+  slotScopeIds: string[] | null,
   optimized: boolean,
   rendererInternals: RendererInternals,
   hydrateNode: (
@@ -639,6 +669,7 @@ function hydrateSuspense(
     vnode: VNode,
     parentComponent: ComponentInternalInstance | null,
     parentSuspense: SuspenseBoundary | null,
+    slotScopeIds: string[] | null,
     optimized: boolean
   ) => Node | null
 ): Node | null {
@@ -651,6 +682,7 @@ function hydrateSuspense(
     document.createElement('div'),
     null,
     isSVG,
+    slotScopeIds,
     optimized,
     rendererInternals,
     true /* hydrating */
@@ -666,6 +698,7 @@ function hydrateSuspense(
     (suspense.pendingBranch = vnode.ssContent!),
     parentComponent,
     suspense,
+    slotScopeIds,
     optimized
   )
   if (suspense.deps === 0) {
index fa61c636a880fb3c22b421d489659bb6e595a938..e75455f96f042ce7e0cc17138bde75afd0b84e8d 100644 (file)
@@ -71,6 +71,7 @@ export const TeleportImpl = {
     parentComponent: ComponentInternalInstance | null,
     parentSuspense: SuspenseBoundary | null,
     isSVG: boolean,
+    slotScopeIds: string[] | null,
     optimized: boolean,
     internals: RendererInternals
   ) {
@@ -115,6 +116,7 @@ export const TeleportImpl = {
             parentComponent,
             parentSuspense,
             isSVG,
+            slotScopeIds,
             optimized
           )
         }
@@ -144,7 +146,8 @@ export const TeleportImpl = {
           currentContainer,
           parentComponent,
           parentSuspense,
-          isSVG
+          isSVG,
+          slotScopeIds
         )
         // even in block tree mode we need to make sure all root-level nodes
         // in the teleport inherit previous DOM references so that they can
@@ -158,7 +161,9 @@ export const TeleportImpl = {
           currentAnchor,
           parentComponent,
           parentSuspense,
-          isSVG
+          isSVG,
+          slotScopeIds,
+          false
         )
       }
 
@@ -283,6 +288,7 @@ function hydrateTeleport(
   vnode: TeleportVNode,
   parentComponent: ComponentInternalInstance | null,
   parentSuspense: SuspenseBoundary | null,
+  slotScopeIds: string[] | null,
   optimized: boolean,
   {
     o: { nextSibling, parentNode, querySelector }
@@ -293,6 +299,7 @@ function hydrateTeleport(
     container: Element,
     parentComponent: ComponentInternalInstance | null,
     parentSuspense: SuspenseBoundary | null,
+    slotScopeIds: string[] | null,
     optimized: boolean
   ) => Node | null
 ): Node | null {
@@ -313,6 +320,7 @@ function hydrateTeleport(
           parentNode(node)!,
           parentComponent,
           parentSuspense,
+          slotScopeIds,
           optimized
         )
         vnode.targetAnchor = targetNode
@@ -324,6 +332,7 @@ function hydrateTeleport(
           target,
           parentComponent,
           parentSuspense,
+          slotScopeIds,
           optimized
         )
       }
index c909e9a2eddffeb0d21467d63a6e6b62581c054d..20f25d03ee9f4a36fafec18131e6553b7efa27d2 100644 (file)
@@ -15,7 +15,7 @@ import { VNode } from './vnode'
 import { isFunction, EMPTY_OBJ, makeMap } from '@vue/shared'
 import { warn } from './warning'
 import { ComponentInternalInstance, Data } from './component'
-import { currentRenderingInstance } from './componentRenderUtils'
+import { currentRenderingInstance } from './componentRenderContext'
 import { callWithAsyncErrorHandling, ErrorCodes } from './errorHandling'
 import { ComponentPublicInstance } from './componentPublicInstance'
 
index 420c4ffc102ed8240d9399335e880d6658b82635..56cdd3dcd450d4c4b465557da42b079563f1b64f 100644 (file)
@@ -53,6 +53,10 @@ export function renderSlot(
       ? PatchFlags.STABLE_FRAGMENT
       : PatchFlags.BAIL
   )
+  // TODO (optimization) only add slot scope id if :slotted is used
+  if (rendered.scopeId) {
+    rendered.slotScopeIds = [rendered.scopeId + '-s']
+  }
   isRenderingCompiledSlot--
   return rendered
 }
index 986079a952294354e4cab691c74b4449bcf03172..1d6a96bf99942c569815aef477502ca361c60d3b 100644 (file)
@@ -1,10 +1,10 @@
-import { currentRenderingInstance } from '../componentRenderUtils'
 import {
   currentInstance,
   ConcreteComponent,
   ComponentOptions,
   getComponentName
 } from '../component'
+import { currentRenderingInstance } from '../componentRenderContext'
 import { Directive } from '../directives'
 import { camelize, capitalize, isString } from '@vue/shared'
 import { warn } from '../warning'
diff --git a/packages/runtime-core/src/helpers/scopeId.ts b/packages/runtime-core/src/helpers/scopeId.ts
deleted file mode 100644 (file)
index fbefe04..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-// SFC scoped style ID management.
-// These are only used in esm-bundler builds, but since exports cannot be
-// conditional, we can only drop inner implementations in non-bundler builds.
-
-import { withCtx } from './withRenderContext'
-
-export let currentScopeId: string | null = null
-const scopeIdStack: string[] = []
-
-/**
- * @private
- */
-export function pushScopeId(id: string) {
-  scopeIdStack.push((currentScopeId = id))
-}
-
-/**
- * @private
- */
-export function popScopeId() {
-  scopeIdStack.pop()
-  currentScopeId = scopeIdStack[scopeIdStack.length - 1] || null
-}
-
-/**
- * @private
- */
-export function withScopeId(id: string): <T extends Function>(fn: T) => T {
-  return ((fn: Function) =>
-    withCtx(function(this: any) {
-      pushScopeId(id)
-      const res = fn.apply(this, arguments)
-      popScopeId()
-      return res
-    })) as any
-}
diff --git a/packages/runtime-core/src/helpers/withRenderContext.ts b/packages/runtime-core/src/helpers/withRenderContext.ts
deleted file mode 100644 (file)
index 88a29ae..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-import { Slot } from '../componentSlots'
-import {
-  setCurrentRenderingInstance,
-  currentRenderingInstance
-} from '../componentRenderUtils'
-import { ComponentInternalInstance } from '../component'
-import { isRenderingCompiledSlot } from './renderSlot'
-import { closeBlock, openBlock } from '../vnode'
-
-/**
- * Wrap a slot function to memoize current rendering instance
- * @private
- */
-export function withCtx(
-  fn: Slot,
-  ctx: ComponentInternalInstance | null = currentRenderingInstance
-) {
-  if (!ctx) return fn
-  const renderFnWithContext = (...args: any[]) => {
-    // If a user calls a compiled slot inside a template expression (#1745), it
-    // can mess up block tracking, so by default we need to push a null block to
-    // avoid that. This isn't necessary if rendering a compiled `<slot>`.
-    if (!isRenderingCompiledSlot) {
-      openBlock(true /* null block that disables tracking */)
-    }
-    const owner = currentRenderingInstance
-    setCurrentRenderingInstance(ctx)
-    const res = fn(...args)
-    setCurrentRenderingInstance(owner)
-    if (!isRenderingCompiledSlot) {
-      closeBlock()
-    }
-    return res
-  }
-  renderFnWithContext._c = true
-  return renderFnWithContext
-}
index c7c88eb65dedd88af8abae1b7843ad9f2ab74888..cad23524eb772f1549948349cd5c6ebf3390b07c 100644 (file)
@@ -63,7 +63,7 @@ export function createHydrationFunctions(
       return
     }
     hasMismatch = false
-    hydrateNode(container.firstChild!, vnode, null, null)
+    hydrateNode(container.firstChild!, vnode, null, null, null)
     flushPostFlushCbs()
     if (hasMismatch && !__TEST__) {
       // this error should show up in production
@@ -76,6 +76,7 @@ export function createHydrationFunctions(
     vnode: VNode,
     parentComponent: ComponentInternalInstance | null,
     parentSuspense: SuspenseBoundary | null,
+    slotScopeIds: string[] | null,
     optimized = false
   ): Node | null => {
     const isFragmentStart = isComment(node) && node.data === '['
@@ -85,6 +86,7 @@ export function createHydrationFunctions(
         vnode,
         parentComponent,
         parentSuspense,
+        slotScopeIds,
         isFragmentStart
       )
 
@@ -147,6 +149,7 @@ export function createHydrationFunctions(
             vnode,
             parentComponent,
             parentSuspense,
+            slotScopeIds,
             optimized
           )
         }
@@ -164,6 +167,7 @@ export function createHydrationFunctions(
               vnode,
               parentComponent,
               parentSuspense,
+              slotScopeIds,
               optimized
             )
           }
@@ -171,6 +175,7 @@ export function createHydrationFunctions(
           // when setting up the render effect, if the initial vnode already
           // has .el set, the component will perform hydration instead of mount
           // on its sub-tree.
+          vnode.slotScopeIds = slotScopeIds
           const container = parentNode(node)!
           const hydrateComponent = () => {
             mountComponent(
@@ -205,6 +210,7 @@ export function createHydrationFunctions(
               vnode as TeleportVNode,
               parentComponent,
               parentSuspense,
+              slotScopeIds,
               optimized,
               rendererInternals,
               hydrateChildren
@@ -217,6 +223,7 @@ export function createHydrationFunctions(
             parentComponent,
             parentSuspense,
             isSVGContainer(parentNode(node)!),
+            slotScopeIds,
             optimized,
             rendererInternals,
             hydrateNode
@@ -238,6 +245,7 @@ export function createHydrationFunctions(
     vnode: VNode,
     parentComponent: ComponentInternalInstance | null,
     parentSuspense: SuspenseBoundary | null,
+    slotScopeIds: string[] | null,
     optimized: boolean
   ) => {
     optimized = optimized || !!vnode.dynamicChildren
@@ -291,6 +299,7 @@ export function createHydrationFunctions(
           el,
           parentComponent,
           parentSuspense,
+          slotScopeIds,
           optimized
         )
         let hasWarned = false
@@ -330,6 +339,7 @@ export function createHydrationFunctions(
     container: Element,
     parentComponent: ComponentInternalInstance | null,
     parentSuspense: SuspenseBoundary | null,
+    slotScopeIds: string[] | null,
     optimized: boolean
   ): Node | null => {
     optimized = optimized || !!parentVNode.dynamicChildren
@@ -346,6 +356,7 @@ export function createHydrationFunctions(
           vnode,
           parentComponent,
           parentSuspense,
+          slotScopeIds,
           optimized
         )
       } else {
@@ -365,7 +376,8 @@ export function createHydrationFunctions(
           null,
           parentComponent,
           parentSuspense,
-          isSVGContainer(container)
+          isSVGContainer(container),
+          slotScopeIds
         )
       }
     }
@@ -377,8 +389,16 @@ export function createHydrationFunctions(
     vnode: VNode,
     parentComponent: ComponentInternalInstance | null,
     parentSuspense: SuspenseBoundary | null,
+    slotScopeIds: string[] | null,
     optimized: boolean
   ) => {
+    const { slotScopeIds: fragmentSlotScopeIds } = vnode
+    if (fragmentSlotScopeIds) {
+      slotScopeIds = slotScopeIds
+        ? slotScopeIds.concat(fragmentSlotScopeIds)
+        : fragmentSlotScopeIds
+    }
+
     const container = parentNode(node)!
     const next = hydrateChildren(
       nextSibling(node)!,
@@ -386,6 +406,7 @@ export function createHydrationFunctions(
       container,
       parentComponent,
       parentSuspense,
+      slotScopeIds,
       optimized
     )
     if (next && isComment(next) && next.data === ']') {
@@ -405,6 +426,7 @@ export function createHydrationFunctions(
     vnode: VNode,
     parentComponent: ComponentInternalInstance | null,
     parentSuspense: SuspenseBoundary | null,
+    slotScopeIds: string[] | null,
     isFragment: boolean
   ): Node | null => {
     hasMismatch = true
@@ -446,7 +468,8 @@ export function createHydrationFunctions(
       next,
       parentComponent,
       parentSuspense,
-      isSVGContainer(container)
+      isSVGContainer(container),
+      slotScopeIds
     )
     return next
   }
index a24672226dbc9c1d2115c9b749888786f8b3781a..98ba289f56567294e8661529bd77880fe6211ffc 100644 (file)
@@ -227,12 +227,11 @@ export { HMRRuntime } from './hmr'
 
 // For compiler generated code
 // should sync with '@vue/compiler-core/src/runtimeConstants.ts'
-export { withCtx } from './helpers/withRenderContext'
+export { withCtx, setScopeId } from './componentRenderContext'
 export { renderList } from './helpers/renderList'
 export { toHandlers } from './helpers/toHandlers'
 export { renderSlot } from './helpers/renderSlot'
 export { createSlots } from './helpers/createSlots'
-export { pushScopeId, popScopeId, withScopeId } from './helpers/scopeId'
 export {
   openBlock,
   createBlock,
@@ -257,10 +256,8 @@ export { transformVNodeArgs } from './vnode'
 // change without notice between versions. User code should never rely on them.
 
 import { createComponentInstance, setupComponent } from './component'
-import {
-  renderComponentRoot,
-  setCurrentRenderingInstance
-} from './componentRenderUtils'
+import { renderComponentRoot } from './componentRenderUtils'
+import { setCurrentRenderingInstance } from './componentRenderContext'
 import { isVNode, normalizeVNode } from './vnode'
 
 const _ssrUtils = {
index 3089b102796bd3ebf77931c9a23e96d86bdb94f8..f2a35794c377519497a476a6e2a4a73360fb3209 100644 (file)
@@ -177,6 +177,7 @@ type PatchFn = (
   parentComponent?: ComponentInternalInstance | null,
   parentSuspense?: SuspenseBoundary | null,
   isSVG?: boolean,
+  slotScopeIds?: string[] | null,
   optimized?: boolean
 ) => void
 
@@ -187,6 +188,7 @@ type MountChildrenFn = (
   parentComponent: ComponentInternalInstance | null,
   parentSuspense: SuspenseBoundary | null,
   isSVG: boolean,
+  slotScopeIds: string[] | null,
   optimized: boolean,
   start?: number
 ) => void
@@ -199,7 +201,8 @@ type PatchChildrenFn = (
   parentComponent: ComponentInternalInstance | null,
   parentSuspense: SuspenseBoundary | null,
   isSVG: boolean,
-  optimized?: boolean
+  slotScopeIds: string[] | null,
+  optimized: boolean
 ) => void
 
 type PatchBlockChildrenFn = (
@@ -208,7 +211,8 @@ type PatchBlockChildrenFn = (
   fallbackContainer: RendererElement,
   parentComponent: ComponentInternalInstance | null,
   parentSuspense: SuspenseBoundary | null,
-  isSVG: boolean
+  isSVG: boolean,
+  slotScopeIds: string[] | null
 ) => void
 
 type MoveFn = (
@@ -469,6 +473,7 @@ function baseCreateRenderer(
     parentComponent = null,
     parentSuspense = null,
     isSVG = false,
+    slotScopeIds = null,
     optimized = false
   ) => {
     // patching & not same type, unmount old tree
@@ -507,6 +512,7 @@ function baseCreateRenderer(
           parentComponent,
           parentSuspense,
           isSVG,
+          slotScopeIds,
           optimized
         )
         break
@@ -520,6 +526,7 @@ function baseCreateRenderer(
             parentComponent,
             parentSuspense,
             isSVG,
+            slotScopeIds,
             optimized
           )
         } else if (shapeFlag & ShapeFlags.COMPONENT) {
@@ -531,6 +538,7 @@ function baseCreateRenderer(
             parentComponent,
             parentSuspense,
             isSVG,
+            slotScopeIds,
             optimized
           )
         } else if (shapeFlag & ShapeFlags.TELEPORT) {
@@ -542,6 +550,7 @@ function baseCreateRenderer(
             parentComponent,
             parentSuspense,
             isSVG,
+            slotScopeIds,
             optimized,
             internals
           )
@@ -554,6 +563,7 @@ function baseCreateRenderer(
             parentComponent,
             parentSuspense,
             isSVG,
+            slotScopeIds,
             optimized,
             internals
           )
@@ -676,6 +686,7 @@ function baseCreateRenderer(
     parentComponent: ComponentInternalInstance | null,
     parentSuspense: SuspenseBoundary | null,
     isSVG: boolean,
+    slotScopeIds: string[] | null,
     optimized: boolean
   ) => {
     isSVG = isSVG || (n2.type as string) === 'svg'
@@ -687,10 +698,19 @@ function baseCreateRenderer(
         parentComponent,
         parentSuspense,
         isSVG,
+        slotScopeIds,
         optimized
       )
     } else {
-      patchElement(n1, n2, parentComponent, parentSuspense, isSVG, optimized)
+      patchElement(
+        n1,
+        n2,
+        parentComponent,
+        parentSuspense,
+        isSVG,
+        slotScopeIds,
+        optimized
+      )
     }
   }
 
@@ -701,19 +721,12 @@ function baseCreateRenderer(
     parentComponent: ComponentInternalInstance | null,
     parentSuspense: SuspenseBoundary | null,
     isSVG: boolean,
+    slotScopeIds: string[] | null,
     optimized: boolean
   ) => {
     let el: RendererElement
     let vnodeHook: VNodeHook | undefined | null
-    const {
-      type,
-      props,
-      shapeFlag,
-      transition,
-      scopeId,
-      patchFlag,
-      dirs
-    } = vnode
+    const { type, props, shapeFlag, transition, patchFlag, dirs } = vnode
     if (
       !__DEV__ &&
       vnode.el &&
@@ -744,6 +757,7 @@ function baseCreateRenderer(
           parentComponent,
           parentSuspense,
           isSVG && type !== 'foreignObject',
+          slotScopeIds,
           optimized || !!vnode.dynamicChildren
         )
       }
@@ -773,7 +787,7 @@ function baseCreateRenderer(
         }
       }
       // scopeId
-      setScopeId(el, scopeId, vnode, parentComponent)
+      setScopeId(el, vnode, vnode.scopeId, slotScopeIds, parentComponent)
     }
     if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
       Object.defineProperty(el, '__vnode', {
@@ -813,30 +827,32 @@ function baseCreateRenderer(
 
   const setScopeId = (
     el: RendererElement,
-    scopeId: string | false | null,
     vnode: VNode,
+    scopeId: string | null,
+    slotScopeIds: string[] | null,
     parentComponent: ComponentInternalInstance | null
   ) => {
     if (scopeId) {
       hostSetScopeId(el, scopeId)
     }
-    if (parentComponent) {
-      const treeOwnerId = parentComponent.type.__scopeId
-      // vnode's own scopeId and the current patched component's scopeId is
-      // different - this is a slot content node.
-      if (treeOwnerId && treeOwnerId !== scopeId) {
-        hostSetScopeId(el, treeOwnerId + '-s')
+    if (slotScopeIds) {
+      for (let i = 0; i < slotScopeIds.length; i++) {
+        hostSetScopeId(el, slotScopeIds[i])
       }
+    }
+    if (parentComponent) {
       let subTree = parentComponent.subTree
-      if (__DEV__ && subTree.type === Fragment) {
+      if (__DEV__ && subTree.patchFlag & PatchFlags.DEV_ROOT_FRAGMENT) {
         subTree =
           filterSingleRoot(subTree.children as VNodeArrayChildren) || subTree
       }
       if (vnode === subTree) {
+        const parentVNode = parentComponent.vnode
         setScopeId(
           el,
-          parentComponent.vnode.scopeId,
-          parentComponent.vnode,
+          parentVNode,
+          parentVNode.scopeId,
+          parentVNode.slotScopeIds,
           parentComponent.parent
         )
       }
@@ -851,6 +867,7 @@ function baseCreateRenderer(
     parentSuspense,
     isSVG,
     optimized,
+    slotScopeIds,
     start = 0
   ) => {
     for (let i = start; i < children.length; i++) {
@@ -865,7 +882,8 @@ function baseCreateRenderer(
         parentComponent,
         parentSuspense,
         isSVG,
-        optimized
+        optimized,
+        slotScopeIds
       )
     }
   }
@@ -876,6 +894,7 @@ function baseCreateRenderer(
     parentComponent: ComponentInternalInstance | null,
     parentSuspense: SuspenseBoundary | null,
     isSVG: boolean,
+    slotScopeIds: string[] | null,
     optimized: boolean
   ) => {
     const el = (n2.el = n1.el!)
@@ -993,7 +1012,8 @@ function baseCreateRenderer(
         el,
         parentComponent,
         parentSuspense,
-        areChildrenSVG
+        areChildrenSVG,
+        slotScopeIds
       )
       if (__DEV__ && parentComponent && parentComponent.type.__hmrId) {
         traverseStaticChildren(n1, n2)
@@ -1007,7 +1027,9 @@ function baseCreateRenderer(
         null,
         parentComponent,
         parentSuspense,
-        areChildrenSVG
+        areChildrenSVG,
+        slotScopeIds,
+        false
       )
     }
 
@@ -1026,7 +1048,8 @@ function baseCreateRenderer(
     fallbackContainer,
     parentComponent,
     parentSuspense,
-    isSVG
+    isSVG,
+    slotScopeIds
   ) => {
     for (let i = 0; i < newChildren.length; i++) {
       const oldVNode = oldChildren[i]
@@ -1054,6 +1077,7 @@ function baseCreateRenderer(
         parentComponent,
         parentSuspense,
         isSVG,
+        slotScopeIds,
         true
       )
     }
@@ -1119,16 +1143,24 @@ function baseCreateRenderer(
     parentComponent: ComponentInternalInstance | null,
     parentSuspense: SuspenseBoundary | null,
     isSVG: boolean,
+    slotScopeIds: string[] | null,
     optimized: boolean
   ) => {
     const fragmentStartAnchor = (n2.el = n1 ? n1.el : hostCreateText(''))!
     const fragmentEndAnchor = (n2.anchor = n1 ? n1.anchor : hostCreateText(''))!
 
-    let { patchFlag, dynamicChildren } = n2
+    let { patchFlag, dynamicChildren, slotScopeIds: fragmentSlotScopeIds } = n2
     if (patchFlag > 0) {
       optimized = true
     }
 
+    // check if this is a slot fragment with :slotted scope ids
+    if (fragmentSlotScopeIds) {
+      slotScopeIds = slotScopeIds
+        ? slotScopeIds.concat(fragmentSlotScopeIds)
+        : fragmentSlotScopeIds
+    }
+
     if (__DEV__ && isHmrUpdating) {
       // HMR updated, force full diff
       patchFlag = 0
@@ -1149,6 +1181,7 @@ function baseCreateRenderer(
         parentComponent,
         parentSuspense,
         isSVG,
+        slotScopeIds,
         optimized
       )
     } else {
@@ -1168,7 +1201,8 @@ function baseCreateRenderer(
           container,
           parentComponent,
           parentSuspense,
-          isSVG
+          isSVG,
+          slotScopeIds
         )
         if (__DEV__ && parentComponent && parentComponent.type.__hmrId) {
           traverseStaticChildren(n1, n2)
@@ -1195,6 +1229,7 @@ function baseCreateRenderer(
           parentComponent,
           parentSuspense,
           isSVG,
+          slotScopeIds,
           optimized
         )
       }
@@ -1209,8 +1244,10 @@ function baseCreateRenderer(
     parentComponent: ComponentInternalInstance | null,
     parentSuspense: SuspenseBoundary | null,
     isSVG: boolean,
+    slotScopeIds: string[] | null,
     optimized: boolean
   ) => {
+    n2.slotScopeIds = slotScopeIds
     if (n1 == null) {
       if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {
         ;(parentComponent!.ctx as KeepAliveContext).activate(
@@ -1382,7 +1419,8 @@ function baseCreateRenderer(
             initialVNode.el as Node,
             subTree,
             instance,
-            parentSuspense
+            parentSuspense,
+            null
           )
           if (__DEV__) {
             endMeasure(instance, `hydrate`)
@@ -1543,6 +1581,7 @@ function baseCreateRenderer(
     parentComponent,
     parentSuspense,
     isSVG,
+    slotScopeIds,
     optimized = false
   ) => {
     const c1 = n1 && n1.children
@@ -1563,6 +1602,7 @@ function baseCreateRenderer(
           parentComponent,
           parentSuspense,
           isSVG,
+          slotScopeIds,
           optimized
         )
         return
@@ -1576,6 +1616,7 @@ function baseCreateRenderer(
           parentComponent,
           parentSuspense,
           isSVG,
+          slotScopeIds,
           optimized
         )
         return
@@ -1604,6 +1645,7 @@ function baseCreateRenderer(
             parentComponent,
             parentSuspense,
             isSVG,
+            slotScopeIds,
             optimized
           )
         } else {
@@ -1625,6 +1667,7 @@ function baseCreateRenderer(
             parentComponent,
             parentSuspense,
             isSVG,
+            slotScopeIds,
             optimized
           )
         }
@@ -1640,6 +1683,7 @@ function baseCreateRenderer(
     parentComponent: ComponentInternalInstance | null,
     parentSuspense: SuspenseBoundary | null,
     isSVG: boolean,
+    slotScopeIds: string[] | null,
     optimized: boolean
   ) => {
     c1 = c1 || EMPTY_ARR
@@ -1660,6 +1704,7 @@ function baseCreateRenderer(
         parentComponent,
         parentSuspense,
         isSVG,
+        slotScopeIds,
         optimized
       )
     }
@@ -1682,6 +1727,7 @@ function baseCreateRenderer(
         parentComponent,
         parentSuspense,
         isSVG,
+        slotScopeIds,
         optimized,
         commonLength
       )
@@ -1697,6 +1743,7 @@ function baseCreateRenderer(
     parentComponent: ComponentInternalInstance | null,
     parentSuspense: SuspenseBoundary | null,
     isSVG: boolean,
+    slotScopeIds: string[] | null,
     optimized: boolean
   ) => {
     let i = 0
@@ -1721,6 +1768,7 @@ function baseCreateRenderer(
           parentComponent,
           parentSuspense,
           isSVG,
+          slotScopeIds,
           optimized
         )
       } else {
@@ -1746,6 +1794,7 @@ function baseCreateRenderer(
           parentComponent,
           parentSuspense,
           isSVG,
+          slotScopeIds,
           optimized
         )
       } else {
@@ -1776,7 +1825,9 @@ function baseCreateRenderer(
             anchor,
             parentComponent,
             parentSuspense,
-            isSVG
+            isSVG,
+            slotScopeIds,
+            optimized
           )
           i++
         }
@@ -1878,6 +1929,7 @@ function baseCreateRenderer(
             parentComponent,
             parentSuspense,
             isSVG,
+            slotScopeIds,
             optimized
           )
           patched++
@@ -1905,7 +1957,9 @@ function baseCreateRenderer(
             anchor,
             parentComponent,
             parentSuspense,
-            isSVG
+            isSVG,
+            slotScopeIds,
+            optimized
           )
         } else if (moved) {
           // move if:
index 92d7c4f428b0f5bd0377793e2d8affe5d1a9d909..e46c9842619059c98c0dc96d9142cc390b022ea9 100644 (file)
@@ -32,9 +32,11 @@ import {
 import { DirectiveBinding } from './directives'
 import { TransitionHooks } from './components/BaseTransition'
 import { warn } from './warning'
-import { currentScopeId } from './helpers/scopeId'
 import { TeleportImpl, isTeleport } from './components/Teleport'
-import { currentRenderingInstance } from './componentRenderUtils'
+import {
+  currentRenderingInstance,
+  currentScopeId
+} from './componentRenderContext'
 import { RendererNode, RendererElement } from './renderer'
 import { NULL_DYNAMIC_COMPONENT } from './helpers/resolveAssets'
 import { hmrDirtyComponents } from './hmr'
@@ -133,7 +135,18 @@ export interface VNode<
   props: (VNodeProps & ExtraProps) | null
   key: string | number | null
   ref: VNodeNormalizedRef | null
-  scopeId: string | null // SFC only
+  /**
+   * SFC only. This is assigned on vnode creation using currentScopeId
+   * which is set alongside currentRenderingInstance.
+   */
+  scopeId: string | null
+  /**
+   * SFC only. This is assigned to:
+   * - Slot fragment vnodes with :slotted SFC styles.
+   * - Component vnodes (during patch/hydration) so that its root node can
+   *   inherit the component's slotScopeIds
+   */
+  slotScopeIds: string[] | null
   children: VNodeNormalizedChildren
   component: ComponentInternalInstance | null
   dirs: DirectiveBinding[] | null
@@ -398,6 +411,7 @@ function _createVNode(
     key: props && normalizeKey(props),
     ref: props && normalizeRef(props),
     scopeId: currentScopeId,
+    slotScopeIds: null,
     children: null,
     component: null,
     suspense: null,
@@ -479,6 +493,7 @@ export function cloneVNode<T, U>(
           : normalizeRef(extraProps)
         : ref,
     scopeId: vnode.scopeId,
+    slotScopeIds: vnode.slotScopeIds,
     children:
       __DEV__ && patchFlag === PatchFlags.HOISTED && isArray(children)
         ? (children as VNode[]).map(deepCloneVNode)
index a02bf365cb7ea347ffac36f349752bc29c771f34..806ca210b23cdc9381e53f997fd0694c01a73485 100644 (file)
@@ -2,13 +2,13 @@ import {
   createApp,
   h,
   createCommentVNode,
-  withScopeId,
   resolveComponent,
   ComponentOptions,
   ref,
   defineComponent,
   createTextVNode,
-  createStaticVNode
+  createStaticVNode,
+  withCtx
 } from 'vue'
 import { escapeHtml } from '@vue/shared'
 import { renderToString } from '../src/renderToString'
@@ -634,34 +634,32 @@ function testRender(type: string, render: typeof renderToString) {
     describe('scopeId', () => {
       // note: here we are only testing scopeId handling for vdom serialization.
       // compiled srr render functions will include scopeId directly in strings.
-      const withId = withScopeId('data-v-test')
-      const withChildId = withScopeId('data-v-child')
 
       test('basic', async () => {
-        expect(
-          await render(
-            withId(() => {
-              return h('div')
-            })()
-          )
-        ).toBe(`<div data-v-test></div>`)
+        const Foo = {
+          __scopeId: 'data-v-test',
+          render() {
+            return h('div')
+          }
+        }
+        expect(await render(h(Foo))).toBe(`<div data-v-test></div>`)
       })
 
       test('with slots', async () => {
         const Child = {
           __scopeId: 'data-v-child',
-          render: withChildId(function(this: any) {
+          render: function(this: any) {
             return h('div', this.$slots.default())
-          })
+          }
         }
 
         const Parent = {
           __scopeId: 'data-v-test',
-          render: withId(() => {
+          render: () => {
             return h(Child, null, {
-              default: withId(() => h('span', 'slot'))
+              default: withCtx(() => h('span', 'slot'))
             })
-          })
+          }
         }
 
         expect(await render(h(Parent))).toBe(
index 8b58fc66b9396c8169e14f3d4ae5c101c0fd016e..7726739e4012d1c008cb77e9b8c7ce3422551b5e 100644 (file)
@@ -1,11 +1,9 @@
-import { createApp, withScopeId } from 'vue'
+import { createApp, mergeProps, withCtx } 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')
-
+describe('ssr: scopedId runtime behavior', () => {
+  test('id on component root', async () => {
     const Child = {
       ssrRender: (ctx: any, push: any, parent: any, attrs: any) => {
         push(`<div${ssrRenderAttrs(attrs)}></div>`)
@@ -13,19 +11,19 @@ describe('ssr: scoped id on component root', () => {
     }
 
     const Comp = {
-      ssrRender: withParentId((ctx: any, push: any, parent: any) => {
+      __scopeId: 'parent',
+      ssrRender: (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')
-
+  test('id and :slotted on component root', async () => {
     const Child = {
+      // <div></div>
       ssrRender: (_: any, push: any, _parent: any, attrs: any) => {
         push(`<div${ssrRenderAttrs(attrs)} child></div>`)
       }
@@ -34,29 +32,126 @@ describe('ssr: scoped id on component root', () => {
     const Wrapper = {
       __scopeId: 'wrapper',
       ssrRender: (ctx: any, push: any, parent: any) => {
-        ssrRenderSlot(ctx.$slots, 'default', {}, null, push, parent)
+        // <slot/>
+        ssrRenderSlot(
+          ctx.$slots,
+          'default',
+          {},
+          null,
+          push,
+          parent,
+          'wrapper-s'
+        )
       }
     }
 
     const Comp = {
-      ssrRender: withParentId((_: any, push: any, parent: any) => {
+      __scopeId: 'parent',
+      ssrRender: (_: any, push: any, parent: any) => {
+        // <Wrapper><Child/></Wrapper>
         push(
           ssrRenderComponent(
             Wrapper,
             null,
             {
-              default: withParentId((_: any, push: any, parent: any) => {
-                push(ssrRenderComponent(Child, null, null, parent))
-              }),
+              default: withCtx(
+                (_: any, push: any, parent: any, scopeId: string) => {
+                  push(ssrRenderComponent(Child, null, null, parent, scopeId))
+                }
+              ),
               _: 1
             } as any,
             parent
           )
         )
-      })
+      }
     }
 
     const result = await renderToString(createApp(Comp))
     expect(result).toBe(`<!--[--><div parent wrapper-s child></div><!--]-->`)
   })
+
+  // #2892
+  test(':slotted on forwarded slots', async () => {
+    const Wrapper = {
+      __scopeId: 'wrapper',
+      ssrRender: (ctx: any, push: any, parent: any, attrs: any) => {
+        // <div class="wrapper"><slot/></div>
+        push(
+          `<div${ssrRenderAttrs(
+            mergeProps({ class: 'wrapper' }, attrs)
+          )} wrapper>`
+        )
+        ssrRenderSlot(
+          ctx.$slots,
+          'default',
+          {},
+          null,
+          push,
+          parent,
+          'wrapper-s'
+        )
+        push(`</div>`)
+      }
+    }
+
+    const Slotted = {
+      __scopeId: 'slotted',
+      ssrRender: (ctx: any, push: any, parent: any, attrs: any) => {
+        // <Wrapper><slot/></Wrapper>
+        push(
+          ssrRenderComponent(
+            Wrapper,
+            attrs,
+            {
+              default: withCtx(
+                (_: any, push: any, parent: any, scopeId: string) => {
+                  ssrRenderSlot(
+                    ctx.$slots,
+                    'default',
+                    {},
+                    null,
+                    push,
+                    parent,
+                    'slotted-s' + scopeId
+                  )
+                }
+              ),
+              _: 1
+            } as any,
+            parent
+          )
+        )
+      }
+    }
+
+    const Root = {
+      __scopeId: 'root',
+      // <Slotted><div></div></Slotted>
+      ssrRender: (_: any, push: any, parent: any, attrs: any) => {
+        push(
+          ssrRenderComponent(
+            Slotted,
+            attrs,
+            {
+              default: withCtx(
+                (_: any, push: any, parent: any, scopeId: string) => {
+                  push(`<div root${scopeId}></div>`)
+                }
+              ),
+              _: 1
+            } as any,
+            parent
+          )
+        )
+      }
+    }
+
+    const result = await renderToString(createApp(Root))
+    expect(result).toBe(
+      `<div class="wrapper" root slotted wrapper>` +
+        `<!--[--><!--[--><div root slotted-s wrapper-s></div><!--]--><!--]-->` +
+        `</div>`
+    )
+  })
 })
index 000f2b482ed2441e3be9e3bdc610c79765c3463a..4709f23a67452403120faf73969ad811f799bb83 100644 (file)
@@ -6,10 +6,12 @@ export function ssrRenderComponent(
   comp: Component,
   props: Props | null = null,
   children: Slots | SSRSlots | null = null,
-  parentComponent: ComponentInternalInstance | null = null
+  parentComponent: ComponentInternalInstance | null = null,
+  slotScopeId?: string
 ): SSRBuffer | Promise<SSRBuffer> {
   return renderComponentVNode(
     createVNode(comp, props, children),
-    parentComponent
+    parentComponent,
+    slotScopeId
   )
 }
index 5ee6113a198862b3dcfc6fd191087d222c29724c..3f3589a3b3fc9e3c74a4311a2b37a642f4cd5e62 100644 (file)
@@ -15,13 +15,13 @@ export function ssrRenderSlot(
   slotProps: Props,
   fallbackRenderFn: (() => void) | null,
   push: PushFn,
-  parentComponent: ComponentInternalInstance
+  parentComponent: ComponentInternalInstance,
+  slotScopeId?: string | null
 ) {
   // template-compiled slots are always rendered as fragments
   push(`<!--[-->`)
   const slotFn = slots[slotName]
   if (slotFn) {
-    const scopeId = parentComponent && parentComponent.type.__scopeId
     const slotBuffer: SSRBufferItem[] = []
     const bufferedPush = (item: SSRBufferItem) => {
       slotBuffer.push(item)
@@ -30,7 +30,7 @@ export function ssrRenderSlot(
       slotProps,
       bufferedPush,
       parentComponent,
-      scopeId ? ` ${scopeId}-s` : ``
+      slotScopeId ? ' ' + slotScopeId : ''
     )
     if (Array.isArray(ret)) {
       // normal slot
index 938a4f83a4f9c583ac0f5a2a4908090b5dbc2d5a..fd40f3be4ce264e3495a46581c6a056c798868c6 100644 (file)
@@ -80,7 +80,8 @@ export function createBuffer() {
 
 export function renderComponentVNode(
   vnode: VNode,
-  parentComponent: ComponentInternalInstance | null = null
+  parentComponent: ComponentInternalInstance | null = null,
+  slotScopeId?: string
 ): SSRBuffer | Promise<SSRBuffer> {
   const instance = createComponentInstance(vnode, parentComponent, null)
   const res = setupComponent(instance, true /* isSSR */)
@@ -97,14 +98,15 @@ export function renderComponentVNode(
         warn(`[@vue/server-renderer]: Uncaught error in serverPrefetch:\n`, err)
       })
     }
-    return p.then(() => renderComponentSubTree(instance))
+    return p.then(() => renderComponentSubTree(instance, slotScopeId))
   } else {
-    return renderComponentSubTree(instance)
+    return renderComponentSubTree(instance, slotScopeId)
   }
 }
 
 function renderComponentSubTree(
-  instance: ComponentInternalInstance
+  instance: ComponentInternalInstance,
+  slotScopeId?: string
 ): SSRBuffer | Promise<SSRBuffer> {
   const comp = instance.type as Component
   const { getBuffer, push } = createBuffer()
@@ -133,13 +135,10 @@ function renderComponentSubTree(
 
       // 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] = ''
+        if (slotScopeId) attrs[slotScopeId.trim()] = ''
       }
 
       // set current rendering instance for asset resolution