Subscribe on changes!

ComponentCustomProps is erased in distributed component types

avatar
May 19th 2023

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

avatar
May 19th 2023

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 {};
avatar
Jun 14th 2023

You could try upgrading your typescript to the latest 5.1.3

avatar
Jun 14th 2023

@shengxinjing No difference with 5.1.3

avatar
Oct 13th 2023

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 {};
avatar
Oct 13th 2023

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;
avatar
Oct 13th 2023

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
avatar
Oct 13th 2023

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 }
>;
avatar
Oct 13th 2023

I couldn't get either of these to work in the actual project, it always emits the resolved new (...args: any[]): {...} type even with the most minimal possible component.