diff --git a/packages/compiler-core/src/ast.ts b/packages/compiler-core/src/ast.ts
index 901f00cef15..df843520209 100644
--- a/packages/compiler-core/src/ast.ts
+++ b/packages/compiler-core/src/ast.ts
@@ -352,6 +352,11 @@ export interface FunctionExpression extends Node {
* withScopeId() wrapper
*/
isSlot: boolean
+ /**
+ * __COMPAT__ only, indicates a slot function that should be excluded from
+ * the legacy $scopedSlots instance property.
+ */
+ isNonScopedSlot?: boolean
}
export interface ConditionalExpression extends Node {
diff --git a/packages/compiler-core/src/codegen.ts b/packages/compiler-core/src/codegen.ts
index d2322f4180c..6367743d8bc 100644
--- a/packages/compiler-core/src/codegen.ts
+++ b/packages/compiler-core/src/codegen.ts
@@ -878,6 +878,9 @@ function genFunctionExpression(
push(`}`)
}
if (isSlot) {
+ if (__COMPAT__ && node.isNonScopedSlot) {
+ push(`, undefined, true`)
+ }
push(`)`)
}
}
diff --git a/packages/compiler-core/src/transforms/vSlot.ts b/packages/compiler-core/src/transforms/vSlot.ts
index e6387be6c33..0c3b28536b6 100644
--- a/packages/compiler-core/src/transforms/vSlot.ts
+++ b/packages/compiler-core/src/transforms/vSlot.ts
@@ -129,11 +129,6 @@ export function buildSlots(
const slotsProperties: Property[] = []
const dynamicSlots: (ConditionalExpression | CallExpression)[] = []
- const buildDefaultSlotProperty = (
- props: ExpressionNode | undefined,
- children: TemplateChildNode[]
- ) => createObjectProperty(`default`, buildSlotFn(props, children, loc))
-
// If the slot is inside a v-for or another v-slot, force it to be dynamic
// since it likely uses a scope variable.
let hasDynamicSlots = context.scopes.vSlot > 0 || context.scopes.vFor > 0
@@ -302,6 +297,17 @@ export function buildSlots(
}
if (!onComponentSlot) {
+ const buildDefaultSlotProperty = (
+ props: ExpressionNode | undefined,
+ children: TemplateChildNode[]
+ ) => {
+ const fn = buildSlotFn(props, children, loc)
+ if (__COMPAT__) {
+ fn.isNonScopedSlot = true
+ }
+ return createObjectProperty(`default`, fn)
+ }
+
if (!hasTemplateSlots) {
// implicit default slot (on component)
slotsProperties.push(buildDefaultSlotProperty(undefined, children))
diff --git a/packages/runtime-core/src/compat/instance.ts b/packages/runtime-core/src/compat/instance.ts
index 01c360b4615..5ed4e19f0ec 100644
--- a/packages/runtime-core/src/compat/instance.ts
+++ b/packages/runtime-core/src/compat/instance.ts
@@ -36,7 +36,7 @@ import {
} from './renderHelpers'
import { resolveFilter } from '../helpers/resolveAssets'
import { resolveMergedOptions } from '../componentOptions'
-import { Slots } from '../componentSlots'
+import { InternalSlots, Slots } from '../componentSlots'
export type LegacyPublicInstance = ComponentPublicInstance &
LegacyPublicProperties
@@ -103,7 +103,14 @@ export function installCompatInstanceProperties(map: PublicPropertiesMap) {
$scopedSlots: i => {
assertCompatEnabled(DeprecationTypes.INSTANCE_SCOPED_SLOTS, i)
- return __DEV__ ? shallowReadonly(i.slots) : i.slots
+ const res: InternalSlots = {}
+ for (const key in i.slots) {
+ const fn = i.slots[key]!
+ if (!(fn as any)._nonScoped) {
+ res[key] = fn
+ }
+ }
+ return res
},
$on: i => on.bind(null, i),
diff --git a/packages/runtime-core/src/compat/renderFn.ts b/packages/runtime-core/src/compat/renderFn.ts
index af8e78a3379..44df5cbe841 100644
--- a/packages/runtime-core/src/compat/renderFn.ts
+++ b/packages/runtime-core/src/compat/renderFn.ts
@@ -281,6 +281,7 @@ function convertLegacySlots(vnode: VNode): VNode {
for (const key in slots) {
const slotChildren = slots[key]
slots[key] = () => slotChildren
+ slots[key]._nonScoped = true
}
}
}
diff --git a/packages/runtime-core/src/componentRenderContext.ts b/packages/runtime-core/src/componentRenderContext.ts
index 6cc232358b5..78557484586 100644
--- a/packages/runtime-core/src/componentRenderContext.ts
+++ b/packages/runtime-core/src/componentRenderContext.ts
@@ -61,7 +61,8 @@ export const withScopeId = (_id: string) => withCtx
*/
export function withCtx(
fn: Function,
- ctx: ComponentInternalInstance | null = currentRenderingInstance
+ ctx: ComponentInternalInstance | null = currentRenderingInstance,
+ isNonScopedSlot?: boolean // __COMPAT__ only
) {
if (!ctx) return fn
const renderFnWithContext = (...args: any[]) => {
@@ -83,5 +84,8 @@ export function withCtx(
// this is used in vnode.ts -> normalizeChildren() to set the slot
// rendering flag.
renderFnWithContext._c = true
+ if (__COMPAT__ && isNonScopedSlot) {
+ renderFnWithContext._nonScoped = true
+ }
return renderFnWithContext
}
diff --git a/packages/vue-compat/__tests__/instance.spec.ts b/packages/vue-compat/__tests__/instance.spec.ts
index 74e77225c30..3c71e831f4a 100644
--- a/packages/vue-compat/__tests__/instance.spec.ts
+++ b/packages/vue-compat/__tests__/instance.spec.ts
@@ -251,30 +251,56 @@ test('INSTANCE_LISTENERS', () => {
).toHaveBeenWarned()
})
-test('INSTANCE_SCOPED_SLOTS', () => {
- let slots: Slots
- new Vue({
- template: `{{ msg }}`,
- components: {
- child: {
- compatConfig: { RENDER_FUNCTION: false },
- render() {
- slots = this.$scopedSlots
+describe('INSTANCE_SCOPED_SLOTS', () => {
+ test('explicit usage', () => {
+ let slots: Slots
+ new Vue({
+ template: `{{ msg }}`,
+ components: {
+ child: {
+ compatConfig: { RENDER_FUNCTION: false },
+ render() {
+ slots = this.$scopedSlots
+ }
}
}
- }
- }).$mount()
+ }).$mount()
- expect(slots!.default!({ msg: 'hi' })).toMatchObject([
- {
- type: Text,
- children: 'hi'
- }
- ])
+ expect(slots!.default!({ msg: 'hi' })).toMatchObject([
+ {
+ type: Text,
+ children: 'hi'
+ }
+ ])
- expect(
- deprecationData[DeprecationTypes.INSTANCE_SCOPED_SLOTS].message
- ).toHaveBeenWarned()
+ expect(
+ deprecationData[DeprecationTypes.INSTANCE_SCOPED_SLOTS].message
+ ).toHaveBeenWarned()
+ })
+
+ test('should not include legacy slot usage in $scopedSlots', () => {
+ let normalSlots: Slots
+ let scopedSlots: Slots
+ new Vue({
+ template: `default
`,
+ components: {
+ child: {
+ compatConfig: { RENDER_FUNCTION: false },
+ render() {
+ normalSlots = this.$slots
+ scopedSlots = this.$scopedSlots
+ }
+ }
+ }
+ }).$mount()
+
+ expect('default' in normalSlots!).toBe(true)
+ expect('default' in scopedSlots!).toBe(false)
+
+ expect(
+ deprecationData[DeprecationTypes.INSTANCE_SCOPED_SLOTS].message
+ ).toHaveBeenWarned()
+ })
})
test('INSTANCE_ATTR_CLASS_STYLE', () => {