From 32ae9c6b6ecb0fee65d49a4df6153a03c4114d06 Mon Sep 17 00:00:00 2001 From: zhiyuanzmj <260480378@qq.com> Date: Mon, 13 Apr 2026 00:31:19 +0800 Subject: [PATCH 1/6] feat(types): support JSX children typing for defineComponent Add ResolveComponentProps and JSXElementChildrenAttribute types. Expose ElementChildrenAttribute in JSX runtime and global JSX types. Wire the new props resolution into defineComponent and add dts tests. --- .../dts-test/defineComponent.test-d.tsx | 23 ++++++++++- packages-private/dts-test/utils.d.ts | 6 +++ .../runtime-core/src/apiDefineComponent.ts | 12 +++--- packages/runtime-core/src/componentProps.ts | 38 ++++++++++++++++++- packages/runtime-core/src/index.ts | 2 + packages/vue/jsx-runtime/index.d.ts | 8 +++- packages/vue/jsx.d.ts | 8 +++- 7 files changed, 85 insertions(+), 12 deletions(-) diff --git a/packages-private/dts-test/defineComponent.test-d.tsx b/packages-private/dts-test/defineComponent.test-d.tsx index 6188b102b31..1a5d55f2189 100644 --- a/packages-private/dts-test/defineComponent.test-d.tsx +++ b/packages-private/dts-test/defineComponent.test-d.tsx @@ -1565,7 +1565,7 @@ export default { } describe('slots', () => { - const comp1 = defineComponent({ + const Comp1 = defineComponent({ slots: Object as SlotsType<{ default: { foo: string; bar: number } optional?: { data: string } @@ -1613,9 +1613,28 @@ describe('slots', () => { // @ts-expect-error slots.optionalUndefinedScope?.('foo') - expectType(new comp1().$slots) + expectType(new Comp1().$slots) }, }) + ; + {({ bar, foo }) => [ + <> + {bar} + {foo} + , + ]} + + ; + {{ + default: ({ bar, foo }) => [ + <> + {bar} + {foo} + , + ], + undefinedScope: () => [], + }} + const comp2 = defineComponent({ setup(props, { slots }) { diff --git a/packages-private/dts-test/utils.d.ts b/packages-private/dts-test/utils.d.ts index c478b30cb6f..c70a3bd5f00 100644 --- a/packages-private/dts-test/utils.d.ts +++ b/packages-private/dts-test/utils.d.ts @@ -4,6 +4,12 @@ // register global JSX import 'vue/jsx' +declare module 'vue' { + interface JSXElementChildrenAttribute { + 'v-slots': {} + } +} + export function describe(_name: string, _fn: () => void): void export function test(_name: string, _fn: () => any): void diff --git a/packages/runtime-core/src/apiDefineComponent.ts b/packages/runtime-core/src/apiDefineComponent.ts index 4865c3b4ea4..7742379efda 100644 --- a/packages/runtime-core/src/apiDefineComponent.ts +++ b/packages/runtime-core/src/apiDefineComponent.ts @@ -21,6 +21,7 @@ import type { ComponentPropsOptions, ExtractDefaultPropTypes, ExtractPropTypes, + ResolveComponentProps, } from './componentProps' import type { EmitsOptions, @@ -116,7 +117,7 @@ export type DefineSetupFnComponent< P extends Record, E extends EmitsOptions = {}, S extends SlotsType = SlotsType, - Props = P & EmitsToProps, + Props = ResolveComponentProps, PP = PublicProps, > = new ( props: Props & PP, @@ -136,9 +137,6 @@ export type DefineSetupFnComponent< S > -type ToResolvedProps = Readonly & - Readonly> - // defineComponent is a utility that is primarily used for type inference // when declaring components. Type inference is provided in the component // options (provided as the argument). The returned value has artificial types @@ -237,7 +235,7 @@ export function defineComponent< */ __typeEl?: TypeEl } & ComponentOptionsBase< - ToResolvedProps, + ResolveComponentProps, SetupBindings, Data, Computed, @@ -257,7 +255,7 @@ export function defineComponent< > & ThisType< CreateComponentPublicInstanceWithMixins< - ToResolvedProps, + ResolveComponentProps, SetupBindings, Data, Computed, @@ -286,7 +284,7 @@ export function defineComponent< ResolvedEmits, RuntimeEmitsKeys, PublicProps, - ToResolvedProps, + ResolveComponentProps, ExtractDefaultPropTypes, Slots, LocalComponents, diff --git a/packages/runtime-core/src/componentProps.ts b/packages/runtime-core/src/componentProps.ts index 775eb8b6728..1e9355613fa 100644 --- a/packages/runtime-core/src/componentProps.ts +++ b/packages/runtime-core/src/componentProps.ts @@ -32,13 +32,15 @@ import { type Data, setCurrentInstance, } from './component' -import { isEmitListener } from './componentEmits' +import { type EmitsOptions, isEmitListener } from './componentEmits' import type { AppContext } from './apiCreateApp' import { createPropsDefaultThis } from './compat/props' import { isCompatEnabled, softAssertCompatEnabled } from './compat/compatConfig' import { DeprecationTypes } from './compat/compatConfig' import { shouldSkipAttr } from './compat/attrsFallthrough' import { createInternalObject } from './internalObject' +import type { EmitsToProps, SlotsType, VNodeChild } from '@vue/runtime-core' +import type { UnwrapSlotsType } from './componentSlots' export type ComponentPropsOptions

= | ComponentObjectPropsOptions

@@ -190,6 +192,40 @@ type NormalizedProp = PropOptions & { export type NormalizedProps = Record export type NormalizedPropsOptions = [NormalizedProps, string[]] | [] +/** + * Defines which prop name is used for children (slots) type checking. + * + * This is not enabled by default. To opt in, add the following + * declaration in your vue-jsx project: + * + * ```ts + * declare module 'vue' { + * interface JSXElementChildrenAttribute { + * 'v-slots': {} + * } + * } + * ``` + * + * @see {@link https://www.typescriptlang.org/docs/handbook/jsx.html#children-type-checking} + */ +export interface JSXElementChildrenAttribute {} + +export type ResolveComponentProps< + Props, + Emits extends EmitsOptions, + RawSlots extends SlotsType | Record = Record, + Element = VNodeChild, + Slots = RawSlots extends SlotsType ? UnwrapSlotsType : RawSlots, +> = Readonly & + Readonly> & + (keyof JSXElementChildrenAttribute extends infer Key extends string + ? { + [K in Key]?: + | ('default' extends keyof Slots ? Slots['default'] | Slots : Slots) + | NoInfer + } + : {}) + export function initProps( instance: ComponentInternalInstance, rawProps: Data | null, diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index 792a28b2582..6e679b957fd 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -322,6 +322,8 @@ export type { ExtractPropTypes, ExtractPublicPropTypes, ExtractDefaultPropTypes, + JSXElementChildrenAttribute, + ResolveComponentProps, } from './componentProps' export type { Directive, diff --git a/packages/vue/jsx-runtime/index.d.ts b/packages/vue/jsx-runtime/index.d.ts index 28071b75afe..08a54c6d717 100644 --- a/packages/vue/jsx-runtime/index.d.ts +++ b/packages/vue/jsx-runtime/index.d.ts @@ -1,5 +1,10 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ -import type { NativeElements, ReservedProps, VNode } from '@vue/runtime-dom' +import type { + JSXElementChildrenAttribute, + NativeElements, + ReservedProps, + VNode, +} from '@vue/runtime-dom' /** * JSX namespace for usage with @jsxImportsSource directive @@ -16,6 +21,7 @@ export namespace JSX { export interface ElementAttributesProperty { $props: {} } + export interface ElementChildrenAttribute extends JSXElementChildrenAttribute {} export interface IntrinsicElements extends NativeElements { // allow arbitrary elements // @ts-ignore suppress ts:2374 = Duplicate string index signature. diff --git a/packages/vue/jsx.d.ts b/packages/vue/jsx.d.ts index cfea000826b..402120bc97e 100644 --- a/packages/vue/jsx.d.ts +++ b/packages/vue/jsx.d.ts @@ -1,7 +1,12 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ // global JSX namespace registration // somehow we have to copy=pase the jsx-runtime types here to make TypeScript happy -import type { NativeElements, ReservedProps, VNode } from '@vue/runtime-dom' +import type { + JSXElementChildrenAttribute, + NativeElements, + ReservedProps, + VNode, +} from '@vue/runtime-dom' declare global { namespace JSX { @@ -12,6 +17,7 @@ declare global { export interface ElementAttributesProperty { $props: {} } + export interface ElementChildrenAttribute extends JSXElementChildrenAttribute {} export interface IntrinsicElements extends NativeElements { // allow arbitrary elements // @ts-ignore suppress ts:2374 = Duplicate string index signature. From 281b9843b650e7ca58d58c02892ffe816af7a716 Mon Sep 17 00:00:00 2001 From: zhiyuanzmj <260480378@qq.com> Date: Mon, 13 Apr 2026 01:10:13 +0800 Subject: [PATCH 2/6] chore: update --- .../dts-test/defineComponent.test-d.tsx | 5 +++-- packages/runtime-core/src/componentProps.ts | 16 +++++++++------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/packages-private/dts-test/defineComponent.test-d.tsx b/packages-private/dts-test/defineComponent.test-d.tsx index 1a5d55f2189..fee684d2406 100644 --- a/packages-private/dts-test/defineComponent.test-d.tsx +++ b/packages-private/dts-test/defineComponent.test-d.tsx @@ -1636,14 +1636,15 @@ describe('slots', () => { }} - const comp2 = defineComponent({ + const Comp2 = defineComponent({ setup(props, { slots }) { // unknown slots expectType(slots) expectType<((...args: any[]) => VNode[]) | undefined>(slots.default) }, }) - expectType(new comp2().$slots) + expectType(new Comp2().$slots) + expectType<{}>(new Comp2().$props) }) // #5885 diff --git a/packages/runtime-core/src/componentProps.ts b/packages/runtime-core/src/componentProps.ts index 1e9355613fa..cdd44efb8e5 100644 --- a/packages/runtime-core/src/componentProps.ts +++ b/packages/runtime-core/src/componentProps.ts @@ -218,13 +218,15 @@ export type ResolveComponentProps< Slots = RawSlots extends SlotsType ? UnwrapSlotsType : RawSlots, > = Readonly & Readonly> & - (keyof JSXElementChildrenAttribute extends infer Key extends string - ? { - [K in Key]?: - | ('default' extends keyof Slots ? Slots['default'] | Slots : Slots) - | NoInfer - } - : {}) + (string extends keyof Slots + ? {} + : keyof JSXElementChildrenAttribute extends infer Key extends string + ? { + [K in Key]?: + | ('default' extends keyof Slots ? Slots['default'] | Slots : Slots) + | NoInfer + } + : {}) export function initProps( instance: ComponentInternalInstance, From ba7a95afc4872e3b5fe08060247600d314ba97a4 Mon Sep 17 00:00:00 2001 From: zhiyuanzmj <260480378@qq.com> Date: Mon, 13 Apr 2026 01:14:59 +0800 Subject: [PATCH 3/6] chore: update --- packages/runtime-core/src/componentProps.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/runtime-core/src/componentProps.ts b/packages/runtime-core/src/componentProps.ts index cdd44efb8e5..67c23eca84c 100644 --- a/packages/runtime-core/src/componentProps.ts +++ b/packages/runtime-core/src/componentProps.ts @@ -195,7 +195,7 @@ export type NormalizedPropsOptions = [NormalizedProps, string[]] | [] /** * Defines which prop name is used for children (slots) type checking. * - * This is not enabled by default. To opt in, add the following + * This is not set by default. To enable it, add the following * declaration in your vue-jsx project: * * ```ts From 2d24d1d188b8084920c582b938d784bbdd57d6f7 Mon Sep 17 00:00:00 2001 From: zhiyuanzmj <260480378@qq.com> Date: Mon, 13 Apr 2026 01:16:45 +0800 Subject: [PATCH 4/6] chore: update --- packages/runtime-core/src/componentProps.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/runtime-core/src/componentProps.ts b/packages/runtime-core/src/componentProps.ts index 67c23eca84c..2ef4da28ae3 100644 --- a/packages/runtime-core/src/componentProps.ts +++ b/packages/runtime-core/src/componentProps.ts @@ -193,7 +193,7 @@ export type NormalizedProps = Record export type NormalizedPropsOptions = [NormalizedProps, string[]] | [] /** - * Defines which prop name is used for children (slots) type checking. + * Defines which prop name is used for JSX children (slots) type checking. * * This is not set by default. To enable it, add the following * declaration in your vue-jsx project: From b6825a671815cd84af4755f83dedc7c5daa5797e Mon Sep 17 00:00:00 2001 From: zhiyuanzmj <260480378@qq.com> Date: Mon, 13 Apr 2026 01:29:42 +0800 Subject: [PATCH 5/6] chore: update --- packages/runtime-core/src/componentProps.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/runtime-core/src/componentProps.ts b/packages/runtime-core/src/componentProps.ts index 2ef4da28ae3..300648c3090 100644 --- a/packages/runtime-core/src/componentProps.ts +++ b/packages/runtime-core/src/componentProps.ts @@ -32,15 +32,19 @@ import { type Data, setCurrentInstance, } from './component' -import { type EmitsOptions, isEmitListener } from './componentEmits' +import { + type EmitsOptions, + type EmitsToProps, + isEmitListener, +} from './componentEmits' import type { AppContext } from './apiCreateApp' import { createPropsDefaultThis } from './compat/props' import { isCompatEnabled, softAssertCompatEnabled } from './compat/compatConfig' import { DeprecationTypes } from './compat/compatConfig' import { shouldSkipAttr } from './compat/attrsFallthrough' import { createInternalObject } from './internalObject' -import type { EmitsToProps, SlotsType, VNodeChild } from '@vue/runtime-core' -import type { UnwrapSlotsType } from './componentSlots' +import type { SlotsType, UnwrapSlotsType } from './componentSlots' +import type { VNodeChild } from './vnode' export type ComponentPropsOptions

= | ComponentObjectPropsOptions

From 005c5dd0ff9112454398b7869ef901b5079cf6bb Mon Sep 17 00:00:00 2001 From: zhiyuanzmj <260480378@qq.com> Date: Tue, 14 Apr 2026 08:44:39 +0800 Subject: [PATCH 6/6] chore: update --- .../runtime-core/src/apiDefineComponent.ts | 15 ++++++---- packages/runtime-core/src/componentProps.ts | 30 +++++++------------ packages/runtime-core/src/index.ts | 2 +- 3 files changed, 21 insertions(+), 26 deletions(-) diff --git a/packages/runtime-core/src/apiDefineComponent.ts b/packages/runtime-core/src/apiDefineComponent.ts index 7742379efda..90b5a662daf 100644 --- a/packages/runtime-core/src/apiDefineComponent.ts +++ b/packages/runtime-core/src/apiDefineComponent.ts @@ -21,7 +21,7 @@ import type { ComponentPropsOptions, ExtractDefaultPropTypes, ExtractPropTypes, - ResolveComponentProps, + SlotsToProps, } from './componentProps' import type { EmitsOptions, @@ -72,7 +72,7 @@ export type DefineComponent< TypeEl extends Element = any, > = ComponentPublicInstanceConstructor< CreateComponentPublicInstanceWithMixins< - Props, + Props & SlotsToProps, RawBindings, D, C, @@ -117,7 +117,7 @@ export type DefineSetupFnComponent< P extends Record, E extends EmitsOptions = {}, S extends SlotsType = SlotsType, - Props = ResolveComponentProps, + Props = P & EmitsToProps & SlotsToProps, PP = PublicProps, > = new ( props: Props & PP, @@ -137,6 +137,9 @@ export type DefineSetupFnComponent< S > +type ToResolvedProps = Readonly & + Readonly> + // defineComponent is a utility that is primarily used for type inference // when declaring components. Type inference is provided in the component // options (provided as the argument). The returned value has artificial types @@ -235,7 +238,7 @@ export function defineComponent< */ __typeEl?: TypeEl } & ComponentOptionsBase< - ResolveComponentProps, + ToResolvedProps, SetupBindings, Data, Computed, @@ -255,7 +258,7 @@ export function defineComponent< > & ThisType< CreateComponentPublicInstanceWithMixins< - ResolveComponentProps, + ToResolvedProps, SetupBindings, Data, Computed, @@ -284,7 +287,7 @@ export function defineComponent< ResolvedEmits, RuntimeEmitsKeys, PublicProps, - ResolveComponentProps, + ToResolvedProps, ExtractDefaultPropTypes, Slots, LocalComponents, diff --git a/packages/runtime-core/src/componentProps.ts b/packages/runtime-core/src/componentProps.ts index 300648c3090..5150c4d4c72 100644 --- a/packages/runtime-core/src/componentProps.ts +++ b/packages/runtime-core/src/componentProps.ts @@ -32,11 +32,7 @@ import { type Data, setCurrentInstance, } from './component' -import { - type EmitsOptions, - type EmitsToProps, - isEmitListener, -} from './componentEmits' +import { isEmitListener } from './componentEmits' import type { AppContext } from './apiCreateApp' import { createPropsDefaultThis } from './compat/props' import { isCompatEnabled, softAssertCompatEnabled } from './compat/compatConfig' @@ -214,23 +210,19 @@ export type NormalizedPropsOptions = [NormalizedProps, string[]] | [] */ export interface JSXElementChildrenAttribute {} -export type ResolveComponentProps< - Props, - Emits extends EmitsOptions, +export type SlotsToProps< RawSlots extends SlotsType | Record = Record, Element = VNodeChild, Slots = RawSlots extends SlotsType ? UnwrapSlotsType : RawSlots, -> = Readonly & - Readonly> & - (string extends keyof Slots - ? {} - : keyof JSXElementChildrenAttribute extends infer Key extends string - ? { - [K in Key]?: - | ('default' extends keyof Slots ? Slots['default'] | Slots : Slots) - | NoInfer - } - : {}) +> = string extends keyof Slots + ? {} + : keyof JSXElementChildrenAttribute extends infer Key extends string + ? { + [K in Key]?: + | ('default' extends keyof Slots ? Slots['default'] | Slots : Slots) + | NoInfer + } + : {} export function initProps( instance: ComponentInternalInstance, diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index 6e679b957fd..59fe7aa78ea 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -323,7 +323,7 @@ export type { ExtractPublicPropTypes, ExtractDefaultPropTypes, JSXElementChildrenAttribute, - ResolveComponentProps, + SlotsToProps, } from './componentProps' export type { Directive,