diff --git a/packages/runtime-core/__tests__/apiCreateComponent.spec.tsx b/packages/runtime-core/__tests__/apiCreateComponent.spec.tsx index f1ebdf54..978cd32a 100644 --- a/packages/runtime-core/__tests__/apiCreateComponent.spec.tsx +++ b/packages/runtime-core/__tests__/apiCreateComponent.spec.tsx @@ -1,4 +1,4 @@ -import { createComponent } from '../src/component' +import { createComponent } from '../src/apiCreateComponent' import { ref } from '@vue/reactivity' import { PropType } from '../src/componentProps' import { h } from '../src/h' diff --git a/packages/runtime-core/src/apiApp.ts b/packages/runtime-core/src/apiApp.ts index cd410c4e..94a05bb4 100644 --- a/packages/runtime-core/src/apiApp.ts +++ b/packages/runtime-core/src/apiApp.ts @@ -1,10 +1,6 @@ -import { - ComponentOptions, - Component, - ComponentRenderProxy, - Data, - ComponentInstance -} from './component' +import { Component, Data, ComponentInstance } from './component' +import { ComponentOptions } from './componentOptions' +import { ComponentRenderProxy } from './componentProxy' import { Directive } from './directives' import { HostNode, RootRenderFunction } from './createRenderer' import { InjectionKey } from './apiInject' diff --git a/packages/runtime-core/src/apiCreateComponent.ts b/packages/runtime-core/src/apiCreateComponent.ts new file mode 100644 index 00000000..098452e1 --- /dev/null +++ b/packages/runtime-core/src/apiCreateComponent.ts @@ -0,0 +1,81 @@ +import { + ComputedOptions, + MethodOptions, + ComponentOptionsWithoutProps, + ComponentOptionsWithArrayProps, + ComponentOptionsWithProps +} from './componentOptions' +import { SetupContext } from './component' +import { VNodeChild } from './vnode' +import { ComponentRenderProxy } from './componentProxy' +import { ExtractPropTypes } from './componentProps' +import { isFunction } from '@vue/shared' + +// overload 1: direct setup function +// (uses user defined props interface) +export function createComponent( + setup: (props: Props, ctx: SetupContext) => object | (() => VNodeChild) +): (props: Props) => any + +// overload 2: object format with no props +// (uses user defined props interface) +// return type is for Vetur and TSX support +export function createComponent< + Props, + RawBindings, + D, + C extends ComputedOptions = {}, + M extends MethodOptions = {} +>( + options: ComponentOptionsWithoutProps +): { + new (): ComponentRenderProxy +} + +// overload 3: object format with array props declaration +// props inferred as { [key in PropNames]?: unknown } +// return type is for Vetur and TSX support +export function createComponent< + PropNames extends string, + RawBindings, + D, + C extends ComputedOptions = {}, + M extends MethodOptions = {} +>( + options: ComponentOptionsWithArrayProps +): { + new (): ComponentRenderProxy< + { [key in PropNames]?: unknown }, + RawBindings, + D, + C, + M + > +} + +// overload 4: object format with object props declaration +// see `ExtractPropTypes` in ./componentProps.ts +export function createComponent< + PropsOptions, + RawBindings, + D, + C extends ComputedOptions = {}, + M extends MethodOptions = {} +>( + options: ComponentOptionsWithProps +): { + // for Vetur and TSX support + new (): ComponentRenderProxy< + ExtractPropTypes, + RawBindings, + D, + C, + M, + ExtractPropTypes + > +} + +// implementation, close to no-op +export function createComponent(options: any) { + return isFunction(options) ? { setup: options } : (options as any) +} diff --git a/packages/runtime-core/src/apiLifecycle.ts b/packages/runtime-core/src/apiLifecycle.ts index a9f09180..d2644b63 100644 --- a/packages/runtime-core/src/apiLifecycle.ts +++ b/packages/runtime-core/src/apiLifecycle.ts @@ -2,9 +2,9 @@ import { ComponentInstance, LifecycleHooks, currentInstance, - setCurrentInstance, - ComponentRenderProxy + setCurrentInstance } from './component' +import { ComponentRenderProxy } from './componentProxy' import { callWithAsyncErrorHandling, ErrorTypeStrings } from './errorHandling' import { warn } from './warning' import { capitalize } from '@vue/shared' diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index 3583d70f..a954efad 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -1,15 +1,7 @@ import { VNode, normalizeVNode, VNodeChild, createVNode, Empty } from './vnode' -import { ReactiveEffect, UnwrapRef, reactive, readonly } from '@vue/reactivity' -import { - EMPTY_OBJ, - isFunction, - capitalize, - NOOP, - isArray, - isObject -} from '@vue/shared' -import { RenderProxyHandlers } from './componentProxy' -import { ComponentPropsOptions, ExtractPropTypes } from './componentProps' +import { ReactiveEffect, reactive, readonly } from '@vue/reactivity' +import { RenderProxyHandlers, ComponentRenderProxy } from './componentProxy' +import { ComponentPropsOptions } from './componentProps' import { Slots } from './componentSlots' import { PatchFlags } from './patchFlags' import { ShapeFlags } from './shapeFlags' @@ -24,100 +16,20 @@ import { AppContext, createAppContext } from './apiApp' import { Directive } from './directives' import { applyOptions, - LegacyOptions, resolveAsset, - ComputedOptions, - MethodOptions, - ExtracComputedReturns -} from './apiOptions' + ComponentOptions +} from './componentOptions' +import { + EMPTY_OBJ, + isFunction, + capitalize, + NOOP, + isArray, + isObject +} from '@vue/shared' export type Data = { [key: string]: unknown } -// public properties exposed on the proxy, which is used as the render context -// in templates (as `this` in the render option) -export type ComponentRenderProxy< - P = {}, - B = {}, - D = {}, - C = {}, - M = {}, - PublicProps = P -> = { - $data: D - $props: PublicProps - $attrs: Data - $refs: Data - $slots: Data - $root: ComponentInstance | null - $parent: ComponentInstance | null - $emit: (event: string, ...args: unknown[]) => void -} & P & - UnwrapRef & - D & - ExtracComputedReturns & - M - -interface ComponentOptionsBase< - Props, - RawBindings, - D, - C extends ComputedOptions, - M extends MethodOptions -> extends LegacyOptions { - setup?: ( - this: null, - props: Props, - ctx: SetupContext - ) => RawBindings | (() => VNodeChild) | void - name?: string - template?: string - // 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?: Record - directives?: Record -} - -export type ComponentOptionsWithoutProps< - Props = {}, - RawBindings = {}, - D = {}, - C extends ComputedOptions = {}, - M extends MethodOptions = {} -> = ComponentOptionsBase & { - props?: undefined -} & ThisType> - -export type ComponentOptionsWithArrayProps< - PropNames extends string = string, - RawBindings = {}, - D = {}, - C extends ComputedOptions = {}, - M extends MethodOptions = {}, - Props = { [key in PropNames]?: unknown } -> = ComponentOptionsBase & { - props: PropNames[] -} & ThisType> - -export type ComponentOptionsWithProps< - PropsOptions = ComponentPropsOptions, - RawBindings = {}, - D = {}, - C extends ComputedOptions = {}, - M extends MethodOptions = {}, - Props = ExtractPropTypes -> = ComponentOptionsBase & { - props: PropsOptions -} & ThisType> - -export type ComponentOptions = - | ComponentOptionsWithoutProps - | ComponentOptionsWithProps - | ComponentOptionsWithArrayProps - export interface FunctionalComponent

