]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix: ensure backwards compat for pre-compiled sfc components
authorEvan You <yyx990803@gmail.com>
Sat, 27 Mar 2021 14:53:45 +0000 (10:53 -0400)
committerEvan You <yyx990803@gmail.com>
Sat, 27 Mar 2021 14:53:45 +0000 (10:53 -0400)
fix #3493

16 files changed:
packages/compiler-core/__tests__/__snapshots__/scopeId.spec.ts.snap
packages/compiler-core/__tests__/scopeId.spec.ts
packages/compiler-core/__tests__/transforms/__snapshots__/vFor.spec.ts.snap
packages/compiler-core/__tests__/transforms/__snapshots__/vIf.spec.ts.snap
packages/compiler-core/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap
packages/compiler-core/__tests__/transforms/transformSlotOutlet.spec.ts
packages/compiler-core/__tests__/transforms/vIf.spec.ts
packages/compiler-core/src/codegen.ts
packages/compiler-core/src/runtimeHelpers.ts
packages/compiler-core/src/transforms/transformSlotOutlet.ts
packages/compiler-ssr/__tests__/ssrScopeId.spec.ts
packages/compiler-ssr/__tests__/ssrSlotOutlet.spec.ts
packages/runtime-core/__tests__/scopeId.spec.ts
packages/runtime-core/src/componentRenderContext.ts
packages/runtime-core/src/helpers/renderSlot.ts
packages/runtime-core/src/index.ts

index 4076e61db6a57c7d5fdab2cee0058468e5384fbe..aa74c6c43d11898fedaeb137b88ff998f9e3e110 100644 (file)
@@ -1,48 +1,51 @@
 // 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, setScopeId as _setScopeId } from \\"vue\\"
+"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\\")
 
-_setScopeId(\\"test\\")
+_pushScopeId(\\"test\\")
 const _hoisted_1 = /*#__PURE__*/_createVNode(\\"div\\", null, \\"hello\\", -1 /* HOISTED */)
 const _hoisted_2 = /*#__PURE__*/_createVNode(\\"div\\", null, \\"world\\", -1 /* HOISTED */)
-_setScopeId(null)
+_popScopeId()
 
