diff --git a/packages/compiler-core/__tests__/__snapshots__/scopeId.spec.ts.snap b/packages/compiler-core/__tests__/__snapshots__/scopeId.spec.ts.snap
index 7af32808b..4076e61db 100644
--- a/packages/compiler-core/__tests__/__snapshots__/scopeId.spec.ts.snap
+++ b/packages/compiler-core/__tests__/__snapshots__/scopeId.spec.ts.snap
@@ -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\\"))
-})"
+}"
`;
diff --git a/packages/compiler-core/__tests__/scopeId.spec.ts b/packages/compiler-core/__tests__/scopeId.spec.ts
index 710c65bff..3c1d6d554 100644
--- a/packages/compiler-core/__tests__/scopeId.spec.ts
+++ b/packages/compiler-core/__tests__/scopeId.spec.ts
@@ -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(`
`, {
- 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(``, {
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()
diff --git a/packages/compiler-core/src/codegen.ts b/packages/compiler-core/src/codegen.ts
index 20f7e33ec..cd5723132 100644
--- a/packages/compiler-core/src/codegen.ts
+++ b/packages/compiler-core/src/codegen.ts
@@ -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(`)`)
}
}
diff --git a/packages/compiler-core/src/runtimeHelpers.ts b/packages/compiler-core/src/runtimeHelpers.ts
index f40c94c3d..5172b8c7f 100644
--- a/packages/compiler-core/src/runtimeHelpers.ts
+++ b/packages/compiler-core/src/runtimeHelpers.ts
@@ -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`
diff --git a/packages/compiler-ssr/__tests__/ssrScopeId.spec.ts b/packages/compiler-ssr/__tests__/ssrScopeId.spec.ts
index 5d7951c1f..954a7d44f 100644
--- a/packages/compiler-ssr/__tests__/ssrScopeId.spec.ts
+++ b/packages/compiler-ssr/__tests__/ssrScopeId.spec.ts
@@ -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(\`hello
\`)
- })"
+ }"
`)
})
@@ -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(\`hello\`)
} 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(\`hello\`)
_push(_ssrRenderComponent(_component_bar, null, {
- default: _withId((_, _push, _parent, _scopeId) => {
+ default: _withCtx((_, _push, _parent, _scopeId) => {
if (_push) {
_push(\`\`)
} 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))
- })"
+ }"
`)
})
})
diff --git a/packages/compiler-ssr/__tests__/ssrSlotOutlet.spec.ts b/packages/compiler-ssr/__tests__/ssrSlotOutlet.spec.ts
index 668a80e87..2219ff077 100644
--- a/packages/compiler-ssr/__tests__/ssrSlotOutlet.spec.ts
+++ b/packages/compiler-ssr/__tests__/ssrSlotOutlet.spec.ts
@@ -6,7 +6,7 @@ describe('ssr: ', () => {
"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: ', () => {
"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: ', () => {
"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: ', () => {
_ssrRenderSlot(_ctx.$slots, \\"foo\\", {
p: 1,
bar: \\"2\\"
- }, null, _push, _parent)
+ }, null, _push, _parent, null)
}"
`)
})
@@ -53,7 +53,49 @@ describe('ssr: ', () => {
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(``, {
+ 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(``, {
+ 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))
}"
`)
})
diff --git a/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts b/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts
index 5acf8000b..de96b7efc 100644
--- a/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts
+++ b/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts
@@ -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(
diff --git a/packages/compiler-ssr/src/transforms/ssrTransformSlotOutlet.ts b/packages/compiler-ssr/src/transforms/ssrTransformSlotOutlet.ts
index b40d17ab4..b2b2de4f5 100644
--- a/packages/compiler-ssr/src/transforms/ssrTransformSlotOutlet.ts
+++ b/packages/compiler-ssr/src/transforms/ssrTransformSlotOutlet.ts
@@ -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 . 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!)
}
diff --git a/packages/runtime-core/__tests__/components/KeepAlive.spec.ts b/packages/runtime-core/__tests__/components/KeepAlive.spec.ts
index 0b3e7f593..1cc7fe01e 100644
--- a/packages/runtime-core/__tests__/components/KeepAlive.spec.ts
+++ b/packages/runtime-core/__tests__/components/KeepAlive.spec.ts
@@ -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(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(`one
`)
diff --git a/packages/runtime-core/__tests__/helpers/renderSlot.spec.ts b/packages/runtime-core/__tests__/helpers/renderSlot.spec.ts
index 99f2292b7..0cc8f3bab 100644
--- a/packages/runtime-core/__tests__/helpers/renderSlot.spec.ts
+++ b/packages/runtime-core/__tests__/helpers/renderSlot.spec.ts
@@ -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
index f570c7f0c..000000000
--- a/packages/runtime-core/__tests__/helpers/scopeId.spec.ts
+++ /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(``)
- })
-
- 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', 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(``)
- })
-})
diff --git a/packages/runtime-core/__tests__/scopeId.spec.ts b/packages/runtime-core/__tests__/scopeId.spec.ts
new file mode 100644
index 000000000..e81af6c56
--- /dev/null
+++ b/packages/runtime-core/__tests__/scopeId.spec.ts
@@ -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(``)
+ })
+
+ 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(
+ ``
+ )
+ })
+
+ // :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(
+ `` +
+ `
` +
+ // component inside slot should have:
+ // - scopeId from template context
+ // - slotted scopeId from slot owner
+ // - its own scopeId
+ `
` +
+ `
`
+ )
+ })
+
+ // #2892
+ test(':slotted on forwarded slots', async () => {
+ const Wrapper = {
+ __scopeId: 'wrapper',
+ render(this: any) {
+ //
+ return h('div', { class: 'wrapper' }, [
+ renderSlot(this.$slots, 'default')
+ ])
+ }
+ }
+
+ const Slotted = {
+ __scopeId: 'slotted',
+ render(this: any) {
+ //
+ 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) {
+ // hoisted
{{ dynamic }}
+ return h(Slotted, null, {
+ default: withCtx(() => {
+ return [hoisted, h('div', 'dynamic')]
+ })
+ })
+ }
+ }
+
+ const root = nodeOps.createElement('div')
+ render(h(Root), root)
+ expect(serializeInner(root)).toBe(
+ `` +
+ `
hoisted
` +
+ `
dynamic
` +
+ `
`
+ )
+
+ const Root2 = {
+ __scopeId: 'root',
+ render(this: any) {
+ //
+ //
+ // hoisted
{{ dynamic }}
+ //
+ //
+ 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(
+ `` +
+ `
` +
+ `
hoisted
` +
+ `
dynamic
` +
+ `
` +
+ `
`
+ )
+ })
+
+ // #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(``)
+ })
+})
diff --git a/packages/runtime-core/__tests__/vnode.spec.ts b/packages/runtime-core/__tests__/vnode.spec.ts
index 56f1e7b43..b5a501053 100644
--- a/packages/runtime-core/__tests__/vnode.spec.ts
+++ b/packages/runtime-core/__tests__/vnode.spec.ts
@@ -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' })
diff --git a/packages/runtime-core/src/apiInject.ts b/packages/runtime-core/src/apiInject.ts
index 402113cd1..a1ec6126b 100644
--- a/packages/runtime-core/src/apiInject.ts
+++ b/packages/runtime-core/src/apiInject.ts
@@ -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 extends Symbol {}
diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts
index 9b1420362..bfb7736b4 100644
--- a/packages/runtime-core/src/component.ts
+++ b/packages/runtime-core/src/component.ts
@@ -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
diff --git a/packages/runtime-core/src/componentPublicInstance.ts b/packages/runtime-core/src/componentPublicInstance.ts
index c493fea0b..bbdb03360 100644
--- a/packages/runtime-core/src/componentPublicInstance.ts
+++ b/packages/runtime-core/src/componentPublicInstance.ts
@@ -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
index 000000000..78297dc5a
--- /dev/null
+++ b/packages/runtime-core/src/componentRenderContext.ts
@@ -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 ``.
+ 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
+}
diff --git a/packages/runtime-core/src/componentRenderUtils.ts b/packages/runtime-core/src/componentRenderUtils.ts
index c1454e96c..89cdcf19c 100644
--- a/packages/runtime-core/src/componentRenderUtils.ts
+++ b/packages/runtime-core/src/componentRenderUtils.ts
@@ -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
}
diff --git a/packages/runtime-core/src/componentSlots.ts b/packages/runtime-core/src/componentSlots.ts
index 4a6a1b753..fbb009697 100644
--- a/packages/runtime-core/src/componentSlots.ts
+++ b/packages/runtime-core/src/componentSlots.ts
@@ -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[]
diff --git a/packages/runtime-core/src/components/KeepAlive.ts b/packages/runtime-core/src/components/KeepAlive.ts
index 6ad53fda7..b6d04cbba 100644
--- a/packages/runtime-core/src/components/KeepAlive.ts
+++ b/packages/runtime-core/src/components/KeepAlive.ts
@@ -112,6 +112,7 @@ const KeepAliveImpl = {
instance,
parentSuspense,
isSVG,
+ vnode.slotScopeIds,
optimized
)
queuePostRenderEffect(() => {
diff --git a/packages/runtime-core/src/components/Suspense.ts b/packages/runtime-core/src/components/Suspense.ts
index 1b5adfed3..0ec78125b 100644
--- a/packages/runtime-core/src/components/Suspense.ts
+++ b/packages/runtime-core/src/components/Suspense.ts
@@ -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) {
diff --git a/packages/runtime-core/src/components/Teleport.ts b/packages/runtime-core/src/components/Teleport.ts
index fa61c636a..e75455f96 100644
--- a/packages/runtime-core/src/components/Teleport.ts
+++ b/packages/runtime-core/src/components/Teleport.ts
@@ -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
)
}
diff --git a/packages/runtime-core/src/directives.ts b/packages/runtime-core/src/directives.ts
index c909e9a2e..20f25d03e 100644
--- a/packages/runtime-core/src/directives.ts
+++ b/packages/runtime-core/src/directives.ts
@@ -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'
diff --git a/packages/runtime-core/src/helpers/renderSlot.ts b/packages/runtime-core/src/helpers/renderSlot.ts
index 420c4ffc1..56cdd3dcd 100644
--- a/packages/runtime-core/src/helpers/renderSlot.ts
+++ b/packages/runtime-core/src/helpers/renderSlot.ts
@@ -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
}
diff --git a/packages/runtime-core/src/helpers/resolveAssets.ts b/packages/runtime-core/src/helpers/resolveAssets.ts
index 986079a95..1d6a96bf9 100644
--- a/packages/runtime-core/src/helpers/resolveAssets.ts
+++ b/packages/runtime-core/src/helpers/resolveAssets.ts
@@ -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
index fbefe04a4..000000000
--- a/packages/runtime-core/src/helpers/scopeId.ts
+++ /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): (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
index 88a29ae32..000000000
--- a/packages/runtime-core/src/helpers/withRenderContext.ts
+++ /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 ``.
- 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
-}
diff --git a/packages/runtime-core/src/hydration.ts b/packages/runtime-core/src/hydration.ts
index c7c88eb65..cad23524e 100644
--- a/packages/runtime-core/src/hydration.ts
+++ b/packages/runtime-core/src/hydration.ts
@@ -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
}
diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts
index a24672226..98ba289f5 100644
--- a/packages/runtime-core/src/index.ts
+++ b/packages/runtime-core/src/index.ts
@@ -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 = {
diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts
index 3089b1027..f2a35794c 100644
--- a/packages/runtime-core/src/renderer.ts
+++ b/packages/runtime-core/src/renderer.ts
@@ -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:
diff --git a/packages/runtime-core/src/vnode.ts b/packages/runtime-core/src/vnode.ts
index 92d7c4f42..e46c98426 100644
--- a/packages/runtime-core/src/vnode.ts
+++ b/packages/runtime-core/src/vnode.ts
@@ -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(
: normalizeRef(extraProps)
: ref,
scopeId: vnode.scopeId,
+ slotScopeIds: vnode.slotScopeIds,
children:
__DEV__ && patchFlag === PatchFlags.HOISTED && isArray(children)
? (children as VNode[]).map(deepCloneVNode)
diff --git a/packages/server-renderer/__tests__/render.spec.ts b/packages/server-renderer/__tests__/render.spec.ts
index a02bf365c..806ca210b 100644
--- a/packages/server-renderer/__tests__/render.spec.ts
+++ b/packages/server-renderer/__tests__/render.spec.ts
@@ -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(``)
+ const Foo = {
+ __scopeId: 'data-v-test',
+ render() {
+ return h('div')
+ }
+ }
+ expect(await render(h(Foo))).toBe(``)
})
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(
diff --git a/packages/server-renderer/__tests__/ssrScopeId.spec.ts b/packages/server-renderer/__tests__/ssrScopeId.spec.ts
index 8b58fc66b..7726739e4 100644
--- a/packages/server-renderer/__tests__/ssrScopeId.spec.ts
+++ b/packages/server-renderer/__tests__/ssrScopeId.spec.ts
@@ -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(``)
@@ -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(``)
})
- test('inside slot', async () => {
- const withParentId = withScopeId('parent')
-
+ test('id and :slotted on component root', async () => {
const Child = {
+ //
ssrRender: (_: any, push: any, _parent: any, attrs: any) => {
push(``)
}
@@ -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)
+ //
+ 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) => {
+ //
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(``)
})
+
+ // #2892
+ test(':slotted on forwarded slots', async () => {
+ const Wrapper = {
+ __scopeId: 'wrapper',
+ ssrRender: (ctx: any, push: any, parent: any, attrs: any) => {
+ //
+ push(
+ ``
+ )
+ ssrRenderSlot(
+ ctx.$slots,
+ 'default',
+ {},
+ null,
+ push,
+ parent,
+ 'wrapper-s'
+ )
+ push(`
`)
+ }
+ }
+
+ const Slotted = {
+ __scopeId: 'slotted',
+ ssrRender: (ctx: any, push: any, parent: any, attrs: any) => {
+ //
+ 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',
+ //
+ ssrRender: (_: any, push: any, parent: any, attrs: any) => {
+ push(
+ ssrRenderComponent(
+ Slotted,
+ attrs,
+ {
+ default: withCtx(
+ (_: any, push: any, parent: any, scopeId: string) => {
+ push(``)
+ }
+ ),
+ _: 1
+ } as any,
+ parent
+ )
+ )
+ }
+ }
+
+ const result = await renderToString(createApp(Root))
+ expect(result).toBe(
+ ``
+ )
+ })
})
diff --git a/packages/server-renderer/src/helpers/ssrRenderComponent.ts b/packages/server-renderer/src/helpers/ssrRenderComponent.ts
index 000f2b482..4709f23a6 100644
--- a/packages/server-renderer/src/helpers/ssrRenderComponent.ts
+++ b/packages/server-renderer/src/helpers/ssrRenderComponent.ts
@@ -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 {
return renderComponentVNode(
createVNode(comp, props, children),
- parentComponent
+ parentComponent,
+ slotScopeId
)
}
diff --git a/packages/server-renderer/src/helpers/ssrRenderSlot.ts b/packages/server-renderer/src/helpers/ssrRenderSlot.ts
index 5ee6113a1..3f3589a3b 100644
--- a/packages/server-renderer/src/helpers/ssrRenderSlot.ts
+++ b/packages/server-renderer/src/helpers/ssrRenderSlot.ts
@@ -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
diff --git a/packages/server-renderer/src/render.ts b/packages/server-renderer/src/render.ts
index 938a4f83a..fd40f3be4 100644
--- a/packages/server-renderer/src/render.ts
+++ b/packages/server-renderer/src/render.ts
@@ -80,7 +80,8 @@ export function createBuffer() {
export function renderComponentVNode(
vnode: VNode,
- parentComponent: ComponentInternalInstance | null = null
+ parentComponent: ComponentInternalInstance | null = null,
+ slotScopeId?: string
): SSRBuffer | Promise {
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 {
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