diff --git a/packages/compiler-core/__tests__/__snapshots__/scopeId.spec.ts.snap b/packages/compiler-core/__tests__/__snapshots__/scopeId.spec.ts.snap index 4076e61db6a..aa74c6c43d1 100644 --- a/packages/compiler-core/__tests__/__snapshots__/scopeId.spec.ts.snap +++ b/packages/compiler-core/__tests__/__snapshots__/scopeId.spec.ts.snap @@ -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 */ })) -}" +})" `; diff --git a/packages/compiler-core/__tests__/scopeId.spec.ts b/packages/compiler-core/__tests__/scopeId.spec.ts index 3c1d6d554b7..37ffb8893ca 100644 --- a/packages/compiler-core/__tests__/scopeId.spec.ts +++ b/packages/compiler-core/__tests__/scopeId.spec.ts @@ -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() diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/vFor.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/vFor.spec.ts.snap index 3259964e991..47c990674cc 100644 --- a/packages/compiler-core/__tests__/transforms/__snapshots__/vFor.spec.ts.snap +++ b/packages/compiler-core/__tests__/transforms/__snapshots__/vFor.spec.ts.snap @@ -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 */)) } }" diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/vIf.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/vIf.spec.ts.snap index 961716a1fc1..518a3f1ffb0 100644 --- a/packages/compiler-core/__tests__/transforms/__snapshots__/vIf.spec.ts.snap +++ b/packages/compiler-core/__tests__/transforms/__snapshots__/vIf.spec.ts.snap @@ -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) } }" diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap index 59836a12c6e..f4897194f03 100644 --- a/packages/compiler-core/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap +++ b/packages/compiler-core/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap @@ -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] ) diff --git a/packages/compiler-core/__tests__/transforms/transformSlotOutlet.spec.ts b/packages/compiler-core/__tests__/transforms/transformSlotOutlet.spec.ts index 3cfa8d07757..219c209856c 100644 --- a/packages/compiler-core/__tests__/transforms/transformSlotOutlet.spec.ts +++ b/packages/compiler-core/__tests__/transforms/transformSlotOutlet.spec.ts @@ -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 outlets', () => { }) }) - test('slot with slotted: true', async () => { - const ast = parseWithSlots(``, { slotted: true }) + test('slot with slotted: false', async () => { + const ast = parseWithSlots(``, { slotted: false, scopeId: 'foo' }) expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({ type: NodeTypes.JS_CALL_EXPRESSION, callee: RENDER_SLOT, diff --git a/packages/compiler-core/__tests__/transforms/vIf.spec.ts b/packages/compiler-core/__tests__/transforms/vIf.spec.ts index 846a4d9a956..f1b91cc4567 100644 --- a/packages/compiler-core/__tests__/transforms/vIf.spec.ts +++ b/packages/compiler-core/__tests__/transforms/vIf.spec.ts @@ -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() }) diff --git a/packages/compiler-core/src/codegen.ts b/packages/compiler-core/src/codegen.ts index 3acb8cf5423..939b3b356ad 100644 --- a/packages/compiler-core/src/codegen.ts +++ b/packages/compiler-core/src/codegen.ts @@ -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)) { diff --git a/packages/compiler-core/src/runtimeHelpers.ts b/packages/compiler-core/src/runtimeHelpers.ts index 5172b8c7f95..f40c94c3d89 100644 --- a/packages/compiler-core/src/runtimeHelpers.ts +++ b/packages/compiler-core/src/runtimeHelpers.ts @@ -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` diff --git a/packages/compiler-core/src/transforms/transformSlotOutlet.ts b/packages/compiler-core/src/transforms/transformSlotOutlet.ts index 5dc22257edf..a5b5d81cd69 100644 --- a/packages/compiler-core/src/transforms/transformSlotOutlet.ts +++ b/packages/compiler-core/src/transforms/transformSlotOutlet.ts @@ -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(`{}`) } diff --git a/packages/compiler-ssr/__tests__/ssrScopeId.spec.ts b/packages/compiler-ssr/__tests__/ssrScopeId.spec.ts index 954a7d44fbd..3d7ba96506f 100644 --- a/packages/compiler-ssr/__tests__/ssrScopeId.spec.ts +++ b/packages/compiler-ssr/__tests__/ssrScopeId.spec.ts @@ -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(\`hello\`) - }" + })" `) }) @@ -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(\`hello\`) } 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(\`hello\`) _push(_ssrRenderComponent(_component_bar, null, { - default: _withCtx((_, _push, _parent, _scopeId) => { + default: _withId((_, _push, _parent, _scopeId) => { if (_push) { _push(\`\`) } 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)) - }" + })" `) }) }) diff --git a/packages/compiler-ssr/__tests__/ssrSlotOutlet.spec.ts b/packages/compiler-ssr/__tests__/ssrSlotOutlet.spec.ts index 415412e8354..b651aff5640 100644 --- a/packages/compiler-ssr/__tests__/ssrSlotOutlet.spec.ts +++ b/packages/compiler-ssr/__tests__/ssrSlotOutlet.spec.ts @@ -105,7 +105,7 @@ describe('ssr: ', () => { _ssrRenderSlot(_ctx.$slots, \\"default\\", {}, null, _push, _parent, \\"hello-s\\" + _scopeId) } else { return [ - _renderSlot(_ctx.$slots, \\"default\\", {}, undefined, true) + _renderSlot(_ctx.$slots, \\"default\\") ] } }), diff --git a/packages/runtime-core/__tests__/scopeId.spec.ts b/packages/runtime-core/__tests__/scopeId.spec.ts index 4b565275fbd..c3c64970629 100644 --- a/packages/runtime-core/__tests__/scopeId.spec.ts +++ b/packages/runtime-core/__tests__/scopeId.spec.ts @@ -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) { //
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) { // 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(`
`) }) }) + +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(`
`) + }) + + 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( + `
` + ) + }) + + 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( + `
` + + `
` + + // component inside slot should have: + // - scopeId from template context + // - slotted scopeId from slot owner + // - its own scopeId + `` + + `
` + ) + }) + + // #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(`
`) + }) + + 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( + `
hello
` + ) + }) +}) diff --git a/packages/runtime-core/src/componentRenderContext.ts b/packages/runtime-core/src/componentRenderContext.ts index c74712a0353..1771d9aa029 100644 --- a/packages/runtime-core/src/componentRenderContext.ts +++ b/packages/runtime-core/src/componentRenderContext.ts @@ -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 diff --git a/packages/runtime-core/src/helpers/renderSlot.ts b/packages/runtime-core/src/helpers/renderSlot.ts index 08b14558b1c..26e7b825008 100644 --- a/packages/runtime-core/src/helpers/renderSlot.ts +++ b/packages/runtime-core/src/helpers/renderSlot.ts @@ -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-- diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index 98ba289f565..6026d4c248d 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -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'