diff --git a/package.json b/package.json index ea8d44bb95f..bf91637abc0 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "semver": "^7.3.2", "serve": "^12.0.0", "ts-jest": "^26.2.0", - "typescript": "^4.2.2", + "typescript": "^4.4.2", "yorkie": "^2.0.0" } } diff --git a/packages/compiler-core/__tests__/transforms/vSlot.spec.ts b/packages/compiler-core/__tests__/transforms/vSlot.spec.ts index 4a7df1e8dfe..687c4d8b358 100644 --- a/packages/compiler-core/__tests__/transforms/vSlot.spec.ts +++ b/packages/compiler-core/__tests__/transforms/vSlot.spec.ts @@ -780,9 +780,7 @@ describe('compiler: transform component slots', () => { }) test('<slot w/ nested component>', () => { - const { slots } = parseWithSlots( - `<Comp><Comp><slot/></Comp></Comp>` - ) + const { slots } = parseWithSlots(`<Comp><Comp><slot/></Comp></Comp>`) expect(slots).toMatchObject(toMatch) }) }) diff --git a/packages/compiler-sfc/src/cssVars.ts b/packages/compiler-sfc/src/cssVars.ts index f51269f1269..3032e2fbf5d 100644 --- a/packages/compiler-sfc/src/cssVars.ts +++ b/packages/compiler-sfc/src/cssVars.ts @@ -12,7 +12,8 @@ import { PluginCreator } from 'postcss' import hash from 'hash-sum' export const CSS_VARS_HELPER = `useCssVars` -export const cssVarRE = /\bv-bind\(\s*(?:'([^']+)'|"([^"]+)"|([^'"][^)]*))\s*\)/g +export const cssVarRE = + /\bv-bind\(\s*(?:'([^']+)'|"([^"]+)"|([^'"][^)]*))\s*\)/g export function genCssVarsFromList( vars: string[], diff --git a/packages/runtime-core/src/apiDefineComponent.ts b/packages/runtime-core/src/apiDefineComponent.ts index f6793da5f30..854ea69832e 100644 --- a/packages/runtime-core/src/apiDefineComponent.ts +++ b/packages/runtime-core/src/apiDefineComponent.ts @@ -1,77 +1,70 @@ import { ComputedOptions, MethodOptions, - ComponentOptionsWithoutProps, - ComponentOptionsWithArrayProps, - ComponentOptionsWithObjectProps, ComponentOptionsMixin, RenderFunction, - ComponentOptionsBase + BettterComponentOptionsWithObjectProps, + BetterComponentOptions, + BettterComponentOptionsWithArrayProps, + BettterComponentOptionsWithoutProps } from './componentOptions' import { SetupContext, AllowedComponentProps, - ComponentCustomProps + ComponentCustomProps, + Component, + BetterComponent } from './component' import { ExtractPropTypes, - ComponentPropsOptions, - ExtractDefaultPropTypes + ExtractDefaultPropTypes, + ComponentObjectPropsOptions } from './componentProps' -import { EmitsOptions, EmitsToProps } from './componentEmits' +import { EmitsOptions } from './componentEmits' import { isFunction } from '@vue/shared' import { VNodeProps } from './vnode' -import { - CreateComponentPublicInstance, - ComponentPublicInstanceConstructor -} from './componentPublicInstance' +import { RenderComponent } from './componentPublicInstance' +import { Slots } from './componentSlots' +import { Directive } from './directives' export type PublicProps = VNodeProps & AllowedComponentProps & ComponentCustomProps +// Type Helper for defineComponent return export type DefineComponent< - PropsOrPropOptions = {}, + Props extends Record<string, unknown>, + Emits extends EmitsOptions = {}, + S = {}, + LC extends Record<string, Component> = {}, + LD extends Record<string, Directive> = {}, RawBindings = {}, D = {}, - C extends ComputedOptions = ComputedOptions, - M extends MethodOptions = MethodOptions, + C extends ComputedOptions = {}, + M extends MethodOptions = {}, + Exposed extends string = string, Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, Extends extends ComponentOptionsMixin = ComponentOptionsMixin, - E extends EmitsOptions = Record<string, any>, - EE extends string = string, - PP = PublicProps, - Props = Readonly<ExtractPropTypes<PropsOrPropOptions>> & EmitsToProps<E>, - Defaults = ExtractDefaultPropTypes<PropsOrPropOptions> -> = ComponentPublicInstanceConstructor< - CreateComponentPublicInstance< - Props, - RawBindings, - D, - C, - M, - Mixin, - Extends, - E, - PP & Props, - Defaults, - true - > & - Props + Defaults = {}, + Options = any +> = BetterComponent< + Props, + Emits, + S, + // TODO ADD Similar binding as the BetterCreateComponentPublicInstance + {}, + LC, + LD, + D, + RawBindings, + C, + M, + Exposed, + Mixin, + Extends, + Options > & - ComponentOptionsBase< - Props, - RawBindings, - D, - C, - M, - Mixin, - Extends, - E, - EE, - Defaults - > & - PP + RenderComponent<Props, Emits, S, {}, LC, LD, D, Options> // defineComponent is a utility that is primarily used for type inference // when declaring components. Type inference is provided in the component @@ -80,104 +73,249 @@ export type DefineComponent< // overload 1: direct setup function // (uses user defined props interface) -export function defineComponent<Props, RawBindings = object>( +export function defineComponent< + Props extends Record<string, unknown> = {}, + RawBindings = {}, + Emits extends EmitsOptions = {}, + S = {}, + Exposed extends string = string +>( setup: ( props: Readonly<Props>, - ctx: SetupContext + ctx: SetupContext<Emits, Slots<S>> ) => RawBindings | RenderFunction -): DefineComponent<Props, RawBindings> +): DefineComponent< + Props, + Emits, + S, + {}, + {}, + RawBindings, + {}, + {}, + {}, + Exposed, + {}, + {}, + {}, + BetterComponentOptions< + Props, + Emits, + S, + {}, + {}, + RawBindings, + {}, + {}, + {}, + string, + Exposed + > +> // overload 2: object format with no props // (uses user defined props interface) // return type is for Vetur and TSX support export function defineComponent< - Props = {}, + Emits extends EmitsOptions = {}, + S = {}, + LC extends Record<string, Component> = {}, + LD extends Record<string, Directive> = {}, RawBindings = {}, D = {}, C extends ComputedOptions = {}, M extends MethodOptions = {}, + EE extends string = string, + Exposed extends string = string, Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, Extends extends ComponentOptionsMixin = ComponentOptionsMixin, - E extends EmitsOptions = EmitsOptions, - EE extends string = string + Defaults = {} >( - options: ComponentOptionsWithoutProps< - Props, + options: BettterComponentOptionsWithoutProps< + Emits, + S, + LC, + LD, RawBindings, D, C, M, + EE, + Exposed, Mixin, Extends, - E, - EE + Defaults > -): DefineComponent<Props, RawBindings, D, C, M, Mixin, Extends, E, EE> - +): DefineComponent< + {}, + Emits, + S, + LC, + LD, + RawBindings, + D, + C, + M, + Exposed, + Mixin, + Extends, + Defaults, + BettterComponentOptionsWithoutProps< + Emits, + S, + LC, + LD, + RawBindings, + D, + C, + M, + EE, + Exposed, + Mixin, + Extends, + Defaults + > +> // overload 3: object format with array props declaration // props inferred as { [key in PropNames]?: any } // return type is for Vetur and TSX support export function defineComponent< PropNames extends string, - RawBindings, - D, + Emits extends EmitsOptions = {}, + S = {}, + LC extends Record<string, Component> = {}, + LD extends Record<string, Directive> = {}, + RawBindings = {}, + D = {}, C extends ComputedOptions = {}, M extends MethodOptions = {}, + EE extends string = string, + Exposed extends string = string, Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, Extends extends ComponentOptionsMixin = ComponentOptionsMixin, - E extends EmitsOptions = Record<string, any>, - EE extends string = string + Defaults = {}, + Props extends Record<string, unknown> = Readonly<{ [key in PropNames]?: any }> >( - options: ComponentOptionsWithArrayProps< - PropNames, + options: BettterComponentOptionsWithArrayProps< + Props, + Emits, + S, + LC, + LD, RawBindings, D, C, M, + EE, + Exposed, Mixin, Extends, - E, - EE + Defaults, + PropNames > ): DefineComponent< - Readonly<{ [key in PropNames]?: any }>, + Props, + Emits, + S, + LC, + LD, RawBindings, D, C, M, + Exposed, Mixin, Extends, - E, - EE + Defaults, + BettterComponentOptionsWithArrayProps< + Props, + Emits, + S, + LC, + LD, + RawBindings, + D, + C, + M, + EE, + Exposed, + Mixin, + Extends, + Defaults, + PropNames + > > // overload 4: object format with object props declaration // see `ExtractPropTypes` in ./componentProps.ts export function defineComponent< - // the Readonly constraint allows TS to treat the type of { required: true } - // as constant instead of boolean. - PropsOptions extends Readonly<ComponentPropsOptions>, - RawBindings, - D, + PropsOptions extends ComponentObjectPropsOptions = ComponentObjectPropsOptions, + Emits extends EmitsOptions = {}, + S = {}, + LC extends Record<string, Component> = {}, + LD extends Record<string, Directive> = {}, + RawBindings = {}, + D = {}, C extends ComputedOptions = {}, M extends MethodOptions = {}, + EE extends string = string, + Exposed extends string = string, Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, Extends extends ComponentOptionsMixin = ComponentOptionsMixin, - E extends EmitsOptions = Record<string, any>, - EE extends string = string + Defaults = ExtractDefaultPropTypes<PropsOptions>, + Props extends Record<string, unknown> = Readonly< + ExtractPropTypes<PropsOptions> + > >( - options: ComponentOptionsWithObjectProps< - PropsOptions, + options: BettterComponentOptionsWithObjectProps< + Props, + Emits, + S, + LC, + LD, + RawBindings, + D, + C, + M, + EE, + Exposed, + Mixin, + Extends, + Defaults, + PropsOptions + > +): DefineComponent< + Props, + Emits, + S, + LC, + LD, + RawBindings, + D, + C, + M, + Exposed, + Mixin, + Extends, + Defaults, + BettterComponentOptionsWithObjectProps< + Props, + Emits, + S, + LC, + LD, RawBindings, D, C, M, + EE, + Exposed, Mixin, Extends, - E, - EE + Defaults, + PropsOptions > -): DefineComponent<PropsOptions, RawBindings, D, C, M, Mixin, Extends, E, EE> +> // implementation, close to no-op export function defineComponent(options: unknown) { diff --git a/packages/runtime-core/src/compat/componentFunctional.ts b/packages/runtime-core/src/compat/componentFunctional.ts index 80af32a1dae..8e561250d01 100644 --- a/packages/runtime-core/src/compat/componentFunctional.ts +++ b/packages/runtime-core/src/compat/componentFunctional.ts @@ -13,7 +13,9 @@ const normalizedFunctionalComponentMap = new Map< FunctionalComponent >() -export const legacySlotProxyHandlers: ProxyHandler<InternalSlots> = { +export const legacySlotProxyHandlers: ProxyHandler< + InternalSlots<Record<string, null>> +> = { get(target, key: string) { const slot = target[key] return slot && slot() diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index 16b2333051d..952cf6221d6 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -23,7 +23,8 @@ import { ComponentPropsOptions, NormalizedPropsOptions, initProps, - normalizePropsOptions + normalizePropsOptions, + Prop } from './componentProps' import { Slots, initSlots, InternalSlots } from './componentSlots' import { warn } from './warning' @@ -32,7 +33,10 @@ import { AppContext, createAppContext, AppConfig } from './apiCreateApp' import { Directive, validateDirectiveName } from './directives' import { applyOptions, + BetterComponentOptions, + BetterComponentOptionsMixin, ComponentOptions, + ComponentOptionsMixin, ComputedOptions, MethodOptions } from './componentOptions' @@ -70,6 +74,40 @@ export type Data = Record<string, unknown> */ export interface ComponentCustomProps {} +/** + * For globally defined Directives + * Here is an example of adding a directive `VTooltip` as global directive: + * + * @example + * ```ts + * import VTooltip from 'v-tooltip' + * + * declare module '@vue/runtime-core' { + * interface GlobalDirectives { + * VTooltip + * } + * } + * ``` + */ +export interface GlobalDirectives extends Record<string, Directive> {} + +/** + * For globally defined Components + * Here is an example of adding a component `RouterView` as global component: + * + * @example + * ```ts + * import { RouterView } from 'vue-router' + * + * declare module '@vue/runtime-core' { + * interface GlobalComponents { + * RouterView + * } + * } + * ``` + */ +export interface GlobalComponents extends Record<string, Component> {} + /** * Default allowed non-declared props on component in TSX */ @@ -103,10 +141,13 @@ export interface ComponentInternalOptions { __file?: string } -export interface FunctionalComponent<P = {}, E extends EmitsOptions = {}> - extends ComponentInternalOptions { +export interface FunctionalComponent< + P = {}, + E extends EmitsOptions = {}, + S extends Slots = Slots +> extends ComponentInternalOptions { // use of any here is intentional so it can be a valid JSX Element constructor - (props: P, ctx: Omit<SetupContext<E>, 'expose'>): any + (props: P, ctx: Omit<SetupContext<E, P, S>, 'expose'>): any props?: ComponentPropsOptions<P> emits?: E | (keyof E)[] inheritAttrs?: boolean @@ -129,10 +170,13 @@ export type ConcreteComponent< RawBindings = any, D = any, C extends ComputedOptions = ComputedOptions, - M extends MethodOptions = MethodOptions + M extends MethodOptions = MethodOptions, + Mixin extends ComponentOptionsMixin = any, + Extends extends ComponentOptionsMixin = any, + E extends EmitsOptions = any > = - | ComponentOptions<Props, RawBindings, D, C, M> - | FunctionalComponent<Props, any> + | ComponentOptions<Props, RawBindings, D, C, M, Mixin, Extends, E> + | FunctionalComponent<Props, E> /** * A type used in public APIs where a component type is expected. @@ -148,6 +192,43 @@ export type Component< | ConcreteComponent<Props, RawBindings, D, C, M> | ComponentPublicInstanceConstructor<Props> +// export type BetterConcreteComponent< +// Props extends Record<string, Prop<unknown>> = {}, +// Emits extends EmitsOptions = {}, +// Slots = {}, +// // Allows to expose public API properties, this bypasses Data/Computed/Methods, +// // easier to declare manually +// Bindings extends Record<string, unknown> = {}, +// Options extends ComponentOptions = {} + +// > =; + +export type BetterComponent< + Props extends Record<string, unknown> = {}, + Emits extends EmitsOptions = {}, + Slots = {}, + // Allows to expose public API properties, this bypasses Data/Computed/Methods, + // easier to declare manually + Bindings extends Record<string, unknown> = {}, + LocalComponents extends Record<string, Component> = {}, + LocalDirectives extends Record<string, Directive> = {}, + D = any, + RawBindings = any, + C extends ComputedOptions = ComputedOptions, + M extends MethodOptions = MethodOptions, + Exposed extends string = string, + Mixin extends BetterComponentOptionsMixin = any, + Extends extends BetterComponentOptionsMixin = any, + Options extends BetterComponentOptions = any +> = + | ConcreteComponent<Props, Bindings> + | ComponentPublicInstanceConstructor<Props> + | { supa: Options } + +const ddd: BetterComponent = { + setup() {} +} + export { ComponentOptions } type LifecycleHook<TFn = Function> = TFn[] | null @@ -169,10 +250,14 @@ export const enum LifecycleHooks { SERVER_PREFETCH = 'sp' } -export interface SetupContext<E = EmitsOptions> { +export interface SetupContext< + E = EmitsOptions, + P = {}, + S extends Slots = Slots +> { attrs: Data - slots: Slots - emit: EmitFn<E> + slots: S + emit: EmitFn<E, P> expose: (exposed?: Record<string, any>) => void } diff --git a/packages/runtime-core/src/componentEmits.ts b/packages/runtime-core/src/componentEmits.ts index 390c6350b8c..59553af2a76 100644 --- a/packages/runtime-core/src/componentEmits.ts +++ b/packages/runtime-core/src/componentEmits.ts @@ -53,10 +53,34 @@ export type EmitsToProps<T extends EmitsOptions> = T extends string[] } : {} +// declare function processEmits<T extends EmitsOptions>(a: T): EmitsToProps<T> + +// const x = processEmits({ click: (a: 1) => true }) + +// declare const a: EmitsToProps<'click'[] & 'bar'[]> +// a.onClick + +// type Unpacked<T> = T extends (infer U)[] +// ? U +// : T extends (...args: any[]) => infer U +// ? U +// : T extends Promise<infer U> +// ? U +// : T +// type SSS<T> = T extends string[] ? T : false + +// declare const t: SSS<'click'[] & 'bar'[]> + +export type EmitVModelUpdate< + T, + E extends keyof T & string = keyof T & string +> = (event: `update:${E}`, value: T[E]) => void + export type EmitFn< Options = ObjectEmitsOptions, + P = {}, Event extends keyof Options = keyof Options -> = Options extends Array<infer V> +> = (Options extends Array<infer V> ? (event: V, ...args: any[]) => void : {} extends Options // if the emit is empty object (usually the default value for emit) should be converted to function ? (event: string, ...args: any[]) => void @@ -66,7 +90,8 @@ export type EmitFn< ? (event: key, ...args: Args) => void : (event: key, ...args: any[]) => void }[Event] - > + >) & + EmitVModelUpdate<P> export function emit( instance: ComponentInternalInstance, diff --git a/packages/runtime-core/src/componentOptions.ts b/packages/runtime-core/src/componentOptions.ts index db12bfbcf51..e9898e83a2c 100644 --- a/packages/runtime-core/src/componentOptions.ts +++ b/packages/runtime-core/src/componentOptions.ts @@ -55,7 +55,8 @@ import { EmitsOptions, EmitsToProps } from './componentEmits' import { Directive } from './directives' import { CreateComponentPublicInstance, - ComponentPublicInstance + ComponentPublicInstance, + BetterCreateComponentPublicInstance } from './componentPublicInstance' import { warn } from './warning' import { VNodeChild } from './vnode' @@ -69,6 +70,8 @@ import { softAssertCompatEnabled } from './compat/compatConfig' import { OptionMergeFunction } from './apiCreateApp' +import { Slots } from './componentSlots' +import { PublicProps } from './apiDefineComponent' /** * Interface for declaring custom options. @@ -98,13 +101,117 @@ type ExtractOptionProp<T> = T extends ComponentOptionsBase< any, // M any, // Mixin any, // Extends - any // EmitsOptions + infer E // EmitsOptions > ? unknown extends P ? {} - : P + : P & EmitsToProps<E> : {} +export interface BetterComponentOptionsBase< + Props extends Record<string, unknown>, + Emits extends EmitsOptions, + S, + LC extends Record<string, Component>, + LD extends Record<string, Directive>, + RawBindings, + D, + C extends ComputedOptions, + M extends MethodOptions, + EE extends string = string, + Exposed extends string = string, + Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, + Extends extends ComponentOptionsMixin = ComponentOptionsMixin, + Defaults = {} +> extends LegacyOptions<Props, D, C, M, Mixin, Extends, Emits>, + ComponentInternalOptions, + ComponentCustomOptions { + setup?: ( + this: void, + props: Readonly< + LooseRequired< + Props & + UnionToIntersection<ExtractOptionProp<Mixin>> & + UnionToIntersection<ExtractOptionProp<Extends>> & + EmitsToProps<Emits> + > + >, + ctx: SetupContext<Emits, Props, Slots<S>> + ) => Promise<RawBindings> | RawBindings | RenderFunction | void + + name?: string + template?: string | object // can be a direct DOM node + // Note: we are intentionally using the signature-less `Function` type here + // since any type with signature will cause the whole inference to fail when + // the return expression contains reference to `this`. + // Luckily `render()` doesn't need any arguments nor does it care about return + // type. + render?: Function + components?: LC + directives?: LD + inheritAttrs?: boolean + emits?: (Emits & ThisType<void>) | EE[] + expose?: Exposed[] + serverPrefetch?(): Promise<any> + + // Runtime compiler only ----------------------------------------------------- + compilerOptions?: RuntimeCompilerOptions + + slots?: S & ThisType<void> + + // Internal ------------------------------------------------------------------ + + /** + * SSR only. This is produced by compiler-ssr and attached in compiler-sfc + * not user facing, so the typing is lax and for test only. + * @internal + */ + ssrRender?: ( + ctx: any, + push: (item: any) => void, + parentInstance: ComponentInternalInstance, + attrs: Data | undefined, + // for compiler-optimized bindings + $props: ComponentInternalInstance['props'], + $setup: ComponentInternalInstance['setupState'], + $data: ComponentInternalInstance['data'], + $options: ComponentInternalInstance['ctx'] + ) => void + + /** + * Only generated by compiler-sfc to mark a ssr render function inlined and + * returned from setup() + * @internal + */ + __ssrInlineRender?: boolean + + /** + * marker for AsyncComponentWrapper + * @internal + */ + __asyncLoader?: () => Promise<ConcreteComponent> + /** + * the inner component resolved by the AsyncComponentWrapper + * @internal + */ + __asyncResolved?: ConcreteComponent + + // Type differentiators ------------------------------------------------------ + + // Note these are internal but need to be exposed in d.ts for type inference + // to work! + + // type-only differentiator to separate OptionWithoutProps from a constructor + // type returned by defineComponent() or FunctionalComponent + call?: (this: unknown, ...args: unknown[]) => never + // type-only differentiators for built-in Vnode types + __isFragment?: never + __isTeleport?: never + __isSuspense?: never + + __defaults?: Defaults +} + export interface ComponentOptionsBase< Props, RawBindings, @@ -113,10 +220,14 @@ export interface ComponentOptionsBase< M extends MethodOptions, Mixin extends ComponentOptionsMixin, Extends extends ComponentOptionsMixin, - E extends EmitsOptions, + E extends EmitsOptions = {}, EE extends string = string, + S = any, + LC extends Record<string, Component> = {}, + Directives extends Record<string, Directive> = {}, + Exposed extends string = string, Defaults = {} -> extends LegacyOptions<Props, D, C, M, Mixin, Extends>, +> extends LegacyOptions<Props, D, C, M, Mixin, Extends, E>, ComponentInternalOptions, ComponentCustomOptions { setup?: ( @@ -125,10 +236,11 @@ export interface ComponentOptionsBase< LooseRequired< Props & UnionToIntersection<ExtractOptionProp<Mixin>> & - UnionToIntersection<ExtractOptionProp<Extends>> + UnionToIntersection<ExtractOptionProp<Extends>> & + EmitsToProps<E> > >, - ctx: SetupContext<E> + ctx: SetupContext<E, Props, Slots<S>> ) => Promise<RawBindings> | RawBindings | RenderFunction | void name?: string template?: string | object // can be a direct DOM node @@ -138,17 +250,18 @@ export interface ComponentOptionsBase< // Luckily `render()` doesn't need any arguments nor does it care about return // type. render?: Function - components?: Record<string, Component> - directives?: Record<string, Directive> + components?: LC + directives?: Directives inheritAttrs?: boolean - emits?: (E | EE[]) & ThisType<void> - // TODO infer public instance type based on exposed keys - expose?: string[] + emits?: (E & ThisType<void>) | EE[] + expose?: Exposed[] serverPrefetch?(): Promise<any> // Runtime compiler only ----------------------------------------------------- compilerOptions?: RuntimeCompilerOptions + slots?: S & ThisType<void> + // Internal ------------------------------------------------------------------ /** @@ -222,6 +335,10 @@ export type ComponentOptionsWithoutProps< Extends extends ComponentOptionsMixin = ComponentOptionsMixin, E extends EmitsOptions = EmitsOptions, EE extends string = string, + S = any, + LC extends Record<string, Component> = {}, + Directives extends Record<string, Directive> = {}, + Exposed extends string = string, PE = Props & EmitsToProps<E> > = ComponentOptionsBase< PE, @@ -233,11 +350,31 @@ export type ComponentOptionsWithoutProps< Extends, E, EE, + S, + LC, + Directives, + Exposed, {} > & { props?: undefined } & ThisType< - CreateComponentPublicInstance<PE, RawBindings, D, C, M, Mixin, Extends, E> + CreateComponentPublicInstance< + Props, + RawBindings, + D, + C, + M, + Mixin, + Extends, + E, + S, + Props, + {}, + false, + LC, + Directives, + Exposed + > > export type ComponentOptionsWithArrayProps< @@ -250,7 +387,11 @@ export type ComponentOptionsWithArrayProps< Extends extends ComponentOptionsMixin = ComponentOptionsMixin, E extends EmitsOptions = EmitsOptions, EE extends string = string, - Props = Readonly<{ [key in PropNames]?: any }> & EmitsToProps<E> + S = any, + LC extends Record<string, Component> = {}, + Directives extends Record<string, Directive> = {}, + Exposed extends string = string, + Props = Readonly<{ [key in PropNames]?: any }> > = ComponentOptionsBase< Props, RawBindings, @@ -261,6 +402,10 @@ export type ComponentOptionsWithArrayProps< Extends, E, EE, + S, + LC, + Directives, + Exposed, {} > & { props: PropNames[] @@ -273,7 +418,14 @@ export type ComponentOptionsWithArrayProps< M, Mixin, Extends, - E + E, + S, + Props, + {}, + false, + LC, + Directives, + Exposed > > @@ -285,9 +437,13 @@ export type ComponentOptionsWithObjectProps< M extends MethodOptions = {}, Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, Extends extends ComponentOptionsMixin = ComponentOptionsMixin, - E extends EmitsOptions = EmitsOptions, + E extends EmitsOptions = {}, EE extends string = string, - Props = Readonly<ExtractPropTypes<PropsOptions>> & EmitsToProps<E>, + S = any, + LC extends Record<string, Component> = {}, + Directives extends Record<string, Directive> = {}, + Exposed extends string = string, + Props = Readonly<ExtractPropTypes<PropsOptions>>, Defaults = ExtractDefaultPropTypes<PropsOptions> > = ComponentOptionsBase< Props, @@ -299,6 +455,10 @@ export type ComponentOptionsWithObjectProps< Extends, E, EE, + S, + LC, + Directives, + Exposed, Defaults > & { props: PropsOptions & ThisType<void> @@ -312,9 +472,13 @@ export type ComponentOptionsWithObjectProps< Mixin, Extends, E, + S, Props, - Defaults, - false + {}, + false, + LC, + Directives, + Exposed > > @@ -326,11 +490,29 @@ export type ComponentOptions< M extends MethodOptions = any, Mixin extends ComponentOptionsMixin = any, Extends extends ComponentOptionsMixin = any, - E extends EmitsOptions = any -> = ComponentOptionsBase<Props, RawBindings, D, C, M, Mixin, Extends, E> & + E extends EmitsOptions = {}, + S = any, + LC extends Record<string, Component> = {}, + Directives extends Record<string, Directive> = {}, + Exposed extends string = string +> = ComponentOptionsBase< + Props, + RawBindings, + D, + C, + M, + Mixin, + Extends, + E, + string, + S, + LC, + Directives, + Exposed +> & ThisType< CreateComponentPublicInstance< - {}, + Props, RawBindings, D, C, @@ -338,12 +520,254 @@ export type ComponentOptions< Mixin, Extends, E, - Readonly<Props> + S, + Props, + {}, + false, + LC, + Directives, + Exposed + > + > + +export type BettterComponentOptionsWithoutProps< + Emits extends EmitsOptions = {}, + S = {}, + LC extends Record<string, Component> = {}, + LD extends Record<string, Directive> = {}, + RawBindings = {}, + D = {}, + C extends ComputedOptions = {}, + M extends MethodOptions = {}, + EE extends string = string, + Exposed extends string = string, + Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, + Extends extends ComponentOptionsMixin = ComponentOptionsMixin, + Defaults = {} +> = BetterComponentOptions< + {}, + Emits, + S, + LC, + LD, + RawBindings, + D, + C, + M, + EE, + Exposed, + Mixin, + Extends, + Defaults +> & { + props?: null +} + +export type BettterComponentOptionsWithArrayProps< + Props extends Record<string, unknown> = {}, + Emits extends EmitsOptions = {}, + S = {}, + LC extends Record<string, Component> = {}, + LD extends Record<string, Directive> = {}, + RawBindings = {}, + D = {}, + C extends ComputedOptions = {}, + M extends MethodOptions = {}, + EE extends string = string, + Exposed extends string = string, + Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, + Extends extends ComponentOptionsMixin = ComponentOptionsMixin, + Defaults = {}, + PropNames extends string = string +> = BetterComponentOptions< + Props, + Emits, + S, + LC, + LD, + RawBindings, + D, + C, + M, + EE, + Exposed, + Mixin, + Extends, + Defaults +> & { + props: PropNames[] +} + +export type BettterComponentOptionsWithObjectProps< + Props extends Record<string, unknown> = {}, + Emits extends EmitsOptions = {}, + S = {}, + LC extends Record<string, Component> = {}, + LD extends Record<string, Directive> = {}, + RawBindings = {}, + D = {}, + C extends ComputedOptions = {}, + M extends MethodOptions = {}, + EE extends string = string, + Exposed extends string = string, + Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, + Extends extends ComponentOptionsMixin = ComponentOptionsMixin, + Defaults = {}, + PropsOptions extends ComponentObjectPropsOptions = ComponentObjectPropsOptions +> = BetterComponentOptions< + Props, + Emits, + S, + LC, + LD, + RawBindings, + D, + C, + M, + EE, + Exposed, + Mixin, + Extends, + Defaults +> & { + props: PropsOptions & ThisType<void> +} + +// export type BetterComponentOptionsTyped< +// Props extends Record<string, unknown> = {}, +// Emits extends EmitsOptions = {}, +// S = {}, +// LC extends Record<string, Component> = {}, +// LD extends Record<string, Directive> = {}, +// RawBindings = {}, +// D = {}, +// C extends ComputedOptions = {}, +// M extends MethodOptions = {}, +// EE extends string = string, +// Exposed extends string = string, +// Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, +// Extends extends ComponentOptionsMixin = ComponentOptionsMixin, +// Defaults = {} +// > = BetterComponentOptionsBase< +// Props, +// Emits, +// S, +// LC, +// LD, +// RawBindings, +// D, +// C, +// M, +// EE, +// Exposed, +// Mixin, +// Extends, +// Defaults +// > & +// ThisType< +// BetterCreateComponentPublicInstance< +// Props, +// Emits, +// S, +// LC, +// LD, +// RawBindings, +// D, +// C, +// M, +// Exposed, +// Mixin, +// Extends, +// PublicProps, +// Defaults +// > +// > + +export type BetterComponentOptions< + Props extends Record<string, unknown> = {}, + Emits extends EmitsOptions = {}, + S = {}, + LC extends Record<string, Component> = {}, + LD extends Record<string, Directive> = {}, + RawBindings = {}, + D = {}, + C extends ComputedOptions = {}, + M extends MethodOptions = {}, + EE extends string = string, + Exposed extends string = string, + Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, + Extends extends ComponentOptionsMixin = ComponentOptionsMixin, + Defaults = {} +> = BetterComponentOptionsBase< + Props, + Emits, + S, + LC, + LD, + RawBindings, + D, + C, + M, + EE, + Exposed, + Mixin, + Extends, + Defaults +> & + ThisType< + BetterCreateComponentPublicInstance< + Props, + Emits, + S, + LC, + LD, + RawBindings, + D, + C, + M, + Exposed, + Mixin, + Extends, + PublicProps, + Defaults > > export type ComponentOptionsMixin = ComponentOptionsBase< any, + any, + any, + any, + any, + any, + any, + any, + any, + any, + any, + any, + any, + any +> + +export type BetterComponentOptionsAny = BetterComponentOptions< + any, + any, + any, + any, + any, + any, + any, + any, + any, + any, + any, + any, + any, + any +> + +export type BetterComponentOptionsMixin = BetterComponentOptionsBase< any, any, any, @@ -394,6 +818,7 @@ interface LegacyOptions< D, C extends ComputedOptions, M extends MethodOptions, + E extends EmitsOptions, Mixin extends ComponentOptionsMixin, Extends extends ComponentOptionsMixin > { @@ -414,7 +839,8 @@ interface LegacyOptions< {}, MethodOptions, Mixin, - Extends + Extends, + E >, vm: CreateComponentPublicInstance< Props, @@ -423,7 +849,8 @@ interface LegacyOptions< {}, MethodOptions, Mixin, - Extends + Extends, + E > ) => D computed?: C @@ -500,7 +927,7 @@ export type MergedComponentOptionsOverride = { errorCaptured?: MergedHook<ErrorCapturedHook> } -export type OptionTypesKeys = 'P' | 'B' | 'D' | 'C' | 'M' | 'Defaults' +export type OptionTypesKeys = 'P' | 'B' | 'D' | 'C' | 'M' | 'E' | 'Defaults' export type OptionTypesType< P = {}, @@ -508,9 +935,44 @@ export type OptionTypesType< D = {}, C extends ComputedOptions = {}, M extends MethodOptions = {}, + E extends EmitsOptions = {}, + Defaults = {} +> = { + P: P + B: B + D: D + C: C + M: M + E: E + Defaults: Defaults +} + +export type BetterOptionTypesKeys = + | 'P' + | 'E' + | 'LC' + | 'LD' + | 'B' + | 'D' + | 'C' + | 'M' + | 'Defaults' + +export type BetterOptionTypesType< + P extends Record<string, unknown> = {}, + E extends EmitsOptions = {}, + LC extends Record<string, Component> = {}, + LD extends Record<string, Directive> = {}, + B = {}, + D = {}, + C extends ComputedOptions = {}, + M extends MethodOptions = {}, Defaults = {} > = { P: P + E: E + LC: LC + LD: LD B: B D: D C: C diff --git a/packages/runtime-core/src/componentProps.ts b/packages/runtime-core/src/componentProps.ts index 7d7f3a38728..319ff59935d 100644 --- a/packages/runtime-core/src/componentProps.ts +++ b/packages/runtime-core/src/componentProps.ts @@ -97,7 +97,7 @@ type DefaultKeys<T> = { : never }[keyof T] -type InferPropType<T> = [T] extends [null] +export type InferPropType<T> = [T] extends [null] ? any // null & true would fail to infer : [T] extends [{ type: null | true }] ? any // As TS issue https://github.com/Microsoft/TypeScript/issues/14829 // somehow `ObjectConstructor` when inferred from { (): T } becomes `any` // `BooleanConstructor` when inferred from PropConstructor(with PropMethod) becomes `Boolean` @@ -113,12 +113,20 @@ type InferPropType<T> = [T] extends [null] : V : T -export type ExtractPropTypes<O> = O extends object +export type ExtractPropTypesOld<O> = O extends object ? { [K in keyof O]?: unknown } & // This is needed to keep the relation between the option prop and the props, allowing to use ctrl+click to navigate to the prop options. see: #3656 { [K in RequiredKeys<O>]: InferPropType<O[K]> } & { [K in OptionalKeys<O>]?: InferPropType<O[K]> } : { [K in string]: any } +export type ExtractPropTypes<O, RK = RequiredKeys<O>> = O extends object + ? { + [K in keyof O]: K extends RK + ? InferPropType<O[K]> + : InferPropType<O[K]> | undefined + } + : {} // or { [K in string]: any } //TODO CR check + const enum BooleanFlags { shouldCast, shouldCastTrue diff --git a/packages/runtime-core/src/componentPublicInstance.ts b/packages/runtime-core/src/componentPublicInstance.ts index aad6207ec46..486a3ed4595 100644 --- a/packages/runtime-core/src/componentPublicInstance.ts +++ b/packages/runtime-core/src/componentPublicInstance.ts @@ -1,4 +1,5 @@ import { + Component, ComponentInternalInstance, Data, getExposeProxy, @@ -33,15 +34,23 @@ import { OptionTypesKeys, resolveMergedOptions, shouldCacheAccess, - MergedComponentOptionsOverride + MergedComponentOptionsOverride, + BetterOptionTypesType, + BetterComponentOptionsBase, + BetterOptionTypesKeys, + BetterComponentOptionsMixin, + BetterComponentOptionsAny } from './componentOptions' -import { EmitsOptions, EmitFn } from './componentEmits' -import { Slots } from './componentSlots' +import { EmitsOptions, EmitFn, EmitsToProps } from './componentEmits' +import { RenderSlot, Slots } from './componentSlots' import { markAttrsAccessed } from './componentRenderUtils' import { currentRenderingInstance } from './componentRenderContext' import { warn } from './warning' import { UnionToIntersection } from './helpers/typeUtils' +import { Directive } from './directives' import { installCompatInstanceProperties } from './compat/instance' +import { Slot } from './componentSlots' +import { PublicProps } from './apiDefineComponent' /** * Custom properties added to component instances in any way and can be accessed through `this` @@ -84,24 +93,71 @@ type MixinToOptionTypes<T> = T extends ComponentOptionsBase< infer M, infer Mixin, infer Extends, + infer E, + any, + any, + any, any, any, infer Defaults > - ? OptionTypesType<P & {}, B & {}, D & {}, C & {}, M & {}, Defaults & {}> & + ? OptionTypesType<P & {}, B & {}, D & {}, C & {}, M & {}, E, Defaults & {}> & IntersectionMixin<Mixin> & IntersectionMixin<Extends> : never +type BetterMixinToOptionTypes<T> = T extends BetterComponentOptionsBase< + infer Props, + infer Emits, + any, + infer LC, + infer LD, + infer B, + infer D, + infer C, + infer M, + any, + any, + infer Mixin, + infer Extends, + infer Defaults +> + ? BetterOptionTypesType< + Props & {}, + Emits /* & {}*/, + LC & {}, + LD & {}, + B & {}, + D & {}, + C & {}, + M & {}, + Defaults & {} + > & + BetterIntersectionMixin<Mixin> & + BetterIntersectionMixin<Extends> + : never + // ExtractMixin(map type) is used to resolve circularly references type ExtractMixin<T> = { Mixin: MixinToOptionTypes<T> }[T extends ComponentOptionsMixin ? 'Mixin' : never] type IntersectionMixin<T> = IsDefaultMixinComponent<T> extends true - ? OptionTypesType<{}, {}, {}, {}, {}> + ? OptionTypesType<{}, {}, {}, {}, {}, {}, {}> : UnionToIntersection<ExtractMixin<T>> +type BetterExtractMixin<T> = { + Mixin: MixinToOptionTypes<T> +}[T extends ComponentOptionsMixin ? 'Mixin' : never] +type BetterIntersectionMixin<T> = IsDefaultMixinComponent<T> extends true + ? BetterOptionTypesType<{}, {}, {}, {}, {}, {}, {}> + : BetterExtractMixin<T> + +type BetterUnwrapMixinsType< + T, + Type extends BetterOptionTypesKeys +> = T extends BetterOptionTypesType ? T[Type] : never + type UnwrapMixinsType< T, Type extends OptionTypesKeys @@ -109,6 +165,67 @@ type UnwrapMixinsType< type EnsureNonVoid<T> = T extends void ? {} : T +/** + Props extends Record<string, unknown> = {}, + Emits extends EmitsOptions = {}, + S extends Slots<unknown> = {}, + Bindings extends Record<string, unknown> = {}, + LC extends Record<string, Component> = {}, + LD extends Record<string, Directive> = {}, + D = {}, // return from data() + Options extends BetterComponentOptionsAny = BetterComponentOptionsBase< + Props, + Emits, + S, + LC, + LD, + any, + D, + any, + any + > + +*/ + +// Render component AKA ComponentPublicInstanceConstructor +// NOTE maybe this can be a class? to allow more advanced typing +export type RenderComponent< + Props extends Record<string, unknown> = {}, + Emits extends EmitsOptions = {}, + S = {}, + // Allows to expose public API properties, this bypasses Data/Computed/Methods, + // easier to declare manually + Bindings extends Record<string, unknown> = {}, + LC extends Record<string, Component> = {}, + LD extends Record<string, Directive> = {}, + D = {}, // return from data() + Options extends BetterComponentOptionsAny = BetterComponentOptionsBase< + Props, + Emits, + S, + LC, + LD, + any, + D, + any, + any + > +> = { + __isFragment?: never + __isTeleport?: never + __isSuspense?: never + new (...args: any[]): BetterComponentPublicInstance< + Props, + Emits, + Readonly<Slots<S>>, + Bindings, + LC, + LD, + D, + Options + > +} + export type ComponentPublicInstanceConstructor< T extends ComponentPublicInstance< Props, @@ -129,6 +246,86 @@ export type ComponentPublicInstanceConstructor< new (...args: any[]): T } +type FixS<T extends EmitsOptions> = T extends string[] + ? // the omit serves to remove the type 'bar[]' from `bar[] & { foo: Function}` + Record<T[number], null> & + (T extends object ? Omit<T, T[number] | keyof string[]> : never) + : T + +export type BetterCreateComponentPublicInstance< + P extends Record<string, unknown> = {}, + E extends EmitsOptions = {}, + S = any, + LC extends Record<string, Component> = {}, + LD extends Record<string, Directive> = {}, + B = {}, + D = {}, + C extends ComputedOptions = {}, + M extends MethodOptions = {}, + Exposed extends string = string, + Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, + Extends extends ComponentOptionsMixin = ComponentOptionsMixin, + PublicProps = P, + Defaults = {}, + MakeDefaultsOptional extends boolean = false, + PublicMixin = BetterIntersectionMixin<Mixin> & + BetterIntersectionMixin<Extends>, + PublicP extends Record<string, unknown> = Readonly< + BetterUnwrapMixinsType<PublicMixin, 'P'> + > & + EnsureNonVoid<P>, + PublicLC extends Record<string, Component> = BetterUnwrapMixinsType< + PublicMixin, + 'LC' + > & + EnsureNonVoid<LC>, + PublicLD extends Record<string, Directive> = BetterUnwrapMixinsType< + PublicMixin, + 'LD' + > & + EnsureNonVoid<LD>, + PublicB = BetterUnwrapMixinsType<PublicMixin, 'B'> & EnsureNonVoid<B>, + PublicD = BetterUnwrapMixinsType<PublicMixin, 'D'> & EnsureNonVoid<D>, + PublicC extends ComputedOptions = BetterUnwrapMixinsType<PublicMixin, 'C'> & + EnsureNonVoid<C>, + PublicM extends MethodOptions = BetterUnwrapMixinsType<PublicMixin, 'M'> & + EnsureNonVoid<M>, + // Emits behave a bit different and require union instead of intersection + PublicE extends EmitsOptions = FixS< + BetterUnwrapMixinsType<BetterIntersectionMixin<Mixin>, 'E'> + > & + FixS<BetterUnwrapMixinsType<BetterIntersectionMixin<Extends>, 'E'>> & + FixS<EnsureNonVoid<E>>, + PublicDefaults = BetterUnwrapMixinsType<PublicMixin, 'Defaults'> & + EnsureNonVoid<Defaults> +> = BetterComponentPublicInstance< + (MakeDefaultsOptional extends true + ? Partial<PublicDefaults> & + Omit<PublicP & PublicProps, keyof PublicDefaults> + : PublicP & PublicProps) & + EmitsToProps<PublicE>, + PublicE, + Readonly<Slots<S>>, + ExposedKeys< + // Props + (MakeDefaultsOptional extends true + ? Partial<PublicDefaults> & + Omit<PublicP & PublicProps, keyof PublicDefaults> + : PublicP & PublicProps) & + EmitsToProps<PublicE> & + // /Props + + ShallowUnwrapRef<PublicB> & + UnwrapNestedRefs<PublicD> & + ExtractComputedReturns<PublicC> & + PublicM, + Exposed + >, + PublicLC, + PublicLD, + PublicD +> + export type CreateComponentPublicInstance< P = {}, B = {}, @@ -138,17 +335,27 @@ export type CreateComponentPublicInstance< Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, Extends extends ComponentOptionsMixin = ComponentOptionsMixin, E extends EmitsOptions = {}, + S = any, PublicProps = P, Defaults = {}, MakeDefaultsOptional extends boolean = false, + LC extends Record<string, Component> = {}, + Directives extends Record<string, Directive> = {}, + Exposed extends string = string, PublicMixin = IntersectionMixin<Mixin> & IntersectionMixin<Extends>, - PublicP = UnwrapMixinsType<PublicMixin, 'P'> & EnsureNonVoid<P>, + PublicP = Readonly<UnwrapMixinsType<PublicMixin, 'P'>> & EnsureNonVoid<P>, PublicB = UnwrapMixinsType<PublicMixin, 'B'> & EnsureNonVoid<B>, PublicD = UnwrapMixinsType<PublicMixin, 'D'> & EnsureNonVoid<D>, PublicC extends ComputedOptions = UnwrapMixinsType<PublicMixin, 'C'> & EnsureNonVoid<C>, PublicM extends MethodOptions = UnwrapMixinsType<PublicMixin, 'M'> & EnsureNonVoid<M>, + // Emits behave a bit different and require union instead of intersection + PublicE extends EmitsOptions = FixS< + UnwrapMixinsType<IntersectionMixin<Mixin>, 'E'> + > & + FixS<UnwrapMixinsType<IntersectionMixin<Extends>, 'E'>> & + FixS<EnsureNonVoid<E>>, PublicDefaults = UnwrapMixinsType<PublicMixin, 'Defaults'> & EnsureNonVoid<Defaults> > = ComponentPublicInstance< @@ -157,13 +364,78 @@ export type CreateComponentPublicInstance< PublicD, PublicC, PublicM, - E, + PublicE, + S, PublicProps, PublicDefaults, MakeDefaultsOptional, - ComponentOptionsBase<P, B, D, C, M, Mixin, Extends, E, string, Defaults> + ComponentOptionsBase< + P, + B, + D, + C, + M, + Mixin, + Extends, + E, + string, + S, + LC, + Directives, + Exposed, + Defaults + >, + Exposed > +export type ExposedKeys< + T, + Exposed extends string & keyof T +> = '' extends Exposed ? T : Pick<T, Exposed> + +export type BetterComponentPublicInstance< + Props extends Record<string, unknown> = {}, + Emits extends EmitsOptions = {}, + S extends Slots<unknown> = {}, + Bindings extends Record<string, unknown> = {}, + LC extends Record<string, Component> = {}, + LD extends Record<string, Directive> = {}, + D = {}, // return from data() + Options extends BetterComponentOptionsAny = BetterComponentOptionsBase< + Props, + Emits, + S, + LC, + LD, + any, + D, + any, + any + > +> = { + $: ComponentInternalInstance + $data: D + $props: Props & PublicProps + + $attrs: Data + $refs: Data + $slots: Readonly<S> + $root: BetterComponentPublicInstance | null + $parent: BetterComponentPublicInstance | null + $emit: EmitFn<Emits, Props> + $el: any + $options: Options & MergedComponentOptionsOverride + $forceUpdate: () => void + $nextTick: typeof nextTick + $watch( + source: string | Function, + cb: Function, + options?: WatchOptions + ): WatchStopHandle +} & Bindings & + // TODO check if the custom properties are exposed with expose + ComponentCustomProperties + // public properties exposed on the proxy, which is used as the render context // in templates (as `this` in the render option) export type ComponentPublicInstance< @@ -173,22 +445,37 @@ export type ComponentPublicInstance< C extends ComputedOptions = {}, M extends MethodOptions = {}, E extends EmitsOptions = {}, + S = any, PublicProps = P, Defaults = {}, MakeDefaultsOptional extends boolean = false, - Options = ComponentOptionsBase<any, any, any, any, any, any, any, any, any> + Options = ComponentOptionsBase< + any, + any, + any, + any, + any, + any, + any, + any, + any, + any + >, + Exposed extends string = '' > = { + $XXX: E $: ComponentInternalInstance $data: D - $props: MakeDefaultsOptional extends true + $props: (MakeDefaultsOptional extends true ? Partial<Defaults> & Omit<P & PublicProps, keyof Defaults> - : P & PublicProps + : P & PublicProps) & + EmitsToProps<E> $attrs: Data $refs: Data - $slots: Slots + $slots: Slots<S> $root: ComponentPublicInstance | null $parent: ComponentPublicInstance | null - $emit: EmitFn<E> + $emit: EmitFn<E, P> $el: any $options: Options & MergedComponentOptionsOverride $forceUpdate: () => void @@ -198,12 +485,15 @@ export type ComponentPublicInstance< cb: Function, options?: WatchOptions ): WatchStopHandle -} & P & - ShallowUnwrapRef<B> & - UnwrapNestedRefs<D> & - ExtractComputedReturns<C> & - M & - ComponentCustomProperties +} & ExposedKeys< + P & + ShallowUnwrapRef<B> & + UnwrapNestedRefs<D> & + ExtractComputedReturns<C> & + M & + ComponentCustomProperties, + Exposed +> export type PublicPropertiesMap = Record< string, diff --git a/packages/runtime-core/src/componentSlots.ts b/packages/runtime-core/src/componentSlots.ts index ec58fd695c0..542bb0a524e 100644 --- a/packages/runtime-core/src/componentSlots.ts +++ b/packages/runtime-core/src/componentSlots.ts @@ -22,16 +22,37 @@ import { isHmrUpdating } from './hmr' import { DeprecationTypes, isCompatEnabled } from './compat/compatConfig' import { toRaw } from '@vue/reactivity' -export type Slot = (...args: any[]) => VNode[] +export type Slot = (...args: any[]) => VNode[] | VNode -export type InternalSlots = { - [name: string]: Slot | undefined +export type SlotTyped<T> = T extends null + ? () => VNode[] | VNode + : (arg: T) => VNode[] | VNode + +export type InternalSlots<T = any> = { + [K in keyof T]?: T[K] extends () => infer R ? SlotTyped<R> : SlotTyped<T[K]> } -export type Slots = Readonly<InternalSlots> +export type SlotsObject<T = any> = InternalSlots<T> +export type SlotArray<V = PropertyKey> = V extends PropertyKey + ? Record<V, Slot> + : Record<string, Slot> -export type RawSlots = { - [name: string]: unknown +export type Slots<T = any> = RenderSlot & + (unknown extends T + ? Readonly<Partial<Record<string, Slot>>> + : T extends Array<infer V> + ? Readonly<SlotArray<V>> + : Readonly<SlotsObject<T>>) + +declare const rr: Slots<{ a: null }> extends Readonly<InternalSlots<any>> + ? true + : false + +if (rr) { + rr?.valueOf() +} + +export type RenderSlot = { // manual render fn hint to skip forced children updates $stable?: boolean /** @@ -50,6 +71,9 @@ export type RawSlots = { */ _?: SlotFlags } +export type RawSlots = { + [name: string]: unknown +} & RenderSlot const isInternalKey = (key: string) => key[0] === '_' || key === '$stable' diff --git a/packages/runtime-core/src/directives.ts b/packages/runtime-core/src/directives.ts index 58e56df0522..4b834879221 100644 --- a/packages/runtime-core/src/directives.ts +++ b/packages/runtime-core/src/directives.ts @@ -22,19 +22,29 @@ import { mapCompatDirectiveHook } from './compat/customDirective' import { pauseTracking, resetTracking } from '@vue/reactivity' import { traverse } from './apiWatch' -export interface DirectiveBinding<V = any> { +export interface DirectiveBinding< + Value = any, + Modifiers extends string = string, + Arg extends string = string +> { instance: ComponentPublicInstance | null - value: V - oldValue: V | null - arg?: string - modifiers: DirectiveModifiers - dir: ObjectDirective<any, V> + value: Value + oldValue: Value | null + arg?: Arg + modifiers: DirectiveModifiers<Modifiers> + dir: ObjectDirective<any, Value> } -export type DirectiveHook<T = any, Prev = VNode<any, T> | null, V = any> = ( - el: T, - binding: DirectiveBinding<V>, - vnode: VNode<any, T>, +export type DirectiveHook< + HostElement = any, + Prev = VNode<any, HostElement> | null, + Value = any, + Modifiers extends string = string, + Arg extends string = string +> = ( + el: HostElement, + binding: DirectiveBinding<Value, Modifiers, Arg>, + vnode: VNode<any, HostElement>, prevVNode: Prev ) => void @@ -43,25 +53,52 @@ export type SSRDirectiveHook = ( vnode: VNode ) => Data | undefined -export interface ObjectDirective<T = any, V = any> { - created?: DirectiveHook<T, null, V> - beforeMount?: DirectiveHook<T, null, V> - mounted?: DirectiveHook<T, null, V> - beforeUpdate?: DirectiveHook<T, VNode<any, T>, V> - updated?: DirectiveHook<T, VNode<any, T>, V> - beforeUnmount?: DirectiveHook<T, null, V> - unmounted?: DirectiveHook<T, null, V> +export interface ObjectDirective< + HostElement = any, + Value = any, + Modifiers extends string = string, + Arg extends string = string +> { + created?: DirectiveHook<HostElement, null, Value, Modifiers, Arg> + beforeMount?: DirectiveHook<HostElement, null, Value, Modifiers, Arg> + mounted?: DirectiveHook<HostElement, null, Value, Modifiers, Arg> + beforeUpdate?: DirectiveHook< + HostElement, + VNode<any, HostElement>, + Value, + Modifiers, + Arg + > + updated?: DirectiveHook< + HostElement, + VNode<any, HostElement>, + Value, + Modifiers, + Arg + > + beforeUnmount?: DirectiveHook<HostElement, null, Value, Modifiers, Arg> + unmounted?: DirectiveHook<HostElement, null, Value, Modifiers, Arg> getSSRProps?: SSRDirectiveHook deep?: boolean } -export type FunctionDirective<T = any, V = any> = DirectiveHook<T, any, V> +export type FunctionDirective< + HostElement = any, + V = any, + Modifiers extends string = string, + Arg extends string = string +> = DirectiveHook<HostElement, any, V, Modifiers, Arg> -export type Directive<T = any, V = any> = - | ObjectDirective<T, V> - | FunctionDirective<T, V> +export type Directive< + HostElement = any, + Value = any, + Modifiers extends string = string, + Arg extends string = string +> = + | ObjectDirective<HostElement, Value, Modifiers, Arg> + | FunctionDirective<HostElement, Value, Modifiers, Arg> -export type DirectiveModifiers = Record<string, boolean> +export type DirectiveModifiers<K extends string = string> = Record<K, boolean> const isBuiltInDirective = /*#__PURE__*/ makeMap( 'bind,cloak,else-if,else,for,html,if,model,on,once,pre,show,slot,text' diff --git a/packages/runtime-core/src/h.ts b/packages/runtime-core/src/h.ts index f22e4bb30d0..753503ec367 100644 --- a/packages/runtime-core/src/h.ts +++ b/packages/runtime-core/src/h.ts @@ -15,11 +15,15 @@ import { RawSlots } from './componentSlots' import { FunctionalComponent, Component, + ConcreteComponent, ComponentOptions, - ConcreteComponent + BetterComponent } from './component' import { EmitsOptions } from './componentEmits' -import { DefineComponent } from './apiDefineComponent' +import { betterDefineComponent, DefineComponent } from './apiDefineComponent' +import { Prop, Slots } from 'test-dts' +import { BetterComponentOptions } from './componentOptions' +import { RenderComponent } from './componentPublicInstance' // `h` is a more user-friendly version of `createVNode` that allows omitting the // props when possible. It is intended for manually written render functions. @@ -68,16 +72,67 @@ type RawChildren = | (() => any) // fake constructor type returned from `defineComponent` -interface Constructor<P = any> { +interface Constructor<P = {}, S = any> { __isFragment?: never __isTeleport?: never __isSuspense?: never - new (...args: any[]): { $props: P } + __isDefineComponent?: never + new (...args: any[]): { $props: P; $slots?: S } } +// Converts emits value to object +type ExtractEmitEvents<T> = T extends Readonly<Array<infer V>> + ? { [K in V & string as `on${Capitalize<K>}`]: (...args: any[]) => void } + : T extends any[] + ? { [K in T & string as `on${Capitalize<K>}`]: (...args: any[]) => void } + : {} extends T // if the emit is empty object (usually the default value for emit) should be converted to function + ? {} + : { + [K in keyof T & string as `on${Capitalize<K>}`]: T[K] extends ( + ...args: infer Args + ) => any + ? (...args: Args) => void + : (...args: any[]) => void + } + +// TODO remove `on*` props aka Emit events +type ExtractEmitPropUpdate< + P = {}, + PK extends keyof P & string = keyof P & string +> = P extends Readonly<Array<infer V>> + ? { [K in V & string as `onUpdate:${K}`]?: (value: any) => void } + : P extends any[] + ? { [K in P & string as `onUpdate:${K}`]?: (value: any) => void } + : // we need to omit if it infers emit as props + { + [K in keyof Omit<P, `on${Capitalize<PK>}`> & + string as `onUpdate:${K}`]?: (value: P[K]) => void + } + +type RenderProps<P, E extends EmitsOptions = {}> = + | (Partial<ExtractEmitEvents<E>> & RawProps & P & ExtractEmitPropUpdate<P>) + | ({} extends P ? Partial<ExtractEmitEvents<E>> | null : never) + +type RenderSlots<S> = + | Slots<S> + | ({} extends S ? RawSlots : Slots<S>) + | RawChildren + // The following is a series of overloads for providing props validation of // manually written render functions. +// functional component +// NOTE: is set on top to allow infer the props when doing +/// const Func = (_props: { foo: string; bar?: number }) => '' +/// h(Func, {}) +// otherwise it will default to `h(type: string)` +export function h<P, E extends EmitsOptions = {}>( + type: FunctionalComponent<P, E>, + props?: RenderProps<P, E>, + children?: RawChildren | RawSlots +): VNode +export function h(type: FunctionalComponent): VNode + // element export function h(type: string, children?: RawChildren): VNode export function h( @@ -120,23 +175,24 @@ export function h( ): VNode // functional component -export function h<P, E extends EmitsOptions = {}>( - type: FunctionalComponent<P, E>, - props?: (RawProps & P) | ({} extends P ? null : never), - children?: RawChildren | RawSlots -): VNode +// export function h<P, E extends EmitsOptions = {}>( +// type: FunctionalComponent<P, E>, +// props?: (RawProps & P) | ({} extends P ? null : never), +// children?: RawChildren | RawSlots +// ): VNode // catch-all for generic component types export function h(type: Component, children?: RawChildren): VNode // concrete component -export function h<P>( - type: ConcreteComponent | string, +export function h<P, E extends EmitsOptions = {}>( + type: ConcreteComponent<P, any, any, any, any, any, any, E> | string, + props?: RenderProps<P, E>, children?: RawChildren ): VNode + export function h<P>( - type: ConcreteComponent<P> | string, - props?: (RawProps & P) | ({} extends P ? null : never), + type: ConcreteComponent | string, children?: RawChildren ): VNode @@ -148,27 +204,59 @@ export function h( ): VNode // exclude `defineComponent` constructors -export function h<P>( - type: ComponentOptions<P>, - props?: (RawProps & P) | ({} extends P ? null : never), - children?: RawChildren | RawSlots -): VNode - -// fake constructor type returned by `defineComponent` or class component -export function h(type: Constructor, children?: RawChildren): VNode -export function h<P>( - type: Constructor<P>, - props?: (RawProps & P) | ({} extends P ? null : never), - children?: RawChildren | RawSlots +export function h< + P, + S, + E extends EmitsOptions = {}, + PP = {}, + Props = {}, + Defaults = {} +>( + type: ComponentOptions<P, any, any, any, any, any, any, E, S>, + props?: RenderProps<Partial<Defaults> & Omit<Props & PP, keyof Defaults>, E>, + children?: (RawChildren & Slots<S>) | ({} extends S ? RawSlots : Slots<S>) ): VNode // fake constructor type returned by `defineComponent` +export function h< + P, + S, + E extends EmitsOptions = {}, + PP = {}, + Props = {}, + Defaults = {} +>( + type: DefineComponent< + P, + any, + any, + any, + any, + any, + any, + E, + any, + S, + {}, + {}, + string, + PP, + Props, + Defaults + >, + props?: RenderProps<Partial<Defaults> & Omit<Props & PP, keyof Defaults>, E>, + children?: (RawChildren & Slots<S>) | ({} extends S ? RawSlots : Slots<S>) +): VNode +export function h(type: DefineComponent): VNode export function h(type: DefineComponent, children?: RawChildren): VNode -export function h<P>( - type: DefineComponent<P>, - props?: (RawProps & P) | ({} extends P ? null : never), - children?: RawChildren | RawSlots + +// fake constructor type returned by `defineComponent` or class component +export function h<P, S, E extends EmitsOptions = {}>( + type: Constructor<P, S>, + props?: RenderProps<P, E>, + children?: (RawChildren & Slots<S>) | ({} extends S ? RawSlots : Slots<S>) ): VNode +export function h(type: Constructor, children?: RawChildren): VNode // Actual implementation export function h(type: any, propsOrChildren?: any, children?: any): VNode { @@ -194,3 +282,101 @@ export function h(type: any, propsOrChildren?: any, children?: any): VNode { return createVNode(type, propsOrChildren, children) } } + +// declare function betterH< +// P extends Record<string, unknown>, +// E extends EmitsOptions, +// S +// >( +// type: BetterComponentOptions<P, E, S>, +// props?: RenderProps<P, E>, +// children?: (RawChildren & Slots<S>) | ({} extends S ? RawSlots : Slots<S>) +// ): P +export function betterH< + P extends Record<string, unknown>, + E extends EmitsOptions, + S +>( + type: BetterComponent<P, E, S>, + props?: RenderProps<P, E>, + children?: RenderSlots<S> +): P + +export function betterH< + P extends Record<string, unknown>, + E extends EmitsOptions, + S +>( + type: RenderComponent<P, E, S>, + props?: RenderProps<P, E>, + children?: RenderSlots<S> +): P +export function betterH( + type: any, + propsOrChildren?: any, + children?: any +): any {} + +declare const MyComp: BetterComponent< + { test: number }, + ['hey'], + { + default: null + typedSlot: { a: number } + } +> +const r = betterH( + MyComp, + { + test: 2, + onHey(rrr) {} + }, + { + default() { + return {} as unknown as VNode + }, + typedSlot(e) { + return {} as unknown as VNode + } + } +) + +const x = betterH( + {} as unknown as RenderComponent< + { + test: number + }, + ['tst'], + { + default: null + } + >, + { + test: 2 + } +) + +const Comp = betterDefineComponent({ + // props: ['test'] +}) + +betterH( + Comp, + { + test: 2 + }, + {} +) + +declare function test(t: BetterComponent): boolean +test(Comp) + +declare const MyComp2: Component<{ test: number }> + +h( + MyComp2, + { + test: 2 + }, + {} +) diff --git a/packages/runtime-core/src/helpers/typeUtils.ts b/packages/runtime-core/src/helpers/typeUtils.ts index 204543e6de2..8ab2862afd6 100644 --- a/packages/runtime-core/src/helpers/typeUtils.ts +++ b/packages/runtime-core/src/helpers/typeUtils.ts @@ -5,4 +5,6 @@ export type UnionToIntersection<U> = ( : never // make keys required but keep undefined values -export type LooseRequired<T> = { [P in string & keyof T]: T[P] } +// export type LooseRequired<T> = { [P in string & keyof T]: T[P] } +// TODO validate this change, was what's above +export type LooseRequired<T> = { [P in keyof T]: T[P] } diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index c46b6df2276..b2fc21d3341 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -146,6 +146,23 @@ declare module '@vue/reactivity' { } } +// Augment GlobalComponents +// Note: if updating this, also update `types/globalComponents.d.ts`. +import { TeleportProps } from './components/Teleport' +import { SuspenseProps } from './components/Suspense' +import { KeepAliveProps } from './components/KeepAlive' +import { BaseTransitionProps } from './components/BaseTransition' +import { DefineComponent } from './apiDefineComponent' + +declare module '@vue/runtime-core' { + export interface GlobalComponents { + Teleport: DefineComponent<TeleportProps> + Suspense: DefineComponent<SuspenseProps> + KeepAlive: DefineComponent<KeepAliveProps> + BaseTransition: DefineComponent<BaseTransitionProps> + } +} + export { ReactiveEffectOptions, DebuggerEvent, @@ -192,7 +209,9 @@ export { ComponentInternalInstance, SetupContext, ComponentCustomProps, - AllowedComponentProps + AllowedComponentProps, + GlobalDirectives, + GlobalComponents } from './component' export { DefineComponent } from './apiDefineComponent' export { diff --git a/packages/runtime-core/src/scheduler.ts b/packages/runtime-core/src/scheduler.ts index 333125d578d..39b8d6030c7 100644 --- a/packages/runtime-core/src/scheduler.ts +++ b/packages/runtime-core/src/scheduler.ts @@ -128,10 +128,7 @@ function queueCb( if (!isArray(cb)) { if ( !activeQueue || - !activeQueue.includes( - cb, - cb.allowRecurse ? index + 1 : index - ) + !activeQueue.includes(cb, cb.allowRecurse ? index + 1 : index) ) { pendingQueue.push(cb) } diff --git a/packages/runtime-core/types/globalComponents.d.ts b/packages/runtime-core/types/globalComponents.d.ts new file mode 100644 index 00000000000..a4abd6d1fff --- /dev/null +++ b/packages/runtime-core/types/globalComponents.d.ts @@ -0,0 +1,11 @@ +// Note: this file is auto concatenated to the end of the bundled d.ts during +// build. + +declare module '@vue/runtime-core' { + export interface GlobalComponents { + Teleport: DefineComponent<TeleportProps> + Suspense: DefineComponent<SuspenseProps> + KeepAlive: DefineComponent<KeepAliveProps> + BaseTransition: DefineComponent<BaseTransitionProps> + } +} diff --git a/packages/runtime-dom/__tests__/directives/vOn.spec.ts b/packages/runtime-dom/__tests__/directives/vOn.spec.ts index 477620f6da6..fd64dda9d9a 100644 --- a/packages/runtime-dom/__tests__/directives/vOn.spec.ts +++ b/packages/runtime-dom/__tests__/directives/vOn.spec.ts @@ -41,7 +41,7 @@ describe('runtime-dom: v-on directive', () => { }) test('it should support key modifiers and system modifiers', () => { - const keyNames = ['ctrl', 'shift', 'meta', 'alt'] + const keyNames = ['ctrl', 'shift', 'meta', 'alt'] as const keyNames.forEach(keyName => { const el = document.createElement('div') diff --git a/packages/runtime-dom/src/directives/vModel.ts b/packages/runtime-dom/src/directives/vModel.ts index 11fd5376055..8d0677094f9 100644 --- a/packages/runtime-dom/src/directives/vModel.ts +++ b/packages/runtime-dom/src/directives/vModel.ts @@ -40,12 +40,17 @@ function trigger(el: HTMLElement, type: string) { el.dispatchEvent(e) } -type ModelDirective<T> = ObjectDirective<T & { _assign: AssignerFn }> +type ModelDirective<T, Modifiers extends string = string> = ObjectDirective< + T & { _assign: AssignerFn }, + any, + Modifiers +> // We are exporting the v-model runtime directly as vnode hooks so that it can // be tree-shaken in case v-model is never used. export const vModelText: ModelDirective< - HTMLInputElement | HTMLTextAreaElement + HTMLInputElement | HTMLTextAreaElement, + 'trim' | 'number' | 'lazy' > = { created(el, { modifiers: { lazy, trim, number } }, vnode) { el._assign = getModelAssigner(vnode) @@ -176,7 +181,7 @@ export const vModelRadio: ModelDirective<HTMLInputElement> = { } } -export const vModelSelect: ModelDirective<HTMLSelectElement> = { +export const vModelSelect: ModelDirective<HTMLSelectElement, 'number'> = { // <select multiple> value need to be deep traversed deep: true, created(el, { value, modifiers: { number } }, vnode) { @@ -327,3 +332,10 @@ if (__NODE_JS__) { } } } + +export type VModelDirective = + | typeof vModelText + | typeof vModelCheckbox + | typeof vModelSelect + | typeof vModelRadio + | typeof vModelDynamic diff --git a/packages/runtime-dom/src/directives/vOn.ts b/packages/runtime-dom/src/directives/vOn.ts index 06e596266cf..533b180ace9 100644 --- a/packages/runtime-dom/src/directives/vOn.ts +++ b/packages/runtime-dom/src/directives/vOn.ts @@ -3,36 +3,44 @@ import { DeprecationTypes, LegacyConfig, compatUtils, - ComponentInternalInstance + ComponentInternalInstance, + Directive } from '@vue/runtime-core' import { hyphenate, isArray } from '@vue/shared' -const systemModifiers = ['ctrl', 'shift', 'alt', 'meta'] - type KeyedEvent = KeyboardEvent | MouseEvent | TouchEvent -const modifierGuards: Record< - string, - (e: Event, modifiers: string[]) => void | boolean -> = { - stop: e => e.stopPropagation(), - prevent: e => e.preventDefault(), - self: e => e.target !== e.currentTarget, - ctrl: e => !(e as KeyedEvent).ctrlKey, - shift: e => !(e as KeyedEvent).shiftKey, - alt: e => !(e as KeyedEvent).altKey, - meta: e => !(e as KeyedEvent).metaKey, - left: e => 'button' in e && (e as MouseEvent).button !== 0, - middle: e => 'button' in e && (e as MouseEvent).button !== 1, - right: e => 'button' in e && (e as MouseEvent).button !== 2, - exact: (e, modifiers) => +type SystemModifiers = 'ctrl' | 'shift' | 'alt' | 'meta' +type CompatModifiers = keyof typeof keyNames + +export type VOnModifiers = SystemModifiers | ModifierGuards | CompatModifiers + +const systemModifiers: Array<SystemModifiers> = ['ctrl', 'shift', 'alt', 'meta'] + +const modifierGuards = { + stop: (e: Event) => e.stopPropagation(), + prevent: (e: Event) => e.preventDefault(), + self: (e: Event) => e.target !== e.currentTarget, + ctrl: (e: Event) => !(e as KeyedEvent).ctrlKey, + shift: (e: Event) => !(e as KeyedEvent).shiftKey, + alt: (e: Event) => !(e as KeyedEvent).altKey, + meta: (e: Event) => !(e as KeyedEvent).metaKey, + left: (e: Event) => 'button' in e && (e as MouseEvent).button !== 0, + middle: (e: Event) => 'button' in e && (e as MouseEvent).button !== 1, + right: (e: Event) => 'button' in e && (e as MouseEvent).button !== 2, + exact: (e: Event, modifiers: string[]) => systemModifiers.some(m => (e as any)[`${m}Key`] && !modifiers.includes(m)) } +type ModifierGuards = keyof typeof modifierGuards + /** * @private */ -export const withModifiers = (fn: Function, modifiers: string[]) => { +export const withModifiers = ( + fn: Function, + modifiers: Array<ModifierGuards | SystemModifiers> +) => { return (event: Event, ...args: unknown[]) => { for (let i = 0; i < modifiers.length; i++) { const guard = modifierGuards[modifiers[i]] @@ -44,7 +52,7 @@ export const withModifiers = (fn: Function, modifiers: string[]) => { // Kept for 2.x compat. // Note: IE11 compat for `spacebar` and `del` is removed for now. -const keyNames: Record<string, string | string[]> = { +const keyNames = { esc: 'escape', space: ' ', up: 'arrow-up', @@ -83,7 +91,11 @@ export const withKeys = (fn: Function, modifiers: string[]) => { } const eventKey = hyphenate(event.key) - if (modifiers.some(k => k === eventKey || keyNames[k] === eventKey)) { + if ( + modifiers.some( + k => k === eventKey || keyNames[k as CompatModifiers] === eventKey + ) + ) { return fn(event) } @@ -114,3 +126,5 @@ export const withKeys = (fn: Function, modifiers: string[]) => { } } } + +export type VOnDirective = Directive<any, any, VOnModifiers> diff --git a/packages/runtime-dom/src/index.ts b/packages/runtime-dom/src/index.ts index ed35e1293f8..ef1fd05aa91 100644 --- a/packages/runtime-dom/src/index.ts +++ b/packages/runtime-dom/src/index.ts @@ -9,6 +9,8 @@ import { App, RootHydrateFunction, isRuntimeOnly, + DefineComponent, + Directive, DeprecationTypes, compatUtils } from '@vue/runtime-core' @@ -16,6 +18,11 @@ import { nodeOps } from './nodeOps' import { patchProp } from './patchProp' // Importing from the compiler, will be tree-shaken in prod import { isFunction, isString, isHTMLTag, isSVGTag, extend } from '@vue/shared' +import { TransitionProps } from './components/Transition' +import { TransitionGroupProps } from './components/TransitionGroup' +import { vShow } from './directives/vShow' +import { VOnDirective } from './directives/vOn' +import { VModelDirective } from './directives/vModel' declare module '@vue/reactivity' { export interface RefUnwrapBailTypes { @@ -24,6 +31,24 @@ declare module '@vue/reactivity' { } } +declare module '@vue/runtime-core' { + interface GlobalComponents { + // Note: if updating this, also update `types/globalComponents.d.ts`. + Transition: DefineComponent<TransitionProps> + TransitionGroup: DefineComponent<TransitionGroupProps> + } + + interface GlobalDirectives { + // Note: if updating this, also update `types/globalDirectives.d.ts`. + vShow: typeof vShow + vOn: VOnDirective + vBind: VModelDirective + vIf: Directive<any, boolean> + VOnce: Directive + VSlot: Directive + } +} + const rendererOptions = extend({ patchProp }, nodeOps) // lazy create the renderer - this makes core renderer logic tree-shakable diff --git a/packages/runtime-dom/types/globalComponents.d.ts b/packages/runtime-dom/types/globalComponents.d.ts new file mode 100644 index 00000000000..28d1bfc87c4 --- /dev/null +++ b/packages/runtime-dom/types/globalComponents.d.ts @@ -0,0 +1,10 @@ +// Note: this file is auto concatenated to the end of the bundled d.ts during +// build. + +import { DefineComponent } from '@vue/runtime-core' +declare module '@vue/runtime-core' { + interface GlobalComponents { + Transition: DefineComponent<TransitionProps> + TransitionGroup: DefineComponent<TransitionGroupProps> + } +} diff --git a/packages/runtime-dom/types/globalDirectives.d.ts b/packages/runtime-dom/types/globalDirectives.d.ts new file mode 100644 index 00000000000..041d85e90fb --- /dev/null +++ b/packages/runtime-dom/types/globalDirectives.d.ts @@ -0,0 +1,8 @@ +// Note: this file is auto concatenated to the end of the bundled d.ts during +// build. + +declare module '@vue/runtime-core' { + interface GlobalDirectives { + vShow: typeof vShow + } +} diff --git a/packages/server-renderer/__tests__/render.spec.ts b/packages/server-renderer/__tests__/render.spec.ts index 1079a29a6c2..b40c4802004 100644 --- a/packages/server-renderer/__tests__/render.spec.ts +++ b/packages/server-renderer/__tests__/render.spec.ts @@ -856,11 +856,11 @@ function testRender(type: string, render: typeof renderToString) { await render( createApp({ components: { - A: { + A: defineComponent({ ssrRender(_ctx, _push) { _push(`<div>A</div>`) } - }, + }), B: { render: () => h('div', 'B') } diff --git a/packages/vue-compat/__tests__/instance.spec.ts b/packages/vue-compat/__tests__/instance.spec.ts index 93ceda93c54..08029fb8b43 100644 --- a/packages/vue-compat/__tests__/instance.spec.ts +++ b/packages/vue-compat/__tests__/instance.spec.ts @@ -239,7 +239,8 @@ test('INSTANCE_LISTENERS', () => { components: { child: { template: `<div/>`, - mounted() { + mounted(this: LegacyPublicInstance) { + // @ts-expect-error $listeners type: Record<string, Function | Function[]> listeners = this.$listeners } } @@ -263,7 +264,7 @@ describe('INSTANCE_SCOPED_SLOTS', () => { components: { child: { compatConfig: { RENDER_FUNCTION: false }, - render() { + render(this: LegacyPublicInstance) { slots = this.$scopedSlots } } @@ -290,11 +291,14 @@ describe('INSTANCE_SCOPED_SLOTS', () => { components: { child: { compatConfig: { RENDER_FUNCTION: false }, - render() { + render(this: LegacyPublicInstance) { normalSlots = this.$slots scopedSlots = this.$scopedSlots } } + }, + render() { + this.$ } }).$mount() diff --git a/test-dts/component.test-d.ts b/test-dts/component.test-d.ts index 93c3ea4109c..75e8556cc00 100644 --- a/test-dts/component.test-d.ts +++ b/test-dts/component.test-d.ts @@ -459,6 +459,21 @@ describe('functional', () => { expectType<number>(props.foo) }) + + describe('emit', () => { + const MyComponent: FunctionalComponent< + { foo: number }, + { ev: (a: string) => void } + > = (_, { emit }) => { + emit('ev', 'a') + // @ts-expect-error invalid argument + emit('ev', 12) + // @ts-expect-error invalid event + emit('o', '1') + } + + defineComponent(MyComponent) + }) }) declare type VueClass<Props = {}> = { diff --git a/test-dts/componentTypeExtensions.test-d.tsx b/test-dts/componentTypeExtensions.test-d.tsx index 32a72f844e6..46da0724f9c 100644 --- a/test-dts/componentTypeExtensions.test-d.tsx +++ b/test-dts/componentTypeExtensions.test-d.tsx @@ -1,10 +1,24 @@ -import { defineComponent, expectError, expectType } from './index' +import { + defineComponent, + DefineComponent, + Directive, + expectError, + expectType +} from './index' declare module '@vue/runtime-core' { interface ComponentCustomOptions { test?(n: number): void } + interface GlobalDirectives { + test: Directive + } + + interface GlobalComponents { + RouterView: DefineComponent<{}> + } + interface ComponentCustomProperties { state: 'stopped' | 'running' } @@ -41,6 +55,8 @@ export const Custom = defineComponent({ } }) +expectType<Directive>(Custom.directives!.test) +expectType<DefineComponent<{}>>(Custom.components!.RouterView) expectType<JSX.Element>(<Custom baz={1} />) expectType<JSX.Element>(<Custom custom={1} baz={1} />) expectType<JSX.Element>(<Custom bar="bar" baz={1} />) diff --git a/test-dts/defineComponent.test-d.tsx b/test-dts/defineComponent.test-d.tsx index 9569b585f50..7de755b18eb 100644 --- a/test-dts/defineComponent.test-d.tsx +++ b/test-dts/defineComponent.test-d.tsx @@ -11,8 +11,12 @@ import { ComponentPublicInstance, ComponentOptions, SetupContext, - IsUnion, - h + h, + Directive, + KeepAliveProps, + TransitionProps, + vShow, + IsUnion } from './index' describe('with object props', () => { @@ -551,7 +555,7 @@ describe('with mixins', () => { return {} }, - setup(props) { + setup(props, _) { expectType<string>(props.z) // props expectType<((...args: any[]) => any) | undefined>(props.onClick) @@ -697,6 +701,8 @@ describe('with extends', () => { describe('extends with mixins', () => { const Mixin = defineComponent({ emits: ['bar'], + // emits: { bar: (a: string) => true }, + props: { mP1: { type: String, @@ -709,6 +715,7 @@ describe('extends with mixins', () => { } }, data() { + this.$props.onBar return { a: 1 } @@ -734,14 +741,31 @@ describe('extends with mixins', () => { }, computed: { c(): number { + this.$XXX return this.p2 + this.b } } }) + const OtherMixin = defineComponent({ + emits: { + test: (a: boolean) => true + }, + + render() { + this.$emit('test', true) + + // @ts-expect-error + this.$emit('test', 0) + + // @ts-expect-error + this.$emit('') + } + }) + const MyComponent = defineComponent({ extends: Base, - mixins: [Mixin], - emits: ['click'], + mixins: [Mixin, OtherMixin], + emits: ['click', 'lock'], props: { // required should make property non-void z: { @@ -751,6 +775,19 @@ describe('extends with mixins', () => { }, render() { const props = this.$props + + //@ts-expect-error + this.$emit('') + + this.$emit('bar') + this.$emit('foo') + this.$emit('click') + + this.$emit('test', true) + + //@ts-expect-error + this.$emit('test', 0) + // props expectType<((...args: any[]) => any) | undefined>(props.onClick) // from Mixin @@ -1054,7 +1091,8 @@ describe('extract instance type', () => { required: true }, c: Number - } + }, + setup(props) {} }) const compA = {} as InstanceType<typeof CompA> @@ -1115,6 +1153,148 @@ describe('async setup', () => { vm.a = 2 }) +describe('typed slots', () => { + const Comp = defineComponent({ + slots: { + test: null, + item: Object as () => { item: { value: number }; i: number } + }, + + setup(_, { slots }) { + slots.test!() + slots.item!({ + i: 22, + item: { + value: 22 + } + }) + // @ts-expect-error missing item prop + expectError(slots.item!({ i: 22 })) + } + }) + + h( + // @ts-expect-error the provided slots are not valid + Comp, + {}, + { + // @ts-expect-error no argument expected + test(x) {}, + item(s) { + expectType<number>(s.i) + expectType<{ value: number }>(s.item) + return {} as any + } + } + ) +}) + +describe('typed slots just type', () => { + const Comp = defineComponent({ + slots: {} as { + test: null + item: { item: { value: number }; i: number } + }, + + setup(_, { slots }) { + slots.test!() + slots.item!({ + i: 22, + item: { + value: 22 + } + }) + // @ts-expect-error missing item prop + expectError(slots.item!({ i: 22 })) + } + }) + + h( + // @ts-expect-error the provided slots are not valid + Comp, + {}, + { + // @ts-expect-error no argument expected + test(x) {}, + item(s) { + expectType<number>(s.i) + expectType<{ value: number }>(s.item) + + return {} as any + } + } + ) +}) + +// #3367 expose components types +describe('expose component types', () => { + const child = defineComponent({ + props: { + a: String + } + }) + + const parent = defineComponent({ + components: { + child + } + }) + + expectType<typeof child>(parent.components!.child) + + // global components + expectType<KeepAliveProps>(new parent.components!.KeepAlive().$props) + expectType<KeepAliveProps>(new child.components!.KeepAlive().$props) + + // runtime-dom components + expectType<TransitionProps>(new parent.components!.Transition().$props) + expectType<TransitionProps>(new parent.components!.Transition().$props) +}) + +describe('directive typing', () => { + const customDirective: Directive = { + created(el) {} + } + + const comp = defineComponent({ + props: { + a: String + }, + directives: { + customDirective + } + }) + + expectType<typeof customDirective>(comp.directives!.customDirective) + + // global directive + expectType<typeof vShow>(comp.directives!.vShow) +}) + +describe('expose typing', () => { + const Comp = defineComponent({ + expose: ['a', 'b'], + props: { + some: String + }, + data() { + return { a: 1, b: '2', c: 1 } + } + }) + + expect<Array<'a' | 'b'>>(Comp.expose!) + + const vm = new Comp() + // internal should still be exposed + vm.$props + + expectType<number>(vm.a) + expectType<string>(vm.b) + + // @ts-expect-error shouldn't be exposed + vm.c +}) + // check if defineComponent can be exported export default { // function components diff --git a/test-dts/directives.test-d.ts b/test-dts/directives.test-d.ts new file mode 100644 index 00000000000..700c9727292 --- /dev/null +++ b/test-dts/directives.test-d.ts @@ -0,0 +1,58 @@ +import { ObjectDirective } from '@vue/runtime-core' +import { Directive, expectError, expectType, vModelText } from './index' + +type ExtractBinding<T> = T extends ( + el: any, + binding: infer B, + vnode: any, + prev: any +) => any + ? B + : never + +declare function testDirective< + Value, + Modifiers extends string = string, + Arg extends string = string +>(): ExtractBinding<Directive<any, Value, Modifiers, Arg>> + +describe('vmodel', () => { + expectType<ObjectDirective<any, any, 'trim' | 'number' | 'lazy', string>>( + vModelText + ) + // @ts-expect-error + expectType<ObjectDirective<any, any, 'trim' | 'number', string>>(vModelText) +}) + +describe('custom', () => { + expectType<{ + value: number + oldValue: number | null + arg?: 'Arg' + modifiers: Record<'a' | 'b', boolean> + }>(testDirective<number, 'a' | 'b', 'Arg'>()) + + expectError<{ + value: number + oldValue: number | null + arg?: 'Arg' + modifiers: Record<'a' | 'b', boolean> + // @ts-expect-error + }>(testDirective<number, 'a', 'Arg'>()) + + expectType<{ + value: number + oldValue: number | null + arg?: 'Arg' + modifiers: Record<'a' | 'b', boolean> + // @ts-expect-error + }>(testDirective<number, 'a' | 'b', 'Argx'>()) + + expectType<{ + value: number + oldValue: number | null + arg?: 'Arg' + modifiers: Record<'a' | 'b', boolean> + // @ts-expect-error + }>(testDirective<string, 'a' | 'b', 'Arg'>()) +}) diff --git a/test-dts/functionalComponent.test-d.tsx b/test-dts/functionalComponent.test-d.tsx index fdcf346fb70..5f650ff3d37 100644 --- a/test-dts/functionalComponent.test-d.tsx +++ b/test-dts/functionalComponent.test-d.tsx @@ -29,12 +29,19 @@ const Bar: FunctionalComponent< expectType<number>(props.foo) emit('update', 123) + + emit('update:foo', 123) + // @ts-expect-error expectError(emit('nope')) // @ts-expect-error expectError(emit('update')) // @ts-expect-error expectError(emit('update', 'nope')) + // @ts-expect-error + expectError(emit('update:foo')) + // @ts-expect-error + expectError(emit('update:foo', 'nope')) } // assigning runtime options diff --git a/test-dts/h.test-d.ts b/test-dts/h.test-d.ts index c71b54a2aa8..f5bfd20bbbd 100644 --- a/test-dts/h.test-d.ts +++ b/test-dts/h.test-d.ts @@ -9,7 +9,9 @@ import { Component, expectError, expectAssignable, - resolveComponent + FunctionalComponent, + resolveComponent, + expectType } from './index' describe('h inference w/ element', () => { @@ -70,12 +72,29 @@ describe('h inference w/ functional component', () => { const Func = (_props: { foo: string; bar?: number }) => '' h(Func, { foo: 'hello' }) h(Func, { foo: 'hello', bar: 123 }) + + h(Func, { + foo: '', + 'onUpdate:bar'(v) { + expectType<number | undefined>(v) + }, + 'onUpdate:foo'(v) { + expectType<string>(v) + } + }) + // @ts-expect-error expectError(h(Func, { foo: 123 })) // @ts-expect-error expectError(h(Func, {})) // @ts-expect-error expectError(h(Func, { bar: 123 })) + + const Func2: FunctionalComponent<{}, ['foo', 'bar']> = () => {} + h(Func2, { onFoo() {}, onBar() {} }) + + // @ts-expect-error + h(Func2, { onFoo: 1 }) }) describe('h support w/ plain object component', () => { @@ -84,9 +103,36 @@ describe('h support w/ plain object component', () => { foo: String } } - h(Foo, { foo: 'ok' }) - h(Foo, { foo: 'ok', class: 'extra' }) + h(Foo, { + foo: 'ok', + 'onUpdate:foo'(v) { + expectType<string>(v) + } + }) + h(Foo, { + foo: 'ok', + class: 'extra', + + 'onUpdate:foo'(v) { + expectType<string>(v) + } + }) + // no inference in this case + h( + { + emits: { + foo(a: number) { + return true + } + } + }, + { + onFoo(s) { + expectType<number>(s) + } + } + ) }) describe('h inference w/ defineComponent', () => { @@ -104,12 +150,61 @@ describe('h inference w/ defineComponent', () => { h(Foo, { bar: 1, foo: 'ok' }) // should allow extraneous props (attrs fallthrough) h(Foo, { bar: 1, foo: 'ok', class: 'extra' }) + + // should support model + h(Foo, { + bar: 1, + 'onUpdate:bar'(v) { + expectType<number>(v) + }, + 'onUpdate:foo'(v) { + expectType<string | undefined>(v) + } + }) + // @ts-expect-error should fail on missing required prop expectError(h(Foo, {})) // @ts-expect-error expectError(h(Foo, { foo: 'ok' })) // @ts-expect-error should fail on wrong type expectError(h(Foo, { bar: 1, foo: 1 })) + + const FooEmit = defineComponent({ + props: { + foo: String + }, + emits: { + foo(a: number) { + return true + } + } + }) + + h(FooEmit, { + onFoo(a) { + expectType<number>(a) + } + }) + + const BarPropEmit = defineComponent({ + props: { + bar: Number + }, + emits: { + bar(a: number) { + return true + } + } + }) + + h(BarPropEmit, { + onBar(a) { + expectType<number>(a) + }, + 'onUpdate:bar'(a) { + expectType<number | undefined>(a) + } + }) }) // describe('h inference w/ defineComponent + optional props', () => { diff --git a/test-dts/index.d.ts b/test-dts/index.d.ts index 3d8d288fa6d..6d9d88eaf45 100644 --- a/test-dts/index.d.ts +++ b/test-dts/index.d.ts @@ -9,8 +9,8 @@ export function expectType<T>(value: T): void export function expectError<T>(value: T): void export function expectAssignable<T, T2 extends T = T>(value: T2): void -export type IsUnion<T, U extends T = T> = (T extends any - ? (U extends T ? false : true) - : never) extends false +export type IsUnion<T, U extends T = T> = ( + T extends any ? (U extends T ? false : true) : never +) extends false ? false : true diff --git a/test-dts/tsx.test-d.tsx b/test-dts/tsx.test-d.tsx index ce1aec82043..74b1c1bdb89 100644 --- a/test-dts/tsx.test-d.tsx +++ b/test-dts/tsx.test-d.tsx @@ -1,4 +1,5 @@ // TSX w/ defineComponent is tested in defineComponent.test-d.tsx +import { betterDefineComponent } from 'packages/runtime-core/src/apiDefineComponent' import { KeepAlive, Suspense, @@ -65,3 +66,7 @@ expectType<JSX.Element>( ) // @ts-expect-error expectError(<Suspense onResolve={123} />) + +const EmptyComponent = betterDefineComponent({ props: {} }) + +;<EmptyComponent /> diff --git a/yarn.lock b/yarn.lock index 53dc72e8960..1dde7ff3c86 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7242,7 +7242,12 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@^4.2.2, typescript@~4.3.5: +typescript@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.2.tgz#6d618640d430e3569a1dfb44f7d7e600ced3ee86" + integrity sha512-gzP+t5W4hdy4c+68bfcv0t400HVJMMd2+H9B7gae1nQlBzCqvrXX+6GL/b3GAgyTH966pzrZ70/fRjwAtZksSQ== + +typescript@~4.3.5: version "4.3.5" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.5.tgz#4d1c37cc16e893973c45a06886b7113234f119f4" integrity sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==