// 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\\")
])
}
_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 */
}))
-}"
+})"
`;
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'
mode: 'module',
scopeId: 'test'
})
- expect(code).toMatch(`default: _withCtx(() => [`)
+ expect(code).toMatch(`default: _withId(() => [`)
expect(code).toMatchSnapshot()
})
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()
})
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()
})
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()
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 */))
}
}"
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 */))
}
}"
const { renderSlot: _renderSlot, createCommentVNode: _createCommentVNode } = _Vue
return ok
- ? _renderSlot($slots, \\"default\\", { key: 0 }, undefined, true)
+ ? _renderSlot($slots, \\"default\\", { key: 0 })
: _createCommentVNode(\\"v-if\\", true)
}
}"
const { renderSlot: _renderSlot, createCommentVNode: _createCommentVNode } = _Vue
return ok
- ? _renderSlot($slots, \\"default\\", { key: 0 }, undefined, true)
+ ? _renderSlot($slots, \\"default\\", { key: 0 })
: _createCommentVNode(\\"v-if\\", true)
}
}"
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]
)
function parseWithSlots(template: string, options: CompilerOptions = {}) {
const ast = parse(template)
transform(ast, {
- slotted: false,
nodeTransforms: [
...(options.prefixIdentifiers ? [transformExpression] : []),
transformSlotOutlet,
})
})
- 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,
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()
})
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()
})
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,
import { ImportItem } from './transform'
const PURE_ANNOTATION = `/*#__PURE__*/`
+const WITH_ID = `_withId`
type CodegenNode = TemplateChildNode | JSChildNode | SSRCodegenNode
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
? createCodegenContext(ast, options)
: context
if (!__BROWSER__ && mode === 'module') {
- genModulePreamble(ast, preambleContext, isSetupInlined)
+ genModulePreamble(ast, preambleContext, genScopeId, isSetupInlined)
} else {
genFunctionPreamble(ast, preambleContext)
}
? 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}) {`)
deindent()
push(`}`)
+ if (genScopeId) {
+ push(`)`)
+ }
+
return {
ast,
code: context.code,
function genModulePreamble(
ast: RootNode,
context: CodegenContext,
+ genScopeId: boolean,
inline?: boolean
) {
const {
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
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()
// 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()
}
})
if (genScopeId) {
- push(`${helper(SET_SCOPE_ID)}(null)`)
+ push(`${helper(POP_SCOPE_ID)}()`)
newline()
}
context.pure = false
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)) {
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` : ``)
[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`
slotArgs.push(createFunctionExpression([], children, false, false, loc))
}
- if (context.slotted) {
+ if (context.scopeId && !context.slotted) {
if (!slotProps) {
slotArgs.push(`{}`)
}
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>\`)
- }"
+ })"
`)
})
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 {
}),
_: 1 /* STABLE */
}, _parent))
- }"
+ })"
`)
})
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 {
}),
_: 1 /* STABLE */
}, _parent))
- }"
+ })"
`)
})
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 {
return [
_createVNode(\\"span\\", null, \\"hello\\"),
_createVNode(_component_bar, null, {
- default: _withCtx(() => [
+ default: _withId(() => [
_createVNode(\\"span\\")
]),
_: 1 /* STABLE */
}),
_: 1 /* STABLE */
}, _parent))
- }"
+ })"
`)
})
})
_ssrRenderSlot(_ctx.$slots, \\"default\\", {}, null, _push, _parent, \\"hello-s\\" + _scopeId)
} else {
return [
- _renderSlot(_ctx.$slots, \\"default\\", {}, undefined, true)
+ _renderSlot(_ctx.$slots, \\"default\\")
]
}
}),
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', () => {
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 = {
render(this: any) {
// <div class="wrapper"><slot/></div>
return h('div', { class: 'wrapper' }, [
- renderSlot(this.$slots, 'default')
+ renderSlot(
+ this.$slots,
+ 'default',
+ {},
+ undefined,
+ true /* noSlotted */
+ )
])
}
}
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',
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>`
+ )
+ })
+})
* 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
// 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]
? PatchFlags.STABLE_FRAGMENT
: PatchFlags.BAIL
)
- if (hasSlotted && rendered.scopeId) {
+ if (!noSlotted && rendered.scopeId) {
rendered.slotScopeIds = [rendered.scopeId + '-s']
}
isRenderingCompiledSlot--
// 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'