-export function render(_ctx, _cache) {
+export const render = /*#__PURE__*/_withId((_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 } from \\"vue\\"
+"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\\")
 
-export function render(_ctx, _cache) {
+export const render = /*#__PURE__*/_withId((_ctx, _cache) => {
   const _component_Child = _resolveComponent(\\"Child\\")
 
   return (_openBlock(), _createBlock(_component_Child, null, {
-    default: _withCtx(() => [
+    default: _withId(() => [
       _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 } from \\"vue\\"
+"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\\")
 
-export function render(_ctx, _cache) {
+export const render = /*#__PURE__*/_withId((_ctx, _cache) => {
   const _component_Child = _resolveComponent(\\"Child\\")
 
   return (_openBlock(), _createBlock(_component_Child, null, _createSlots({ _: 2 /* DYNAMIC */ }, [
     (_ctx.ok)
       ? {
           name: \\"foo\\",
-          fn: _withCtx(() => [
+          fn: _withId(() => [
             _createVNode(\\"div\\")
           ])
         }
@@ -50,29 +53,30 @@ export function render(_ctx, _cache) {
     _renderList(_ctx.list, (i) => {
       return {
         name: i,
-        fn: _withCtx(() => [
+        fn: _withId(() => [
           _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 } from \\"vue\\"
+"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\\")
 
-export function render(_ctx, _cache) {
+export const render = /*#__PURE__*/_withId((_ctx, _cache) => {
   const _component_Child = _resolveComponent(\\"Child\\")
 
   return (_openBlock(), _createBlock(_component_Child, null, {
-    foo: _withCtx(({ msg }) => [
+    foo: _withId(({ msg }) => [
       _createTextVNode(_toDisplayString(msg), 1 /* TEXT */)
     ]),
-    bar: _withCtx(() => [
+    bar: _withId(() => [
       _createVNode(\\"div\\")
     ]),
     _: 1 /* STABLE */
   }))
-}"
+})"
 `;
index 3c1d6d554b76b23ae0ea8abb8543ef0767b6db3f..37ffb8893ca8aaf96c612dd35abf7a8e7688bdeb 100644 (file)
@@ -1,5 +1,5 @@
 import { baseCompile } from '../src/compile'
-import { SET_SCOPE_ID } from '../src/runtimeHelpers'
+import { PUSH_SCOPE_ID, POP_SCOPE_ID } from '../src/runtimeHelpers'
 import { PatchFlags } from '@vue/shared'
 import { genFlagText } from './testUtils'
 
@@ -20,7 +20,7 @@ describe('scopeId compiler support', () => {
       mode: 'module',
       scopeId: 'test'
     })
-    expect(code).toMatch(`default: _withCtx(() => [`)
+    expect(code).toMatch(`default: _withId(() => [`)
     expect(code).toMatchSnapshot()
   })
 
@@ -36,8 +36,8 @@ describe('scopeId compiler support', () => {
         scopeId: 'test'
       }
     )
-    expect(code).toMatch(`foo: _withCtx(({ msg }) => [`)
-    expect(code).toMatch(`bar: _withCtx(() => [`)
+    expect(code).toMatch(`foo: _withId(({ msg }) => [`)
+    expect(code).toMatch(`bar: _withId(() => [`)
     expect(code).toMatchSnapshot()
   })
 
@@ -53,8 +53,8 @@ describe('scopeId compiler support', () => {
         scopeId: 'test'
       }
     )
-    expect(code).toMatch(/name: "foo",\s+fn: _withCtx\(/)
-    expect(code).toMatch(/name: i,\s+fn: _withCtx\(/)
+    expect(code).toMatch(/name: "foo",\s+fn: _withId\(/)
+    expect(code).toMatch(/name: i,\s+fn: _withId\(/)
     expect(code).toMatchSnapshot()
   })
 
@@ -67,18 +67,19 @@ describe('scopeId compiler support', () => {
         hoistStatic: true
       }
     )
-    expect(ast.helpers).toContain(SET_SCOPE_ID)
+    expect(ast.helpers).toContain(PUSH_SCOPE_ID)
+    expect(ast.helpers).toContain(POP_SCOPE_ID)
     expect(ast.hoists.length).toBe(2)
     expect(code).toMatch(
       [
-        `_setScopeId("test")`,
+        `_pushScopeId("test")`,
         `const _hoisted_1 = /*#__PURE__*/_createVNode("div", null, "hello", ${genFlagText(
           PatchFlags.HOISTED
         )})`,
         `const _hoisted_2 = /*#__PURE__*/_createVNode("div", null, "world", ${genFlagText(
           PatchFlags.HOISTED
         )})`,
-        `_setScopeId(null)`
+        `_popScopeId()`
       ].join('\n')
     )
     expect(code).toMatchSnapshot()
index 3259964e99147638f8d66bb6de209f165d1bf95b..47c990674cc74f7780749f5af262121171add20d 100644 (file)
@@ -129,7 +129,7 @@ return function render(_ctx, _cache) {
     const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, renderSlot: _renderSlot } = _Vue
 
     return (_openBlock(true), _createBlock(_Fragment, null, _renderList(items, (item) => {
-      return _renderSlot($slots, \\"default\\", {}, undefined, true)
+      return _renderSlot($slots, \\"default\\")
     }), 256 /* UNKEYED_FRAGMENT */))
   }
 }"
@@ -143,7 +143,7 @@ return function render(_ctx, _cache) {
     const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, renderSlot: _renderSlot } = _Vue
 
     return (_openBlock(true), _createBlock(_Fragment, null, _renderList(items, (item) => {
-      return _renderSlot($slots, \\"default\\", {}, undefined, true)
+      return _renderSlot($slots, \\"default\\")
     }), 256 /* UNKEYED_FRAGMENT */))
   }
 }"
index 961716a1fc15e970191be0e97a7236854eccb838..518a3f1ffb05bc201b2a23670498944ed3386749 100644 (file)
@@ -80,7 +80,7 @@ return function render(_ctx, _cache) {
     const { renderSlot: _renderSlot, createCommentVNode: _createCommentVNode } = _Vue
 
     return ok
-      ? _renderSlot($slots, \\"default\\", { key: 0 }, undefined, true)
+      ? _renderSlot($slots, \\"default\\", { key: 0 })
       : _createCommentVNode(\\"v-if\\", true)
   }
 }"
@@ -140,7 +140,7 @@ return function render(_ctx, _cache) {
     const { renderSlot: _renderSlot, createCommentVNode: _createCommentVNode } = _Vue
 
     return ok
-      ? _renderSlot($slots, \\"default\\", { key: 0 }, undefined, true)
+      ? _renderSlot($slots, \\"default\\", { key: 0 })
       : _createCommentVNode(\\"v-if\\", true)
   }
 }"
index 59836a12c6eaeb7b0557be94f37764ce44f26ade..f4897194f03ed481c1c706d95a872a2863feb2a1 100644 (file)
@@ -67,7 +67,7 @@ return function render(_ctx, _cache) {
     return (_openBlock(), _createBlock(\\"div\\", null, [
       _cache[1] || (
         _setBlockTracking(-1),
-        _cache[1] = _renderSlot($slots, \\"default\\", {}, undefined, true),
+        _cache[1] = _renderSlot($slots, \\"default\\"),
         _setBlockTracking(1),
         _cache[1]
       )
index 3cfa8d07757e89e16605836d8632d66af7b0786f..219c209856c82549e22e675c113b5b892f29226f 100644 (file)
@@ -16,7 +16,6 @@ import { transformSlotOutlet } from '../../src/transforms/transformSlotOutlet'
 function parseWithSlots(template: string, options: CompilerOptions = {}) {
   const ast = parse(template)
   transform(ast, {
-    slotted: false,
     nodeTransforms: [
       ...(options.prefixIdentifiers ? [transformExpression] : []),
       transformSlotOutlet,
@@ -340,8 +339,8 @@ describe('compiler: transform <slot> outlets', () => {
     })
   })
 
-  test('slot with slotted: true', async () => {
-    const ast = parseWithSlots(`<slot/>`, { slotted: true })
+  test('slot with slotted: false', async () => {
+    const ast = parseWithSlots(`<slot/>`, { slotted: false, scopeId: 'foo' })
     expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
       type: NodeTypes.JS_CALL_EXPRESSION,
       callee: RENDER_SLOT,
index 846a4d9a9567b68ae8f3e7f2a1840d18e413bea4..f1b91cc4567dbdfdbc4f9dfe3d1d35c424047527 100644 (file)
@@ -404,13 +404,7 @@ describe('compiler: v-if', () => {
       expect(codegenNode.consequent).toMatchObject({
         type: NodeTypes.JS_CALL_EXPRESSION,
         callee: RENDER_SLOT,
-        arguments: [
-          '$slots',
-          '"default"',
-          createObjectMatcher({ key: `[0]` }),
-          'undefined',
-          'true'
-        ]
+        arguments: ['$slots', '"default"', createObjectMatcher({ key: `[0]` })]
       })
       expect(generate(root).code).toMatchSnapshot()
     })
@@ -423,13 +417,7 @@ describe('compiler: v-if', () => {
       expect(codegenNode.consequent).toMatchObject({
         type: NodeTypes.JS_CALL_EXPRESSION,
         callee: RENDER_SLOT,
-        arguments: [
-          '$slots',
-          '"default"',
-          createObjectMatcher({ key: `[0]` }),
-          'undefined',
-          'true'
-        ]
+        arguments: ['$slots', '"default"', createObjectMatcher({ key: `[0]` })]
       })
       expect(generate(root).code).toMatchSnapshot()
     })
index 3acb8cf5423a6c13b4cd7fa38b7d8313ec1be532..939b3b356adff4a538b244d68acc4667c134fa25 100644 (file)
@@ -43,7 +43,9 @@ import {
   SET_BLOCK_TRACKING,
   CREATE_COMMENT,
   CREATE_TEXT,
-  SET_SCOPE_ID,
+  PUSH_SCOPE_ID,
+  POP_SCOPE_ID,
+  WITH_SCOPE_ID,
   WITH_DIRECTIVES,
   CREATE_BLOCK,
   OPEN_BLOCK,
@@ -53,6 +55,7 @@ import {
 import { ImportItem } from './transform'
 
 const PURE_ANNOTATION = `/*#__PURE__*/`
+const WITH_ID = `_withId`
 
 type CodegenNode = TemplateChildNode | JSChildNode | SSRCodegenNode
 
@@ -195,11 +198,13 @@ 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
@@ -209,7 +214,7 @@ export function generate(
     ? createCodegenContext(ast, options)
     : context
   if (!__BROWSER__ && mode === 'module') {
-    genModulePreamble(ast, preambleContext, isSetupInlined)
+    genModulePreamble(ast, preambleContext, genScopeId, isSetupInlined)
   } else {
     genFunctionPreamble(ast, preambleContext)
   }
@@ -226,7 +231,14 @@ export function generate(
       ? args.map(arg => `${arg}: any`).join(',')
       : args.join(', ')
 
-  if (isSetupInlined) {
+  if (genScopeId) {
+    if (isSetupInlined) {
+      push(`${PURE_ANNOTATION}${WITH_ID}(`)
+    } else {
+      push(`const ${functionName} = ${PURE_ANNOTATION}${WITH_ID}(`)
+    }
+  }
+  if (isSetupInlined || genScopeId) {
     push(`(${signature}) => {`)
   } else {
     push(`function ${functionName}(${signature}) {`)
@@ -291,6 +303,10 @@ export function generate(
   deindent()
   push(`}`)
 
+  if (genScopeId) {
+    push(`)`)
+  }
+
   return {
     ast,
     code: context.code,
@@ -361,6 +377,7 @@ function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
 function genModulePreamble(
   ast: RootNode,
   context: CodegenContext,
+  genScopeId: boolean,
   inline?: boolean
 ) {
   const {
@@ -369,12 +386,14 @@ function genModulePreamble(
     optimizeImports,
     runtimeModuleName,
     scopeId,
-    mode
+    helper
   } = context
 
-  const genScopeId = !__BROWSER__ && scopeId != null && mode === 'module'
-  if (genScopeId && ast.hoists.length) {
-    ast.helpers.push(SET_SCOPE_ID)
+  if (genScopeId) {
+    ast.helpers.push(WITH_SCOPE_ID)
+    if (ast.hoists.length) {
+      ast.helpers.push(PUSH_SCOPE_ID, POP_SCOPE_ID)
+    }
   }
 
   // generate import statements for helpers
@@ -417,6 +436,17 @@ function genModulePreamble(
     newline()
   }
 
+  // we technically don't need this anymore since `withCtx` already sets the
+  // correct scopeId, but this is necessary for backwards compat
+  if (genScopeId) {
+    push(
+      `const ${WITH_ID} = ${PURE_ANNOTATION}${helper(
+        WITH_SCOPE_ID
+      )}("${scopeId}")`
+    )
+    newline()
+  }
+
   genHoists(ast.hoists, context)
   newline()
 
@@ -463,7 +493,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(SET_SCOPE_ID)}("${scopeId}")`)
+    push(`${helper(PUSH_SCOPE_ID)}("${scopeId}")`)
     newline()
   }
 
@@ -476,7 +506,7 @@ function genHoists(hoists: (JSChildNode | null)[], context: CodegenContext) {
   })
 
   if (genScopeId) {
-    push(`${helper(SET_SCOPE_ID)}(null)`)
+    push(`${helper(POP_SCOPE_ID)}()`)
     newline()
   }
   context.pure = false
@@ -800,12 +830,15 @@ function genFunctionExpression(
   node: FunctionExpression,
   context: CodegenContext
 ) {
-  const { push, indent, deindent } = context
+  const { push, indent, deindent, scopeId, mode } = 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 (isSlot) {
     // wrap slot functions with owner context
-    push(`_${helperNameMap[WITH_CTX]}(`)
+    push(genScopeId ? `${WITH_ID}(` : `_${helperNameMap[WITH_CTX]}(`)
   }
   push(`(`, node)
   if (isArray(params)) {
index 5172b8c7f95823d27189069f9eadd0f0b1c9cedb..f40c94c3d89efe7ae312c8d642e4e749a694c47a 100644 (file)
@@ -25,7 +25,9 @@ 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 SET_SCOPE_ID = Symbol(__DEV__ ? `setScopeId` : ``)
+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 WITH_CTX = Symbol(__DEV__ ? `withCtx` : ``)
 export const UNREF = Symbol(__DEV__ ? `unref` : ``)
 export const IS_REF = Symbol(__DEV__ ? `isRef` : ``)
@@ -59,7 +61,9 @@ export const helperNameMap: any = {
   [CAPITALIZE]: `capitalize`,
   [TO_HANDLER_KEY]: `toHandlerKey`,
   [SET_BLOCK_TRACKING]: `setBlockTracking`,
-  [SET_SCOPE_ID]: `setScopeId`,
+  [PUSH_SCOPE_ID]: `pushScopeId`,
+  [POP_SCOPE_ID]: `popScopeId`,
+  [WITH_SCOPE_ID]: `withScopeId`,
   [WITH_CTX]: `withCtx`,
   [UNREF]: `unref`,
   [IS_REF]: `isRef`
index 5dc22257edf44f5fb744bab00f98adb521ee6f94..a5b5d81cd69c267827f3446c557cd649c65087e2 100644 (file)
@@ -34,7 +34,7 @@ export const transformSlotOutlet: NodeTransform = (node, context) => {
       slotArgs.push(createFunctionExpression([], children, false, false, loc))
     }
 
-    if (context.slotted) {
+    if (context.scopeId && !context.slotted) {
       if (!slotProps) {
         slotArgs.push(`{}`)
       }
index 954a7d44fbdc5cf56e49d5c585d1b55a61a77fa0..3d7ba96506f4f91ef9e6372d39f39098d1b7c3dd 100644 (file)
@@ -10,11 +10,13 @@ describe('ssr: scopeId', () => {
         mode: 'module'
       }).code
     ).toMatchInlineSnapshot(`
-      "import { ssrRenderAttrs as _ssrRenderAttrs } from \\"@vue/server-renderer\\"
+      "import { withScopeId as _withScopeId } from \\"vue\\"
+      import { ssrRenderAttrs as _ssrRenderAttrs } from \\"@vue/server-renderer\\"
+      const _withId = /*#__PURE__*/_withScopeId(\\"data-v-xxxxxxx\\")
 
-      export function ssrRender(_ctx, _push, _parent, _attrs) {
+      export const ssrRender = /*#__PURE__*/_withId((_ctx, _push, _parent, _attrs) => {
         _push(\`<div\${_ssrRenderAttrs(_attrs)} data-v-xxxxxxx><span data-v-xxxxxxx>hello</span></div>\`)
-      }"
+      })"
     `)
   })
 
@@ -26,14 +28,15 @@ describe('ssr: scopeId', () => {
         mode: 'module'
       }).code
     ).toMatchInlineSnapshot(`
-      "import { resolveComponent as _resolveComponent, withCtx as _withCtx, createTextVNode as _createTextVNode } from \\"vue\\"
+      "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\\")
 
-      export function ssrRender(_ctx, _push, _parent, _attrs) {
+      export const ssrRender = /*#__PURE__*/_withId((_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 {
@@ -44,7 +47,7 @@ describe('ssr: scopeId', () => {
           }),
           _: 1 /* STABLE */
         }, _parent))
-      }"
+      })"
     `)
   })
 
@@ -55,14 +58,15 @@ describe('ssr: scopeId', () => {
         mode: 'module'
       }).code
     ).toMatchInlineSnapshot(`
-      "import { resolveComponent as _resolveComponent, withCtx as _withCtx, createVNode as _createVNode } from \\"vue\\"
+      "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\\")
 
-      export function ssrRender(_ctx, _push, _parent, _attrs) {
+      export const ssrRender = /*#__PURE__*/_withId((_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 {
@@ -73,7 +77,7 @@ describe('ssr: scopeId', () => {
           }),
           _: 1 /* STABLE */
         }, _parent))
-      }"
+      })"
     `)
   })
 
@@ -84,19 +88,20 @@ describe('ssr: scopeId', () => {
         mode: 'module'
       }).code
     ).toMatchInlineSnapshot(`
-      "import { resolveComponent as _resolveComponent, withCtx as _withCtx, createVNode as _createVNode } from \\"vue\\"
+      "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\\")
 
-      export function ssrRender(_ctx, _push, _parent, _attrs) {
+      export const ssrRender = /*#__PURE__*/_withId((_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 {
@@ -111,7 +116,7 @@ describe('ssr: scopeId', () => {
               return [
                 _createVNode(\\"span\\", null, \\"hello\\"),
                 _createVNode(_component_bar, null, {
-                  default: _withCtx(() => [
+                  default: _withId(() => [
                     _createVNode(\\"span\\")
                   ]),
                   _: 1 /* STABLE */
@@ -121,7 +126,7 @@ describe('ssr: scopeId', () => {
           }),
           _: 1 /* STABLE */
         }, _parent))
-      }"
+      })"
     `)
   })
 })
index 415412e8354ede0feed7ac28b6da236d4f3cfbc0..b651aff56406cfea1b84c81f1bd61a64eea55bbf 100644 (file)
@@ -105,7 +105,7 @@ describe('ssr: <slot>', () => {
               _ssrRenderSlot(_ctx.$slots, \\"default\\", {}, null, _push, _parent, \\"hello-s\\" + _scopeId)
             } else {
               return [
-                _renderSlot(_ctx.$slots, \\"default\\", {}, undefined, true)
+                _renderSlot(_ctx.$slots, \\"default\\")
               ]
             }
           }),
index 4b565275fbd52606d8bf99f99e1da5c2ca04fe5a..c3c64970629a993612daa7cbb3a021170cf9f20c 100644 (file)
@@ -3,9 +3,12 @@ import {
   render,
   nodeOps,
   serializeInner,
-  renderSlot
+  renderSlot,
+  withScopeId,
+  pushScopeId,
+  popScopeId
 } from '@vue/runtime-test'
-import { setScopeId, withCtx } from '../src/componentRenderContext'
+import { withCtx } from '../src/componentRenderContext'
 
 describe('scopeId runtime support', () => {
   test('should attach scopeId', () => {
@@ -40,7 +43,7 @@ describe('scopeId runtime support', () => {
     const Child = {
       __scopeId: 'child',
       render(this: any) {
-        return h('div', renderSlot(this.$slots, 'default', {}, undefined, true))
+        return h('div', renderSlot(this.$slots, 'default'))
       }
     }
     const Child2 = {
@@ -82,7 +85,13 @@ describe('scopeId runtime support', () => {
       render(this: any) {
         // <div class="wrapper"><slot/></div>
         return h('div', { class: 'wrapper' }, [
-          renderSlot(this.$slots, 'default')
+          renderSlot(
+            this.$slots,
+            'default',
+            {},
+            undefined,
+            true /* noSlotted */
+          )
         ])
       }
     }
@@ -92,17 +101,15 @@ describe('scopeId runtime support', () => {
       render(this: any) {
         // <Wrapper><slot/></Wrapper>
         return h(Wrapper, null, {
-          default: withCtx(() => [
-            renderSlot(this.$slots, 'default', {}, undefined, true)
-          ])
+          default: withCtx(() => [renderSlot(this.$slots, 'default')])
         })
       }
     }
 
     // simulate hoisted node
-    setScopeId('root')
+    pushScopeId('root')
     const hoisted = h('div', 'hoisted')
-    setScopeId(null)
+    popScopeId()
 
     const Root = {
       __scopeId: 'root',
@@ -178,3 +185,124 @@ describe('scopeId runtime support', () => {
     expect(serializeInner(root)).toBe(`<div parent></div>`)
   })
 })
+
+describe('backwards compat with <=3.0.7', () => {
+  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', renderSlot(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>`)
+  })
+
+  test('hoisted nodes', async () => {
+    pushScopeId('foobar')
+    const hoisted = h('div', 'hello')
+    popScopeId()
+
+    const App = {
+      __scopeId: 'foobar',
+      render: () => h('div', [hoisted])
+    }
+
+    const root = nodeOps.createElement('div')
+    render(h(App), root)
+
+    expect(serializeInner(root)).toBe(
+      `<div foobar><div foobar>hello</div></div>`
+    )
+  })
+})
index c74712a0353e3468a87a23307cb3586208bfa052..1771d9aa0297e1531f431b913a556cadc17e124d 100644 (file)
@@ -32,10 +32,25 @@ export function setCurrentRenderingInstance(
  * Set scope id when creating hoisted vnodes.
  * @private compiler helper
  */
-export function setScopeId(id: string | null) {
+export function pushScopeId(id: string | null) {
   currentScopeId = id
 }
 
+/**
+ * Technically we no longer need this after 3.0.8 but we need to keep the same
+ * API for backwards compat w/ code generated by compilers.
+ * @private
+ */
+export function popScopeId() {
+  currentScopeId = null
+}
+
+/**
+ * Only for backwards compat
+ * @private
+ */
+export const withScopeId = (_id: string) => withCtx
+
 /**
  * Wrap a slot function to memoize current rendering instance
  * @private compiler helper
index 08b14558b1c0891c31ccce5f3b6e3fa69e3c44ba..26e7b82500878c9b9e536eb9b362db8c4a04e6aa 100644 (file)
@@ -26,7 +26,7 @@ export function renderSlot(
   // this is not a user-facing function, so the fallback is always generated by
   // the compiler and guaranteed to be a function returning an array
   fallback?: () => VNodeArrayChildren,
-  hasSlotted?: boolean
+  noSlotted?: boolean
 ): VNode {
   let slot = slots[name]
 
@@ -54,7 +54,7 @@ export function renderSlot(
       ? PatchFlags.STABLE_FRAGMENT
       : PatchFlags.BAIL
   )
-  if (hasSlotted && rendered.scopeId) {
+  if (!noSlotted && rendered.scopeId) {
     rendered.slotScopeIds = [rendered.scopeId + '-s']
   }
   isRenderingCompiledSlot--
index 98ba289f56567294e8661529bd77880fe6211ffc..6026d4c248dcc46035f63d1d8f569c2badefa221 100644 (file)
@@ -226,8 +226,13 @@ export { HMRRuntime } from './hmr'
 // user code should avoid relying on them.
 
 // For compiler generated code
-// should sync with '@vue/compiler-core/src/runtimeConstants.ts'
-export { withCtx, setScopeId } from './componentRenderContext'
+// should sync with '@vue/compiler-core/src/runtimeHelpers.ts'
+export {
+  withCtx,
+  pushScopeId,
+  popScopeId,
+  withScopeId
+} from './componentRenderContext'
 export { renderList } from './helpers/renderList'
 export { toHandlers } from './helpers/toHandlers'
 export { renderSlot } from './helpers/renderSlot'