From 9fa6fc3bc1ba022c206763775dc9f35d09fce984 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Wed, 29 Mar 2023 21:02:41 +0800 Subject: [PATCH 01/14] feat: add defineSlots macro --- packages/compiler-sfc/src/compileScript.ts | 27 ++++++++++++++++++- packages/runtime-core/src/apiSetupHelpers.ts | 12 +++++++++ .../types/scriptSetupHelpers.d.ts | 2 ++ 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index de9e11d071f..cdb03757cbe 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -67,6 +67,7 @@ const DEFINE_EMITS = 'defineEmits' const DEFINE_EXPOSE = 'defineExpose' const WITH_DEFAULTS = 'withDefaults' const DEFINE_OPTIONS = 'defineOptions' +const DEFINT_SLOTS = 'defineSlots' const isBuiltInDir = makeMap( `once,memo,if,for,else,else-if,slot,text,html,on,bind,model,show,cloak,is` @@ -312,6 +313,7 @@ export function compileScript( let hasDefaultExportName = false let hasDefaultExportRender = false let hasDefineOptionsCall = false + let hasDefineSlotsCall = false let propsRuntimeDecl: Node | undefined let propsRuntimeDefaults: Node | undefined let propsDestructureDecl: Node | undefined @@ -590,6 +592,26 @@ export function compileScript( return true } + function processDefineSlots(node: Node, declId?: LVal): boolean { + if (!isCallOf(node, DEFINT_SLOTS)) { + return false + } + if (hasDefineSlotsCall) { + error(`duplicate ${DEFINT_SLOTS}() call`, node) + } + hasDefineSlotsCall = true + + if (declId) { + s.overwrite( + startOffset + node.start!, + startOffset + node.end!, + `${helper('useSlots')}()` + ) + } + + return true + } + function getAstBody(): Statement[] { return scriptAst ? [...scriptSetupAst.body, ...scriptAst.body] @@ -1286,7 +1308,8 @@ export function compileScript( processDefineProps(expr) || processDefineEmits(expr) || processDefineOptions(expr) || - processWithDefaults(expr) + processWithDefaults(expr) || + processDefineSlots(expr) ) { s.remove(node.start! + startOffset, node.end! + startOffset) } else if (processDefineExpose(expr)) { @@ -1321,6 +1344,8 @@ export function compileScript( processDefineProps(init, decl.id) || processWithDefaults(init, decl.id) const isDefineEmits = processDefineEmits(init, decl.id) + processDefineSlots(init, decl.id) + if (isDefineProps || isDefineEmits) { if (left === 1) { s.remove(node.start! + startOffset, node.end! + startOffset) diff --git a/packages/runtime-core/src/apiSetupHelpers.ts b/packages/runtime-core/src/apiSetupHelpers.ts index 31307aa7e75..c560f142a2f 100644 --- a/packages/runtime-core/src/apiSetupHelpers.ts +++ b/packages/runtime-core/src/apiSetupHelpers.ts @@ -25,6 +25,7 @@ import { ExtractPropTypes } from './componentProps' import { warn } from './warning' +import { VNode } from './vnode' // dev only const warnRuntimeUsage = (method: string) => @@ -192,6 +193,17 @@ export function defineOptions< } } +export function defineSlots< + T extends Record +>(): // @ts-expect-error +{ + [K in keyof T]: (scope: T[K]) => VNode[] | undefined +} { + if (__DEV__) { + warnRuntimeUsage(`defineSlots`) + } +} + type NotUndefined = T extends undefined ? never : T type InferDefaults = { diff --git a/packages/runtime-core/types/scriptSetupHelpers.d.ts b/packages/runtime-core/types/scriptSetupHelpers.d.ts index ba4ca79fc59..4ffc2f725ae 100644 --- a/packages/runtime-core/types/scriptSetupHelpers.d.ts +++ b/packages/runtime-core/types/scriptSetupHelpers.d.ts @@ -4,6 +4,7 @@ type _defineProps = typeof defineProps type _defineEmits = typeof defineEmits type _defineExpose = typeof defineExpose type _defineOptions = typeof defineOptions +type _defineSlots = typeof defineSlots type _withDefaults = typeof withDefaults declare global { @@ -11,5 +12,6 @@ declare global { const defineEmits: _defineEmits const defineExpose: _defineExpose const defineOptions: _defineOptions + const defineSlots: _defineSlots const withDefaults: _withDefaults } From 8310e5605933914666e5ab1180a162d2d50ec95b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Wed, 29 Mar 2023 21:17:28 +0800 Subject: [PATCH 02/14] fix: types --- packages/compiler-sfc/src/compileScript.ts | 6 +++--- packages/runtime-core/src/index.ts | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index cdb03757cbe..c735525d084 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -67,7 +67,7 @@ const DEFINE_EMITS = 'defineEmits' const DEFINE_EXPOSE = 'defineExpose' const WITH_DEFAULTS = 'withDefaults' const DEFINE_OPTIONS = 'defineOptions' -const DEFINT_SLOTS = 'defineSlots' +const DEFINE_SLOTS = 'defineSlots' const isBuiltInDir = makeMap( `once,memo,if,for,else,else-if,slot,text,html,on,bind,model,show,cloak,is` @@ -593,11 +593,11 @@ export function compileScript( } function processDefineSlots(node: Node, declId?: LVal): boolean { - if (!isCallOf(node, DEFINT_SLOTS)) { + if (!isCallOf(node, DEFINE_SLOTS)) { return false } if (hasDefineSlotsCall) { - error(`duplicate ${DEFINT_SLOTS}() call`, node) + error(`duplicate ${DEFINE_SLOTS}() call`, node) } hasDefineSlotsCall = true diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index 06f9a2affd4..f388ba7b65d 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -69,6 +69,7 @@ export { defineEmits, defineExpose, defineOptions, + defineSlots, withDefaults, // internal mergeDefaults, From 477dd22af9c0de27fda8cf124d5cfcf0b3b02d1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Thu, 30 Mar 2023 14:31:01 +0800 Subject: [PATCH 03/14] feat: disallow runtime --- packages/compiler-sfc/src/compileScript.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index c735525d084..7d1b96186a4 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -601,6 +601,10 @@ export function compileScript( } hasDefineSlotsCall = true + if (node.arguments) { + error(`${DEFINE_SLOTS}() cannot accept arguments`, node) + } + if (declId) { s.overwrite( startOffset + node.start!, From 05c4dca1447cd2d262138defff46333c11c7b114 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Thu, 30 Mar 2023 15:49:53 +0800 Subject: [PATCH 04/14] feat: slot type for defineComponent --- .../__snapshots__/compileScript.spec.ts.snap | 45 +++++++++++++++++++ .../__tests__/compileScript.spec.ts | 34 ++++++++++++++ packages/compiler-sfc/src/compileScript.ts | 2 +- packages/dts-test/defineComponent.test-d.tsx | 32 ++++++++++++- packages/dts-test/setupHelpers.test-d.ts | 17 ++++++- .../runtime-core/src/apiDefineComponent.ts | 29 +++++++++--- packages/runtime-core/src/apiSetupHelpers.ts | 10 ++--- packages/runtime-core/src/component.ts | 24 +++++++--- packages/runtime-core/src/componentOptions.ts | 31 +++++++++++-- .../src/componentPublicInstance.ts | 6 ++- packages/runtime-core/src/componentSlots.ts | 18 +++++++- packages/runtime-core/src/index.ts | 2 +- packages/runtime-dom/src/apiCustomElement.ts | 9 +++- 13 files changed, 229 insertions(+), 30 deletions(-) diff --git a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap index f59a7407c25..c4cec4779bd 100644 --- a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap +++ b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap @@ -1785,6 +1785,51 @@ return { props, emit } })" `; +exports[`SFC compile + `) + assertCode(content) + expect(content).toMatch(`const slots = _useSlots()`) + }) + + test('defineSlots w/o return value', () => { + const { content } = compile(` + + `) + assertCode(content) + expect(content).not.toMatch(`_useSlots`) + }) + + test('defineSlots w/o generic params', () => { + const { content } = compile(` + + `) + assertCode(content) + expect(content).toMatch(`const slots = _useSlots()`) + }) + test('runtime Enum', () => { const { content, bindings } = compile( ` `) @@ -1601,7 +1601,7 @@ const emit = defineEmits(['a', 'b']) const { content } = compile(` `) From 8140bd648d3781bc1ae63e0a9d37956aa5301bcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Fri, 31 Mar 2023 02:28:56 +0800 Subject: [PATCH 10/14] feat: optional slots --- packages/dts-test/defineComponent.test-d.tsx | 26 ++++++++++++++----- packages/dts-test/setupHelpers.test-d.ts | 2 ++ packages/runtime-core/src/apiSetupHelpers.ts | 8 +++--- packages/runtime-core/src/component.ts | 13 +++++++--- .../runtime-core/src/componentRenderUtils.ts | 2 +- packages/runtime-core/src/componentSlots.ts | 13 +++++----- 6 files changed, 41 insertions(+), 23 deletions(-) diff --git a/packages/dts-test/defineComponent.test-d.tsx b/packages/dts-test/defineComponent.test-d.tsx index b9470cddbfa..add9c8a3fc0 100644 --- a/packages/dts-test/defineComponent.test-d.tsx +++ b/packages/dts-test/defineComponent.test-d.tsx @@ -10,7 +10,8 @@ import { SetupContext, h, SlotsType, - Slots + Slots, + VNode } from 'vue' import { describe, expectType, IsUnion } from './utils' @@ -1409,26 +1410,37 @@ export default { } describe('slots', () => { - defineComponent({ + const comp1 = defineComponent({ slots: Object as SlotsType<{ default: { foo: string; bar: number } - item: { data: number } + optional?: { data: string } }>, setup(props, { slots }) { - expectType any)>( + expectType<(scope: { foo: string; bar: number }) => VNode[]>( slots.default ) - expectType any)>(slots.item) + expectType<((scope: { data: string }) => VNode[]) | undefined>( + slots.optional + ) + + slots.default({ foo: 'foo', bar: 1 }) + + // @ts-expect-error it's optional + slots.optional({ data: 'foo' }) + slots.optional?.({ data: 'foo' }) + + expectType(new comp1().$slots) } }) - defineComponent({ + const comp2 = defineComponent({ setup(props, { slots }) { // unknown slots expectType(slots) - expectType<((...args: any[]) => any) | undefined>(slots.default) + expectType<((...args: any[]) => VNode[]) | undefined>(slots.default) } }) + expectType(new comp2().$slots) }) import { diff --git a/packages/dts-test/setupHelpers.test-d.ts b/packages/dts-test/setupHelpers.test-d.ts index 36f0350822b..ab7ab874f91 100644 --- a/packages/dts-test/setupHelpers.test-d.ts +++ b/packages/dts-test/setupHelpers.test-d.ts @@ -184,8 +184,10 @@ describe('defineEmits w/ runtime declaration', () => { describe('defineSlots', () => { const slots = defineSlots<{ default: { foo: string; bar: number } + optional?: string }>() expectType<(scope: { foo: string; bar: number }) => VNode[]>(slots.default) + expectType VNode[])>(slots.optional) const slotsUntype = defineSlots() expectType(slotsUntype) diff --git a/packages/runtime-core/src/apiSetupHelpers.ts b/packages/runtime-core/src/apiSetupHelpers.ts index e302a47f69d..dab1b4b30e8 100644 --- a/packages/runtime-core/src/apiSetupHelpers.ts +++ b/packages/runtime-core/src/apiSetupHelpers.ts @@ -25,7 +25,7 @@ import { ExtractPropTypes } from './componentProps' import { warn } from './warning' -import { Slot } from './componentSlots' +import { SlotsType, TypedSlots } from './componentSlots' // dev only const warnRuntimeUsage = (method: string) => @@ -190,11 +190,9 @@ export function defineOptions< } export function defineSlots< - T extends Record = Record + S extends Record = Record >(): // @ts-expect-error -Readonly<{ - [K in keyof T]: Slot<[T[K]]> -}> { +TypedSlots> { if (__DEV__) { warnRuntimeUsage(`defineSlots`) } diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index 1e5e973e81a..941231b393d 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -30,6 +30,7 @@ import { import { initSlots, InternalSlots, + Slots, SlotsType, TypedSlots } from './componentSlots' @@ -62,7 +63,8 @@ import { isPromise, ShapeFlags, extend, - getGlobalThis + getGlobalThis, + IfAny } from '@vue/shared' import { SuspenseBoundary } from './components/Suspense' import { CompilerOptions } from '@vue/compiler-core' @@ -125,13 +127,16 @@ export interface ComponentInternalOptions { export interface FunctionalComponent< P = {}, E extends EmitsOptions = {}, - S extends Record = Record + S extends Record = any > extends ComponentInternalOptions { // use of any here is intentional so it can be a valid JSX Element constructor - (props: P, ctx: Omit>, 'expose'>): any + ( + props: P, + ctx: Omit>>, 'expose'> + ): any props?: ComponentPropsOptions

emits?: E | (keyof E)[] - slots?: SlotsType + slots?: IfAny> inheritAttrs?: boolean displayName?: string compatConfig?: CompatConfig diff --git a/packages/runtime-core/src/componentRenderUtils.ts b/packages/runtime-core/src/componentRenderUtils.ts index d9de968a074..194ac30fa2a 100644 --- a/packages/runtime-core/src/componentRenderUtils.ts +++ b/packages/runtime-core/src/componentRenderUtils.ts @@ -102,7 +102,7 @@ export function renderComponentRoot( markAttrsAccessed() return attrs }, - slots, + slots: slots, emit } : { attrs, slots, emit } diff --git a/packages/runtime-core/src/componentSlots.ts b/packages/runtime-core/src/componentSlots.ts index aa0c8b21e3c..1a0db39cadb 100644 --- a/packages/runtime-core/src/componentSlots.ts +++ b/packages/runtime-core/src/componentSlots.ts @@ -14,7 +14,8 @@ import { extend, def, SlotFlags, - Prettify + Prettify, + IfAny } from '@vue/shared' import { warn } from './warning' import { isKeepAlive } from './components/KeepAlive' @@ -23,8 +24,8 @@ import { isHmrUpdating } from './hmr' import { DeprecationTypes, isCompatEnabled } from './compat/compatConfig' import { toRaw } from '@vue/reactivity' -export type Slot = ( - ...args: T extends [any] ? any[] : T +export type Slot = ( + ...args: IfAny ) => VNode[] export type InternalSlots = { @@ -42,9 +43,9 @@ export type TypedSlots = [keyof S] extends [never] ? Slots : Readonly< Prettify<{ - [K in keyof NonNullable]: - | Slot<[NonNullable[K]]> - | undefined + [K in keyof NonNullable]: Slot< + NonNullable[K]> + > }> > From e8a821e1f72e14e7e8f8942d136fd06157ccbaab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Fri, 31 Mar 2023 02:45:03 +0800 Subject: [PATCH 11/14] feat: undefined slot scope --- packages/dts-test/defineComponent.test-d.tsx | 29 ++++++++++++++++++++ packages/runtime-core/src/componentSlots.ts | 4 +-- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/packages/dts-test/defineComponent.test-d.tsx b/packages/dts-test/defineComponent.test-d.tsx index add9c8a3fc0..32912dd9a2a 100644 --- a/packages/dts-test/defineComponent.test-d.tsx +++ b/packages/dts-test/defineComponent.test-d.tsx @@ -1414,6 +1414,8 @@ describe('slots', () => { slots: Object as SlotsType<{ default: { foo: string; bar: number } optional?: { data: string } + undefinedScope: undefined | { data: string } + optionalUndefinedScope?: undefined | { data: string } }>, setup(props, { slots }) { expectType<(scope: { foo: string; bar: number }) => VNode[]>( @@ -1429,6 +1431,33 @@ describe('slots', () => { slots.optional({ data: 'foo' }) slots.optional?.({ data: 'foo' }) + expectType<{ + (): VNode[] + (scope: undefined | { data: string }): VNode[] + }>(slots.undefinedScope) + + expectType< + | { (): VNode[]; (scope: undefined | { data: string }): VNode[] } + | undefined + >(slots.optionalUndefinedScope) + + slots.default({ foo: 'foo', bar: 1 }) + // @ts-expect-error it's optional + slots.optional({ data: 'foo' }) + slots.optional?.({ data: 'foo' }) + slots.undefinedScope() + slots.undefinedScope(undefined) + // @ts-expect-error + slots.undefinedScope('foo') + + slots.optionalUndefinedScope?.() + slots.optionalUndefinedScope?.(undefined) + slots.optionalUndefinedScope?.({ data: 'foo' }) + // @ts-expect-error + slots.optionalUndefinedScope() + // @ts-expect-error + slots.optionalUndefinedScope?.('foo') + expectType(new comp1().$slots) } }) diff --git a/packages/runtime-core/src/componentSlots.ts b/packages/runtime-core/src/componentSlots.ts index 1a0db39cadb..e48fae98ee7 100644 --- a/packages/runtime-core/src/componentSlots.ts +++ b/packages/runtime-core/src/componentSlots.ts @@ -25,7 +25,7 @@ import { DeprecationTypes, isCompatEnabled } from './compat/compatConfig' import { toRaw } from '@vue/reactivity' export type Slot = ( - ...args: IfAny + ...args: IfAny ) => VNode[] export type InternalSlots = { @@ -44,7 +44,7 @@ export type TypedSlots = [keyof S] extends [never] : Readonly< Prettify<{ [K in keyof NonNullable]: Slot< - NonNullable[K]> + NonNullable[K] > }> > From e7742dac919b36f8e654f42bf97d444c25cb46a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Fri, 31 Mar 2023 02:51:38 +0800 Subject: [PATCH 12/14] test: improve --- .../__snapshots__/compileScript.spec.ts.snap | 18 ++--- .../__tests__/compileScript.spec.ts | 65 ++++++++++--------- 2 files changed, 44 insertions(+), 39 deletions(-) diff --git a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap index c4cec4779bd..ee00977c131 100644 --- a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap +++ b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap @@ -1785,45 +1785,45 @@ return { props, emit } })" `; -exports[`SFC compile - `) - assertCode(content) - expect(content).toMatch(`const slots = _useSlots()`) - }) + describe('defineSlots()', () => { + test('basic usage', () => { + const { content } = compile(` + + `) + assertCode(content) + expect(content).toMatch(`const slots = _useSlots()`) + expect(content).not.toMatch('defineSlots') + }) - test('defineSlots w/o return value', () => { - const { content } = compile(` - - `) - assertCode(content) - expect(content).not.toMatch(`_useSlots`) - }) + test('w/o return value', () => { + const { content } = compile(` + + `) + assertCode(content) + expect(content).not.toMatch('defineSlots') + expect(content).not.toMatch(`_useSlots`) + }) - test('defineSlots w/o generic params', () => { - const { content } = compile(` - - `) - assertCode(content) - expect(content).toMatch(`const slots = _useSlots()`) + test('w/o generic params', () => { + const { content } = compile(` + + `) + assertCode(content) + expect(content).toMatch(`const slots = _useSlots()`) + expect(content).not.toMatch('defineSlots') + }) }) test('runtime Enum', () => { From b6d421c7e8353f0621c2a35f06c83b3222b609db Mon Sep 17 00:00:00 2001 From: Evan You Date: Mon, 3 Apr 2023 15:21:08 +0800 Subject: [PATCH 13/14] test: improve functional slots test case --- .../dts-test/functionalComponent.test-d.tsx | 29 ++++++++++++++----- .../runtime-core/src/componentRenderUtils.ts | 2 +- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/packages/dts-test/functionalComponent.test-d.tsx b/packages/dts-test/functionalComponent.test-d.tsx index 37e2c9c9f91..827e8d63fe7 100644 --- a/packages/dts-test/functionalComponent.test-d.tsx +++ b/packages/dts-test/functionalComponent.test-d.tsx @@ -69,13 +69,28 @@ const Qux: FunctionalComponent<{}, ['foo', 'bar']> = (props, { emit }) => { expectType(Qux) -const Quux: FunctionalComponent<{}, {}, { default: { foo: number } }> = ( - props, - { emit, slots } -) => { - expectType<{ default: undefined | ((scope: { foo: number }) => VNode[]) }>( - slots - ) +const Quux: FunctionalComponent< + {}, + {}, + { + default: { foo: number } + optional?: { foo: number } + } +> = (props, { emit, slots }) => { + expectType<{ + default: (scope: { foo: number }) => VNode[] + optional?: (scope: { foo: number }) => VNode[] + }>(slots) + + slots.default({ foo: 123 }) + // @ts-expect-error + slots.default({ foo: 'fesf' }) + + slots.optional?.({ foo: 123 }) + // @ts-expect-error + slots.optional?.({ foo: 'fesf' }) + // @ts-expect-error + slots.optional({ foo: 123 }) } expectType(Quux) ; diff --git a/packages/runtime-core/src/componentRenderUtils.ts b/packages/runtime-core/src/componentRenderUtils.ts index 194ac30fa2a..d9de968a074 100644 --- a/packages/runtime-core/src/componentRenderUtils.ts +++ b/packages/runtime-core/src/componentRenderUtils.ts @@ -102,7 +102,7 @@ export function renderComponentRoot( markAttrsAccessed() return attrs }, - slots: slots, + slots, emit } : { attrs, slots, emit } From 31fa03668b9589141f5e4e1c995558bff121574d Mon Sep 17 00:00:00 2001 From: Evan You Date: Mon, 3 Apr 2023 16:43:02 +0800 Subject: [PATCH 14/14] feat: support using full function types in defineSlots --- packages/dts-test/setupHelpers.test-d.ts | 9 +++++++++ packages/runtime-core/src/componentSlots.ts | 11 +++++++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/dts-test/setupHelpers.test-d.ts b/packages/dts-test/setupHelpers.test-d.ts index ab7ab874f91..13d80947c83 100644 --- a/packages/dts-test/setupHelpers.test-d.ts +++ b/packages/dts-test/setupHelpers.test-d.ts @@ -182,6 +182,7 @@ describe('defineEmits w/ runtime declaration', () => { }) describe('defineSlots', () => { + // short syntax const slots = defineSlots<{ default: { foo: string; bar: number } optional?: string @@ -189,6 +190,14 @@ describe('defineSlots', () => { expectType<(scope: { foo: string; bar: number }) => VNode[]>(slots.default) expectType VNode[])>(slots.optional) + // literal fn syntax (allow for specifying return type) + const fnSlots = defineSlots<{ + default(props: { foo: string; bar: number }): any + optional?(props: string): any + }>() + expectType<(scope: { foo: string; bar: number }) => VNode[]>(fnSlots.default) + expectType VNode[])>(fnSlots.optional) + const slotsUntype = defineSlots() expectType(slotsUntype) }) diff --git a/packages/runtime-core/src/componentSlots.ts b/packages/runtime-core/src/componentSlots.ts index e48fae98ee7..81988599981 100644 --- a/packages/runtime-core/src/componentSlots.ts +++ b/packages/runtime-core/src/componentSlots.ts @@ -39,13 +39,16 @@ export type SlotsType = Record> = { [SlotSymbol]?: T } -export type TypedSlots = [keyof S] extends [never] +export type TypedSlots< + S extends SlotsType, + T = NonNullable +> = [keyof S] extends [never] ? Slots : Readonly< Prettify<{ - [K in keyof NonNullable]: Slot< - NonNullable[K] - > + [K in keyof T]: NonNullable extends (...args: any[]) => any + ? T[K] + : Slot }> >