ComponentCustomProps is erased in distributed component types
Vue version
3.3.2
Link to minimal reproduction
https://github.com/vuetifyjs/vuetify/issues/16190
Steps to reproduce
Build a library with
DefineComponent<MyProps, {}, {}, {}, {}, {}, {}, {}, {}, VNodeProps & AllowedComponentProps & ComponentCustomProps>
What is expected?
The emitted type includes
$props: MyProps & vue.VNodeProps & vue.AllowedComponentProps & vue.ComponentCustomProps
Consumers of the library can use module augmentation to set ComponentCustomProps
What is actually happening?
Prettify
added in 4c9bfd2b999ce472f7481aae4f9dc5bb9f76628e flattens it to
$props: { foo?: string, bar?: string }
Consumers cannot add anything with ComponentCustomProps
System Info
No response
Any additional comments?
https://github.com/vuetifyjs/vuetify/issues/16190#issuecomment-1554611843
I was creating an issue myself, but you were faster. Thanks.
https://stackblitz.com/edit/vitejs-vite-uq3thn?file=src/App.vue
❯ npm run typecheck
$ vue-tsc --noEmit
src/App.vue:7:10 - error TS2345: Argument of type '{ abcd: string; label: string; }' is not assignable to parameter of type '{ symbol?: any; replace?: boolean | undefined; flat?: boolean | undefined; exact?: boolean | undefined; block?: boolean | undefined; active?: boolean | undefined; style?: StyleValue | undefined; ... 47 more ...; "v-slot:loader"?: false | ... 1 more ... | undefined; }'.
Object literal may only specify known properties, and 'abcd' does not exist in type '{ symbol?: any; replace?: boolean | undefined; flat?: boolean | undefined; exact?: boolean | undefined; block?: boolean | undefined; active?: boolean | undefined; style?: StyleValue | undefined; ... 47 more ...; "v-slot:loader"?: false | ... 1 more ... | undefined; }'.
7 <v-btn abcd="string" label="test" />
~~~~~~~~~~~~~
Found 1 error in src/App.vue:7
App.vue
<script lang="ts" setup>
import HelloWorld from './components/HelloWorld.vue';
</script>
<template>
<HelloWorld msg="Vite + Vue" abcd="string" />
<v-btn abcd="string" label="test" />
</template>
shim.d.ts
declare module 'vue' {
interface ComponentCustomProps {
abcd: string;
}
}
export {};
Can't reproduce this locally, even when copying most of defineComponent.tsx types and build them, they still get generated as expected with vue-tsc
and tsc
, I suspect the bad typing is caused by a misconfiguration on vuetify
instead of being caused by vue
here's my test main.ts
// Types
import type {
AllowedComponentProps,
ComponentCustomProps,
ComponentInjectOptions,
ComponentObjectPropsOptions,
ComponentOptions,
ComponentOptionsMixin,
ComponentOptionsWithObjectProps,
ComponentOptionsWithoutProps,
ComponentPropsOptions,
ComputedOptions,
DefineComponent,
EmitsOptions,
ExtractDefaultPropTypes,
ExtractPropTypes,
FunctionalComponent,
MethodOptions,
ObjectEmitsOptions,
SlotsType,
VNode,
VNodeChild,
VNodeProps,
} from "vue";
export declare function betterDefineComponent<P>(p: P): DefineComponent<P>;
// vuetify.d.ts
export type SlotsToProps<U extends RawSlots, T = MakeInternalSlots<U>> = {
$children?:
| VNodeChild
| (T extends { default: infer V } ? V : {})
| { [K in keyof T]?: T[K] };
"v-slots"?: { [K in keyof T]?: T[K] | false };
} & {
[K in keyof T as `v-slot:${K & string}`]?: T[K] | false;
};
type RawSlots = Record<string, unknown>;
type Slot<T> = [T] extends [never] ? () => VNodeChild : (arg: T) => VNodeChild;
type VueSlot<T> = [T] extends [never] ? () => VNode[] : (arg: T) => VNode[];
type MakeInternalSlots<T extends RawSlots> = {
[K in keyof T]: Slot<T[K]>;
};
type MakeSlots<T extends RawSlots> = {
[K in keyof T]: VueSlot<T[K]>;
};
export type GenericProps<Props, Slots extends Record<string, unknown>> = {
$props: Props & SlotsToProps<Slots>;
$slots: MakeSlots<Slots>;
};
type ToListeners<T extends string | number | symbol> = {
[K in T]: K extends `on${infer U}` ? Uncapitalize<U> : K;
}[T];
type EmitsToProps<T extends EmitsOptions> = T extends string[]
? {
[K in string & `on${Capitalize<T[number]>}`]?: (...args: any[]) => any;
}
: T extends ObjectEmitsOptions
? {
[K in string &
`on${Capitalize<string & keyof T>}`]?: K extends `on${infer C}`
? T[Uncapitalize<C>] extends null
? (...args: any[]) => any
: (
...args: T[Uncapitalize<C>] extends (...args: infer P) => any
? P
: never
) => any
: never;
}
: {};
type PublicProps = VNodeProps & AllowedComponentProps & ComponentCustomProps;
// Adds a filterProps method to the component options
export interface FilterPropsOptions<
PropsOptions extends Readonly<ComponentPropsOptions>,
Props = ExtractPropTypes<PropsOptions>
> {
filterProps<
T extends Partial<Props>,
U extends Exclude<keyof Props, Exclude<keyof Props, keyof T>>
>(
props: T
): [yes: Partial<Pick<T, U>>, no: Omit<T, U>];
}
type DefineComponentWithGenericProps<
T extends new (props: Record<string, any>, slots: RawSlots) => {
$props?: Record<string, any>;
}
> = <
PropsOptions extends Readonly<ComponentObjectPropsOptions>,
RawBindings,
D,
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = Record<string, any>,
EE extends string = string,
I extends ComponentInjectOptions = {},
II extends string = string,
// Slots extends RawSlots = ConstructorParameters<T> extends [any, infer SS extends RawSlots | undefined] ? Exclude<SS, undefined> : {},
Slots extends RawSlots = ConstructorParameters<T>[1],
S extends SlotsType = SlotsType<Partial<MakeSlots<Slots>>>,
III = InstanceType<T>,
P = III extends Record<"$props", any>
? Omit<PropsOptions, keyof III["$props"]>
: PropsOptions,
Base = DefineComponent<
P,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E extends any[]
? E
: III extends Record<"$props", any>
? Omit<E, ToListeners<keyof III["$props"]>>
: E,
EE,
PublicProps,
ExtractPropTypes<P> & ({} extends E ? {} : EmitsToProps<E>),
ExtractDefaultPropTypes<P>,
S
>
>(
options: ComponentOptionsWithObjectProps<
PropsOptions,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE,
I,
II,
S
>
) => Base & T & FilterPropsOptions<PropsOptions>;
type DefineComponentWithSlots<Slots extends RawSlots> = <
PropsOptions extends Readonly<ComponentPropsOptions>,
RawBindings,
D,
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = Record<string, any>,
EE extends string = string,
I extends ComponentInjectOptions = {},
II extends string = string,
S extends SlotsType = SlotsType<Partial<MakeSlots<Slots>>>
>(
options: ComponentOptionsWithObjectProps<
PropsOptions,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE,
I,
II,
S
>
) => DefineComponent<
ExtractPropTypes<PropsOptions> & SlotsToProps<Slots>,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE,
PublicProps,
ExtractPropTypes<PropsOptions> &
SlotsToProps<Slots> &
({} extends E ? {} : EmitsToProps<E>),
ExtractDefaultPropTypes<PropsOptions>,
S
> &
FilterPropsOptions<PropsOptions>;
// No argument - simple default slot
export function genericComponent(
exposeDefaults?: boolean
): DefineComponentWithSlots<{ default: never }>;
// Generic constructor argument - generic props and slots
export function genericComponent<
T extends new (props: Record<string, any>, slots: any) => {
$props?: Record<string, any>;
}
>(exposeDefaults?: boolean): DefineComponentWithGenericProps<T>;
// Slots argument - simple slots
export function genericComponent<Slots extends RawSlots>(
exposeDefaults?: boolean
): DefineComponentWithSlots<Slots>;
// Implementation
export function genericComponent(exposeDefaults = true) {
return (options: any) =>
((exposeDefaults ? defineComponent : _defineComponent) as any)(options);
}
export const Comp = genericComponent();
export type VBtnSlots = {
default: never;
prepend: never;
append: never;
loader: never;
};
export const VBtn = genericComponent<VBtnSlots>();
tsconfig - basically default vite
+ outDir
and declaration
{
"compilerOptions": {
"target": "ES2020",
"outDir": "dist",
"declaration": true,
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"]
}
pnpm tsc --emitDeclarationOnly
pnpm vue-tsc --emitDeclarationOnly
built file
import type { AllowedComponentProps, ComponentCustomProps, ComponentInjectOptions, ComponentObjectPropsOptions, ComponentOptionsMixin, ComponentOptionsWithObjectProps, ComponentPropsOptions, ComputedOptions, DefineComponent, EmitsOptions, ExtractDefaultPropTypes, ExtractPropTypes, MethodOptions, ObjectEmitsOptions, SlotsType, VNode, VNodeChild, VNodeProps } from "vue";
export declare function betterDefineComponent<P>(p: P): DefineComponent<P>;
export type SlotsToProps<U extends RawSlots, T = MakeInternalSlots<U>> = {
$children?: VNodeChild | (T extends {
default: infer V;
} ? V : {}) | {
[K in keyof T]?: T[K];
};
"v-slots"?: {
[K in keyof T]?: T[K] | false;
};
} & {
[K in keyof T as `v-slot:${K & string}`]?: T[K] | false;
};
type RawSlots = Record<string, unknown>;
type Slot<T> = [T] extends [never] ? () => VNodeChild : (arg: T) => VNodeChild;
type VueSlot<T> = [T] extends [never] ? () => VNode[] : (arg: T) => VNode[];
type MakeInternalSlots<T extends RawSlots> = {
[K in keyof T]: Slot<T[K]>;
};
type MakeSlots<T extends RawSlots> = {
[K in keyof T]: VueSlot<T[K]>;
};
export type GenericProps<Props, Slots extends Record<string, unknown>> = {
$props: Props & SlotsToProps<Slots>;
$slots: MakeSlots<Slots>;
};
type ToListeners<T extends string | number | symbol> = {
[K in T]: K extends `on${infer U}` ? Uncapitalize<U> : K;
}[T];
type EmitsToProps<T extends EmitsOptions> = T extends string[] ? {
[K in string & `on${Capitalize<T[number]>}`]?: (...args: any[]) => any;
} : T extends ObjectEmitsOptions ? {
[K in string & `on${Capitalize<string & keyof T>}`]?: K extends `on${infer C}` ? T[Uncapitalize<C>] extends null ? (...args: any[]) => any : (...args: T[Uncapitalize<C>] extends (...args: infer P) => any ? P : never) => any : never;
} : {};
type PublicProps = VNodeProps & AllowedComponentProps & ComponentCustomProps;
export interface FilterPropsOptions<PropsOptions extends Readonly<ComponentPropsOptions>, Props = ExtractPropTypes<PropsOptions>> {
filterProps<T extends Partial<Props>, U extends Exclude<keyof Props, Exclude<keyof Props, keyof T>>>(props: T): [yes: Partial<Pick<T, U>>, no: Omit<T, U>];
}
type DefineComponentWithGenericProps<T extends new (props: Record<string, any>, slots: RawSlots) => {
$props?: Record<string, any>;
}> = <PropsOptions extends Readonly<ComponentObjectPropsOptions>, RawBindings, D, C extends ComputedOptions = {}, M extends MethodOptions = {}, Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, Extends extends ComponentOptionsMixin = ComponentOptionsMixin, E extends EmitsOptions = Record<string, any>, EE extends string = string, I extends ComponentInjectOptions = {}, II extends string = string, Slots extends RawSlots = ConstructorParameters<T>[1], S extends SlotsType = SlotsType<Partial<MakeSlots<Slots>>>, III = InstanceType<T>, P = III extends Record<"$props", any> ? Omit<PropsOptions, keyof III["$props"]> : PropsOptions, Base = DefineComponent<P, RawBindings, D, C, M, Mixin, Extends, E extends any[] ? E : III extends Record<"$props", any> ? Omit<E, ToListeners<keyof III["$props"]>> : E, EE, PublicProps, ExtractPropTypes<P> & ({} extends E ? {} : EmitsToProps<E>), ExtractDefaultPropTypes<P>, S>>(options: ComponentOptionsWithObjectProps<PropsOptions, RawBindings, D, C, M, Mixin, Extends, E, EE, I, II, S>) => Base & T & FilterPropsOptions<PropsOptions>;
type DefineComponentWithSlots<Slots extends RawSlots> = <PropsOptions extends Readonly<ComponentPropsOptions>, RawBindings, D, C extends ComputedOptions = {}, M extends MethodOptions = {}, Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, Extends extends ComponentOptionsMixin = ComponentOptionsMixin, E extends EmitsOptions = Record<string, any>, EE extends string = string, I extends ComponentInjectOptions = {}, II extends string = string, S extends SlotsType = SlotsType<Partial<MakeSlots<Slots>>>>(options: ComponentOptionsWithObjectProps<PropsOptions, RawBindings, D, C, M, Mixin, Extends, E, EE, I, II, S>) => DefineComponent<ExtractPropTypes<PropsOptions> & SlotsToProps<Slots>, RawBindings, D, C, M, Mixin, Extends, E, EE, PublicProps, ExtractPropTypes<PropsOptions> & SlotsToProps<Slots> & ({} extends E ? {} : EmitsToProps<E>), ExtractDefaultPropTypes<PropsOptions>, S> & FilterPropsOptions<PropsOptions>;
export declare function genericComponent(exposeDefaults?: boolean): DefineComponentWithSlots<{
default: never;
}>;
export declare function genericComponent<T extends new (props: Record<string, any>, slots: any) => {
$props?: Record<string, any>;
}>(exposeDefaults?: boolean): DefineComponentWithGenericProps<T>;
export declare function genericComponent<Slots extends RawSlots>(exposeDefaults?: boolean): DefineComponentWithSlots<Slots>;
export declare const Comp: DefineComponentWithSlots<{
default: never;
}>;
export type VBtnSlots = {
default: never;
prepend: never;
append: never;
loader: never;
};
export declare const VBtn: DefineComponentWithSlots<VBtnSlots>;
export {};
Minimal reproduction:
import type {
ComponentPropsOptions,
ComputedOptions,
MethodOptions,
ComponentOptionsMixin,
EmitsOptions,
SlotsType,
ComponentOptionsWithObjectProps,
DefineComponent,
ComponentInjectOptions,
} from 'vue'
// overload 4 from vue: object format with object props declaration
declare function defineComponent<
PropsOptions extends Readonly<ComponentPropsOptions>,
RawBindings,
D,
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = {},
EE extends string = string,
S extends SlotsType = {},
I extends ComponentInjectOptions = {},
II extends string = string
>(
options: ComponentOptionsWithObjectProps<
PropsOptions,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE,
I,
II,
S
>
): DefineComponent<
PropsOptions,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE
/* These are not exported by Vue. DefineComponent has them as defaults anyway so it shouldn't matter
PublicProps,
ResolveProps<PropsOptions, E>,
ExtractDefaultPropTypes<PropsOptions>,
S */
> // & {}
export const Component = defineComponent({
props: {
foo: String,
},
setup () {}
})
{
"include": ["src"],
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"jsx": "preserve",
"strict": true,
"declaration": true,
"outDir": "./dist",
"moduleResolution": "node"
}
}
Built file:
import type { ComponentOptionsMixin, DefineComponent } from 'vue';
export declare const Component: DefineComponent<{
foo: StringConstructor;
}, void, unknown, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {}, string>;
Nothing wrong there, but making the return type an intersection (add & {}
) causes typescript to flatten it from DefineComponent<...>
to new (...args: any[]): {...}
:
import type { ComponentOptionsMixin } from 'vue';
export declare const Component: {
new (...args: any[]): {
$: import("vue").ComponentInternalInstance;
$data: {};
$props: {
key?: string | number | symbol | undefined;
style?: unknown;
class?: unknown;
readonly foo?: string | undefined;
ref?: import("vue").VNodeRef | undefined;
ref_for?: boolean | undefined;
ref_key?: string | undefined;
onVnodeBeforeMount?: ((vnode: import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
[key: string]: any;
}>) => void) | ((vnode: import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
[key: string]: any;
}>) => void)[] | undefined;
onVnodeMounted?: ((vnode: import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
[key: string]: any;
}>) => void) | ((vnode: import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
[key: string]: any;
}>) => void)[] | undefined;
onVnodeBeforeUpdate?: ((vnode: import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
[key: string]: any;
}>, oldVNode: import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
[key: string]: any;
}>) => void) | ((vnode: import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
[key: string]: any;
}>, oldVNode: import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
[key: string]: any;
}>) => void)[] | undefined;
onVnodeUpdated?: ((vnode: import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
[key: string]: any;
}>, oldVNode: import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
[key: string]: any;
}>) => void) | ((vnode: import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
[key: string]: any;
}>, oldVNode: import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
[key: string]: any;
}>) => void)[] | undefined;
onVnodeBeforeUnmount?: ((vnode: import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
[key: string]: any;
}>) => void) | ((vnode: import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
[key: string]: any;
}>) => void)[] | undefined;
onVnodeUnmounted?: ((vnode: import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
[key: string]: any;
}>) => void) | ((vnode: import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
[key: string]: any;
}>) => void)[] | undefined;
};
$attrs: {
[x: string]: unknown;
};
$refs: {
[x: string]: unknown;
};
$slots: Readonly<{
[name: string]: import("vue").Slot<any> | undefined;
}>;
$root: import("vue").ComponentPublicInstance<{}, {}, {}, {}, {}, {}, {}, {}, false, import("vue").ComponentOptionsBase<any, any, any, any, any, any, any, any, any, {}, {}, string, {}>, {}, {}> | null;
$parent: import("vue").ComponentPublicInstance<{}, {}, {}, {}, {}, {}, {}, {}, false, import("vue").ComponentOptionsBase<any, any, any, any, any, any, any, any, any, {}, {}, string, {}>, {}, {}> | null;
$emit: (event: string, ...args: any[]) => void;
$el: any;
$options: import("vue").ComponentOptionsBase<Readonly<import("vue").ExtractPropTypes<{
foo: StringConstructor;
}>>, void, unknown, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {}, string, {}, {}, string, {}> & {
beforeCreate?: ((() => void) | (() => void)[]) | undefined;
created?: ((() => void) | (() => void)[]) | undefined;
beforeMount?: ((() => void) | (() => void)[]) | undefined;
mounted?: ((() => void) | (() => void)[]) | undefined;
beforeUpdate?: ((() => void) | (() => void)[]) | undefined;
updated?: ((() => void) | (() => void)[]) | undefined;
activated?: ((() => void) | (() => void)[]) | undefined;
deactivated?: ((() => void) | (() => void)[]) | undefined;
beforeDestroy?: ((() => void) | (() => void)[]) | undefined;
beforeUnmount?: ((() => void) | (() => void)[]) | undefined;
destroyed?: ((() => void) | (() => void)[]) | undefined;
unmounted?: ((() => void) | (() => void)[]) | undefined;
renderTracked?: (((e: import("vue").DebuggerEvent) => void) | ((e: import("vue").DebuggerEvent) => void)[]) | undefined;
renderTriggered?: (((e: import("vue").DebuggerEvent) => void) | ((e: import("vue").DebuggerEvent) => void)[]) | undefined;
errorCaptured?: (((err: unknown, instance: import("vue").ComponentPublicInstance<{}, {}, {}, {}, {}, {}, {}, {}, false, import("vue").ComponentOptionsBase<any, any, any, any, any, any, any, any, any, {}, {}, string, {}>, {}, {}> | null, info: string) => boolean | void) | ((err: unknown, instance: import("vue").ComponentPublicInstance<{}, {}, {}, {}, {}, {}, {}, {}, false, import("vue").ComponentOptionsBase<any, any, any, any, any, any, any, any, any, {}, {}, string, {}>, {}, {}> | null, info: string) => boolean | void)[]) | undefined;
};
$forceUpdate: () => void;
$nextTick: typeof import("vue").nextTick;
$watch<T extends string | ((...args: any) => any)>(source: T, cb: T extends (...args: any) => infer R ? (args_0: R, args_1: R) => any : (...args: any) => any, options?: import("vue").WatchOptions<boolean> | undefined): import("vue").WatchStopHandle;
} & Readonly<import("vue").ExtractPropTypes<{
foo: StringConstructor;
}>> & import("vue").ShallowUnwrapRef<{}> & {} & import("vue").ComponentCustomProperties & {};
__isFragment?: undefined;
__isTeleport?: undefined;
__isSuspense?: undefined;
} & import("vue").ComponentOptionsBase<Readonly<import("vue").ExtractPropTypes<{
foo: StringConstructor;
}>>, void, unknown, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {}, string, {}, {}, string, {}> & import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps;
Now PublicProps
is a static part of the library types so can't be modified by consumers. This was fine in Vue 3.2:
import type { ComponentOptionsMixin } from 'vue';
export declare const Component: {
new (...args: any[]): {
$: import("vue").ComponentInternalInstance;
$data: {};
$props: Partial<{}> & Omit<Readonly<import("vue").ExtractPropTypes<{
foo: StringConstructor;
}>> & import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, never>;
$attrs: {
[x: string]: unknown;
};
$refs: {
[x: string]: unknown;
};
$slots: Readonly<{
[name: string]: import("vue").Slot | undefined;
}>;
$root: import("vue").ComponentPublicInstance<{}, {}, {}, {}, {}, {}, {}, {}, false, import("vue").ComponentOptionsBase<any, any, any, any, any, any, any, any, any, {}, {}, string>, {}> | null;
$parent: import("vue").ComponentPublicInstance<{}, {}, {}, {}, {}, {}, {}, {}, false, import("vue").ComponentOptionsBase<any, any, any, any, any, any, any, any, any, {}, {}, string>, {}> | null;
$emit: (event: string, ...args: any[]) => void;
$el: any;
$options: import("vue").ComponentOptionsBase<Readonly<import("vue").ExtractPropTypes<{
foo: StringConstructor;
}>>, void, unknown, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {}, string, {}, {}, string> & {
beforeCreate?: ((() => void) | (() => void)[]) | undefined;
created?: ((() => void) | (() => void)[]) | undefined;
beforeMount?: ((() => void) | (() => void)[]) | undefined;
mounted?: ((() => void) | (() => void)[]) | undefined;
beforeUpdate?: ((() => void) | (() => void)[]) | undefined;
updated?: ((() => void) | (() => void)[]) | undefined;
activated?: ((() => void) | (() => void)[]) | undefined;
deactivated?: ((() => void) | (() => void)[]) | undefined;
beforeDestroy?: ((() => void) | (() => void)[]) | undefined;
beforeUnmount?: ((() => void) | (() => void)[]) | undefined;
destroyed?: ((() => void) | (() => void)[]) | undefined;
unmounted?: ((() => void) | (() => void)[]) | undefined;
renderTracked?: (((e: import("vue").DebuggerEvent) => void) | ((e: import("vue").DebuggerEvent) => void)[]) | undefined;
renderTriggered?: (((e: import("vue").DebuggerEvent) => void) | ((e: import("vue").DebuggerEvent) => void)[]) | undefined;
errorCaptured?: (((err: unknown, instance: import("vue").ComponentPublicInstance<{}, {}, {}, {}, {}, {}, {}, {}, false, import("vue").ComponentOptionsBase<any, any, any, any, any, any, any, any, any, {}, {}, string>, {}> | null, info: string) => boolean | void) | ((err: unknown, instance: import("vue").ComponentPublicInstance<{}, {}, {}, {}, {}, {}, {}, {}, false, import("vue").ComponentOptionsBase<any, any, any, any, any, any, any, any, any, {}, {}, string>, {}> | null, info: string) => boolean | void)[]) | undefined;
};
$forceUpdate: () => void;
$nextTick: typeof import("vue").nextTick;
$watch<T extends string | ((...args: any) => any)>(source: T, cb: T extends (...args: any) => infer R ? (args_0: R, args_1: R) => any : (...args: any) => any, options?: import("vue").WatchOptions<boolean> | undefined): import("vue").WatchStopHandle;
} & Readonly<import("vue").ExtractPropTypes<{
foo: StringConstructor;
}>> & import("vue").ShallowUnwrapRef<{}> & {} & import("vue").ComponentCustomProperties & {};
__isFragment?: undefined;
__isTeleport?: undefined;
__isSuspense?: undefined;
} & import("vue").ComponentOptionsBase<Readonly<import("vue").ExtractPropTypes<{
foo: StringConstructor;
}>>, void, unknown, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {}, string, {}, {}, string> & import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps;
Looks like we might be able to work around this by defining an intermediary type:
type ExtendDefineComponent<
Props = {},
RawBindings = {},
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = {},
EE extends string = string,
ExtraOptions = {}
> = DefineComponent<
Props,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE
> & ExtraOptions
I also got to the same point, with a simple helper
export type MergeComponent<T, M> = T & M;
// change return type
MergeComponent<
DefineComponent<
PropsOptions,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE
/* These are not exported by Vue. DefineComponent has them as defaults anyway so it shouldn't matter
PublicProps,
ResolveProps<PropsOptions, E>,
ExtractDefaultPropTypes<PropsOptions>,
S */
>,
{ test: string }
>;