From 7f93c76b9647aba058a2adfe77d8a3fdaa534a91 Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 5 May 2021 11:06:04 -0400 Subject: [PATCH] wip: exclude legacy slots from $scopedSlots --- packages/compiler-core/src/ast.ts | 5 ++ packages/compiler-core/src/codegen.ts | 3 + .../compiler-core/src/transforms/vSlot.ts | 16 +++-- packages/runtime-core/src/compat/instance.ts | 11 +++- packages/runtime-core/src/compat/renderFn.ts | 1 + .../src/componentRenderContext.ts | 6 +- .../vue-compat/__tests__/instance.spec.ts | 66 +++++++++++++------ 7 files changed, 80 insertions(+), 28 deletions(-) diff --git a/packages/compiler-core/src/ast.ts b/packages/compiler-core/src/ast.ts index 901f00ce..df843520 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 d2322f41..6367743d 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 e6387be6..0c3b2853 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 01c360b4..5ed4e19f 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 af8e78a3..44df5cbe 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 6cc23235..78557484 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 74e77225..3c71e831 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', () => {