Skip to content

Commit

Permalink
feat(types): further improve Vue type declarations for canonical usage (
Browse files Browse the repository at this point in the history
  • Loading branch information
HerringtonDarkholme authored and yyx990803 committed Oct 6, 2017
1 parent 64e3943 commit db138e2
Show file tree
Hide file tree
Showing 10 changed files with 394 additions and 157 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
"eslint": "^3.0.0",
"eslint-loader": "^1.7.1",
"eslint-plugin-flowtype": "^2.34.0",
"eslint-plugin-jasmine": "^2.2.0",
"eslint-plugin-jasmine": "^2.8.4",
"eslint-plugin-vue-libs": "^1.2.0",
"file-loader": "^0.11.2",
"flow-bin": "^0.54.0",
Expand Down Expand Up @@ -119,7 +119,7 @@
"selenium-server": "^2.53.1",
"serialize-javascript": "^1.3.0",
"shelljs": "^0.7.8",
"typescript": "^2.3.4",
"typescript": "^2.5.2",
"uglify-js": "^3.0.15",
"webpack": "^2.6.1",
"weex-js-runtime": "^0.22.0"
Expand Down
63 changes: 31 additions & 32 deletions types/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,36 @@
import * as V from "./vue";
import * as Options from "./options";
import * as Plugin from "./plugin";
import * as VNode from "./vnode";
import { Vue } from "./vue";

// `Vue` in `export = Vue` must be a namespace
// All available types are exported via this namespace
declare namespace Vue {
export type CreateElement = V.CreateElement;
export default Vue;

export type Component = Options.Component;
export type AsyncComponent = Options.AsyncComponent;
export type ComponentOptions<V extends Vue> = Options.ComponentOptions<V>;
export type FunctionalComponentOptions = Options.FunctionalComponentOptions;
export type RenderContext = Options.RenderContext;
export type PropOptions = Options.PropOptions;
export type ComputedOptions<V extends Vue> = Options.ComputedOptions<V>;
export type WatchHandler<V extends Vue> = Options.WatchHandler<V, any>;
export type WatchOptions = Options.WatchOptions;
export type DirectiveFunction = Options.DirectiveFunction;
export type DirectiveOptions = Options.DirectiveOptions;
export {
CreateElement
} from "./vue";

export type PluginFunction<T> = Plugin.PluginFunction<T>;
export type PluginObject<T> = Plugin.PluginObject<T>;
export {
Component,
AsyncComponent,
ComponentOptions,
FunctionalComponentOptions,
RenderContext,
PropOptions,
ComputedOptions,
WatchHandler,
WatchOptions,
WatchOptionsWithHandler,
DirectiveFunction,
DirectiveOptions
} from "./options";

export type VNodeChildren = VNode.VNodeChildren;
export type VNodeChildrenArrayContents = VNode.VNodeChildrenArrayContents;
export type VNode = VNode.VNode;
export type VNodeComponentOptions = VNode.VNodeComponentOptions;
export type VNodeData = VNode.VNodeData;
export type VNodeDirective = VNode.VNodeDirective;
}
export {
PluginFunction,
PluginObject
} from "./plugin";

// TS cannot merge imported class with namespace, declare a subclass to bypass
declare class Vue extends V.Vue {}

export = Vue;
export {
VNodeChildren,
VNodeChildrenArrayContents,
VNode,
VNodeComponentOptions,
VNodeData,
VNodeDirective
} from "./vnode";
129 changes: 92 additions & 37 deletions types/options.d.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,88 @@
import { Vue, CreateElement } from "./vue";
import { Vue, CreateElement, CombinedVueInstance } from "./vue";
import { VNode, VNodeData, VNodeDirective } from "./vnode";

type Constructor = {
new (...args: any[]): any;
}

export type Component = typeof Vue | ComponentOptions<Vue> | FunctionalComponentOptions;
// we don't support infer props in async component
export type Component<Data=DefaultData<Vue>, Methods=DefaultMethods<Vue>, Computed=DefaultComputed, Props=DefaultProps> =
| typeof Vue
| FunctionalComponentOptions<Props>
| ThisTypedComponentOptionsWithArrayProps<Vue, Data, Methods, Computed, keyof Props>
| ThisTypedComponentOptionsWithRecordProps<Vue, Data, Methods, Computed, Props>;

interface EsModuleComponent {
default: Component
}

export type AsyncComponent = (
resolve: (component: Component) => void,
export type AsyncComponent<Data=DefaultData<Vue>, Methods=DefaultMethods<Vue>, Computed=DefaultComputed, Props=DefaultProps> = (
resolve: (component: Component<Data, Methods, Computed, Props>) => void,
reject: (reason?: any) => void
) => Promise<Component | EsModuleComponent> | Component | void;
) => Promise<Component | EsModuleComponent> | void;

/**
* When the `Computed` type parameter on `ComponentOptions` is inferred,
* it should have a property with the return type of every get-accessor.
* Since there isn't a way to query for the return type of a function, we allow TypeScript
* to infer from the shape of `Accessors<Computed>` and work backwards.
*/
export type Accessors<T> = {
[K in keyof T]: (() => T[K]) | ComputedOptions<T[K]>
}

export interface ComponentOptions<V extends Vue> {
data?: Object | ((this: V) => Object);
props?: string[] | { [key: string]: PropOptions | Constructor | Constructor[] };
/**
* This type should be used when an array of strings is used for a component's `props` value.
*/
export type ThisTypedComponentOptionsWithArrayProps<V extends Vue, Data, Methods, Computed, PropNames extends string> =
object &
ComponentOptions<V, Data | ((this: Readonly<Record<PropNames, any>> & V) => Data), Methods, Computed, PropNames[]> &
ThisType<CombinedVueInstance<V, Data, Methods, Computed, Readonly<Record<PropNames, any>>>>;

/**
* This type should be used when an object mapped to `PropOptions` is used for a component's `props` value.
*/
export type ThisTypedComponentOptionsWithRecordProps<V extends Vue, Data, Methods, Computed, Props> =
object &
ComponentOptions<V, Data | ((this: Readonly<Props> & V) => Data), Methods, Computed, RecordPropsDefinition<Props>> &
ThisType<CombinedVueInstance<V, Data, Methods, Computed, Readonly<Props>>>;

type DefaultData<V> = object | ((this: V) => object);
type DefaultProps = Record<string, any>;
type DefaultMethods<V> = { [key: string]: (this: V, ...args: any[]) => any };
type DefaultComputed = { [key: string]: any };
export interface ComponentOptions<
V extends Vue,
Data=DefaultData<V>,
Methods=DefaultMethods<V>,
Computed=DefaultComputed,
PropsDef=PropsDefinition<DefaultProps>> {
data?: Data;
props?: PropsDef;
propsData?: Object;
computed?: { [key: string]: ((this: V) => any) | ComputedOptions<V> };
methods?: { [key: string]: (this: V, ...args: any[]) => any };
watch?: { [key: string]: ({ handler: WatchHandler<V, any> } & WatchOptions) | WatchHandler<V, any> | string };
computed?: Accessors<Computed>;
methods?: Methods;
watch?: Record<string, WatchOptionsWithHandler<any> | WatchHandler<any> | string>;

el?: Element | String;
template?: string;
render?(this: V, createElement: CreateElement): VNode;
render?(createElement: CreateElement): VNode;
renderError?: (h: () => VNode, err: Error) => VNode;
staticRenderFns?: ((createElement: CreateElement) => VNode)[];

beforeCreate?(this: V): void;
created?(this: V): void;
beforeDestroy?(this: V): void;
destroyed?(this: V): void;
beforeMount?(this: V): void;
mounted?(this: V): void;
beforeUpdate?(this: V): void;
updated?(this: V): void;
activated?(this: V): void;
deactivated?(this: V): void;

directives?: { [key: string]: DirectiveOptions | DirectiveFunction };
components?: { [key: string]: Component | AsyncComponent };
created?(): void;
beforeDestroy?(): void;
destroyed?(): void;
beforeMount?(): void;
mounted?(): void;
beforeUpdate?(): void;
updated?(): void;
activated?(): void;
deactivated?(): void;

directives?: { [key: string]: DirectiveFunction | DirectiveOptions };
components?: { [key: string]: Component<any, any, any, any> | AsyncComponent<any, any, any, any> };
transitions?: { [key: string]: Object };
filters?: { [key: string]: Function };

Expand All @@ -57,49 +97,64 @@ export interface ComponentOptions<V extends Vue> {
parent?: Vue;
mixins?: (ComponentOptions<Vue> | typeof Vue)[];
name?: string;
// TODO: support properly inferred 'extends'
extends?: ComponentOptions<Vue> | typeof Vue;
delimiters?: [string, string];
comments?: boolean;
inheritAttrs?: boolean;
}

export interface FunctionalComponentOptions {
export interface FunctionalComponentOptions<Props = DefaultProps, PropDefs = PropsDefinition<Props>> {
name?: string;
props?: string[] | { [key: string]: PropOptions | Constructor | Constructor[] };
props?: PropDefs;
inject?: { [key: string]: string | symbol } | string[];
functional: boolean;
render(this: never, createElement: CreateElement, context: RenderContext): VNode | void;
render(this: undefined, createElement: CreateElement, context: RenderContext<Props>): VNode;
}

export interface RenderContext {
props: any;
export interface RenderContext<Props=DefaultProps> {
props: Props;
children: VNode[];
slots(): any;
data: VNodeData;
parent: Vue;
injections: any
}

export interface PropOptions {
type?: Constructor | Constructor[] | null;
export type Prop<T> = { (): T } | { new (...args: any[]): T & object }

export type PropValidator<T> = PropOptions<T> | Prop<T> | Prop<T>[];

export interface PropOptions<T=any> {
type?: Prop<T> | Prop<T>[];
required?: boolean;
default?: any;
validator?(value: any): boolean;
default?: T | null | undefined | (() => object);
validator?(value: T): boolean;
}

export interface ComputedOptions<V> {
get?(this: V): any;
set?(this: V, value: any): void;
export type RecordPropsDefinition<T> = {
[K in keyof T]: PropValidator<T[K]>
}
export type ArrayPropsDefinition<T> = (keyof T)[];
export type PropsDefinition<T> = ArrayPropsDefinition<T> | RecordPropsDefinition<T>;

export interface ComputedOptions<T> {
get?(): T;
set?(value: T): void;
cache?: boolean;
}

export type WatchHandler<V, T> = (this: V, val: T, oldVal: T) => void;
export type WatchHandler<T> = (val: T, oldVal: T) => void;

export interface WatchOptions {
deep?: boolean;
immediate?: boolean;
}

export interface WatchOptionsWithHandler<T> extends WatchOptions {
handler: WatchHandler<T>;
}

export type DirectiveFunction = (
el: HTMLElement,
binding: VNodeDirective,
Expand Down
21 changes: 16 additions & 5 deletions types/test/augmentation-test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Vue = require("../index");
import Vue from "../index";

declare module "../vue" {
// add instance property and method
Expand All @@ -8,9 +8,9 @@ declare module "../vue" {
}

// add static property and method
namespace Vue {
const staticProperty: string;
function staticMethod(): void;
interface VueConstructor {
staticProperty: string;
staticMethod(): void;
}
}

Expand All @@ -22,10 +22,21 @@ declare module "../options" {
}

const vm = new Vue({
props: ["bar"],
data: {
a: true
},
foo: "foo"
foo: "foo",
methods: {
foo() {
this.a = false;
}
},
computed: {
BAR(): string {
return this.bar.toUpperCase();
}
}
});

vm.$instanceProperty;
Expand Down
Loading

0 comments on commit db138e2

Please sign in to comment.