{ (props: P, ctx: SetupContext): VNodeChild props?: ComponentPropsOptions

@@ -146,7 +58,7 @@ export const enum LifecycleHooks { type Emit = ((event: string, ...args: unknown[]) => void) -interface SetupContext { +export interface SetupContext { attrs: Data slots: Slots emit: Emit @@ -201,72 +113,6 @@ export interface ComponentInstance { [LifecycleHooks.ERROR_CAPTURED]: LifecycleHook } -// createComponent -// overload 1: direct setup function -// (uses user defined props interface) -export function createComponent( - setup: (props: Props, ctx: SetupContext) => object | (() => VNodeChild) -): (props: Props) => any -// overload 2: object format with no props -// (uses user defined props interface) -// return type is for Vetur and TSX support -export function createComponent< - Props, - RawBindings, - D, - C extends ComputedOptions = {}, - M extends MethodOptions = {} ->( - options: ComponentOptionsWithoutProps -): { - new (): ComponentRenderProxy -} -// overload 3: object format with array props declaration -// props inferred as { [key in PropNames]?: unknown } -// return type is for Vetur and TSX support -export function createComponent< - PropNames extends string, - RawBindings, - D, - C extends ComputedOptions = {}, - M extends MethodOptions = {} ->( - options: ComponentOptionsWithArrayProps -): { - new (): ComponentRenderProxy< - { [key in PropNames]?: unknown }, - RawBindings, - D, - C, - M - > -} -// overload 4: object format with object props declaration -// see `ExtractPropTypes` in ./componentProps.ts -export function createComponent< - PropsOptions, - RawBindings, - D, - C extends ComputedOptions = {}, - M extends MethodOptions = {} ->( - options: ComponentOptionsWithProps -): { - // for Vetur and TSX support - new (): ComponentRenderProxy< - ExtractPropTypes, - RawBindings, - D, - C, - M, - ExtractPropTypes - > -} -// implementation, close to no-op -export function createComponent(options: any) { - return isFunction(options) ? { setup: options } : (options as any) -} - const emptyAppContext = createAppContext() export function createComponentInstance( diff --git a/packages/runtime-core/src/apiOptions.ts b/packages/runtime-core/src/componentOptions.ts similarity index 74% rename from packages/runtime-core/src/apiOptions.ts rename to packages/runtime-core/src/componentOptions.ts index 3863d64c..85cdb26c 100644 --- a/packages/runtime-core/src/apiOptions.ts +++ b/packages/runtime-core/src/componentOptions.ts @@ -1,10 +1,10 @@ import { ComponentInstance, Data, - ComponentOptions, currentRenderingInstance, currentInstance, - ComponentRenderProxy + Component, + SetupContext } from './component' import { isFunction, @@ -31,6 +31,71 @@ import { } from './apiLifecycle' import { DebuggerEvent, reactive } from '@vue/reactivity' import { warn } from './warning' +import { ComponentPropsOptions, ExtractPropTypes } from './componentProps' +import { Directive } from './directives' +import { VNodeChild } from './vnode' +import { ComponentRenderProxy } from './componentProxy' + +interface ComponentOptionsBase< + Props, + RawBindings, + D, + C extends ComputedOptions, + M extends MethodOptions +> extends LegacyOptions { + setup?: ( + this: null, + props: Props, + ctx: SetupContext + ) => RawBindings | (() => VNodeChild) | void + name?: string + template?: string + // 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?: Record + directives?: Record +} + +export type ComponentOptionsWithoutProps< + Props = {}, + RawBindings = {}, + D = {}, + C extends ComputedOptions = {}, + M extends MethodOptions = {} +> = ComponentOptionsBase & { + props?: undefined +} & ThisType> + +export type ComponentOptionsWithArrayProps< + PropNames extends string = string, + RawBindings = {}, + D = {}, + C extends ComputedOptions = {}, + M extends MethodOptions = {}, + Props = { [key in PropNames]?: unknown } +> = ComponentOptionsBase & { + props: PropNames[] +} & ThisType> + +export type ComponentOptionsWithProps< + PropsOptions = ComponentPropsOptions, + RawBindings = {}, + D = {}, + C extends ComputedOptions = {}, + M extends MethodOptions = {}, + Props = ExtractPropTypes +> = ComponentOptionsBase & { + props: PropsOptions +} & ThisType> + +export type ComponentOptions = + | ComponentOptionsWithoutProps + | ComponentOptionsWithProps + | ComponentOptionsWithArrayProps // TODO legacy component definition also supports constructors with .options type LegacyComponent = ComponentOptions @@ -60,6 +125,18 @@ type WatchHandler = ( onCleanup: CleanupRegistrator ) => void +type ComponentWatchOptions = Record< + string, + string | WatchHandler | { handler: WatchHandler } & WatchOptions +> + +type ComponentInjectOptions = + | string[] + | Record< + string | symbol, + string | symbol | { from: string | symbol; default?: any } + > + // TODO type inference for these options export interface LegacyOptions< Props, @@ -71,21 +148,16 @@ export interface LegacyOptions< el?: any // state + // Limitation: we cannot expose RawBindings on the `this` context for data + // since that leads to some sort of circular inference and breaks ThisType + // for the entire component. data?: D | ((this: ComponentRenderProxy) => D) computed?: C methods?: M // TODO watch array - watch?: Record< - string, - string | WatchHandler | { handler: WatchHandler } & WatchOptions - > + watch?: ComponentWatchOptions provide?: Data | Function - inject?: - | string[] - | Record< - string | symbol, - string | symbol | { from: string | symbol; default?: any } - > + inject?: ComponentInjectOptions // composition mixins?: LegacyComponent[] diff --git a/packages/runtime-core/src/componentProxy.ts b/packages/runtime-core/src/componentProxy.ts index 716df577..b9580623 100644 --- a/packages/runtime-core/src/componentProxy.ts +++ b/packages/runtime-core/src/componentProxy.ts @@ -1,7 +1,33 @@ -import { ComponentInstance } from './component' +import { ComponentInstance, Data } from './component' import { nextTick } from './scheduler' import { instanceWatch } from './apiWatch' import { EMPTY_OBJ, hasOwn } from '@vue/shared' +import { ExtracComputedReturns } from './componentOptions' +import { UnwrapRef } from '@vue/reactivity' + +// public properties exposed on the proxy, which is used as the render context +// in templates (as `this` in the render option) +export type ComponentRenderProxy< + P = {}, + B = {}, + D = {}, + C = {}, + M = {}, + PublicProps = P +> = { + $data: D + $props: PublicProps + $attrs: Data + $refs: Data + $slots: Data + $root: ComponentInstance | null + $parent: ComponentInstance | null + $emit: (event: string, ...args: unknown[]) => void +} & P & + UnwrapRef & + D & + ExtracComputedReturns & + M export const RenderProxyHandlers = { get(target: ComponentInstance, key: string) { diff --git a/packages/runtime-core/src/directives.ts b/packages/runtime-core/src/directives.ts index d18afeac..dd3f542a 100644 --- a/packages/runtime-core/src/directives.ts +++ b/packages/runtime-core/src/directives.ts @@ -14,14 +14,11 @@ return applyDirectives(h(comp), [ import { VNode, cloneVNode } from './vnode' import { extend, isArray, isFunction } from '@vue/shared' import { warn } from './warning' -import { - ComponentInstance, - currentRenderingInstance, - ComponentRenderProxy -} from './component' +import { ComponentInstance, currentRenderingInstance } from './component' import { callWithAsyncErrorHandling, ErrorTypes } from './errorHandling' import { HostNode } from './createRenderer' -import { resolveAsset } from './apiOptions' +import { resolveAsset } from './componentOptions' +import { ComponentRenderProxy } from './componentProxy' export interface DirectiveBinding { instance: ComponentRenderProxy | null diff --git a/packages/runtime-core/src/h.ts b/packages/runtime-core/src/h.ts index ca94b7c7..5ebb485d 100644 --- a/packages/runtime-core/src/h.ts +++ b/packages/runtime-core/src/h.ts @@ -9,13 +9,13 @@ import { import { isObject, isArray } from '@vue/shared' import { Ref } from '@vue/reactivity' import { RawSlots } from './componentSlots' +import { FunctionalComponent } from './component' import { - FunctionalComponent, ComponentOptionsWithoutProps, ComponentOptionsWithArrayProps, ComponentOptionsWithProps, ComponentOptions -} from './component' +} from './componentOptions' import { ExtractPropTypes } from './componentProps' // `h` is a more user-friendly version of `createVNode` that allows omitting the diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index 59fc13bc..5b794cf3 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -1,6 +1,6 @@ // Public API ------------------------------------------------------------------ -export { createComponent } from './component' +export { createComponent } from './apiCreateComponent' export { nextTick } from './scheduler' export * from './apiReactivity' export * from './apiWatch'