Преглед изворни кода

add types for Vue 1.x (#4731)

* add types for Vue 1.x

* add missing types and remove unnecessary option
Kaorun343 пре 9 година
родитељ
комит
c8f3d6b955

+ 3 - 1
package.json

@@ -24,7 +24,7 @@
   "bugs": "https://github.com/vuejs/vue/issues",
   "homepage": "http://vuejs.org",
   "scripts": {
-    "test": "npm run lint && npm run cover && npm run build && npm run e2e",
+    "test": "npm run lint && npm run cover && npm run build && npm run e2e && npm run types",
     "build": "node build/build.js",
     "install-hook": "ln -s ../../build/git-hooks/pre-commit .git/hooks/pre-commit",
     "dev": "webpack --watch --config build/webpack.dev.config.js & npm run serve-test",
@@ -34,6 +34,7 @@
     "e2e": "casperjs test --concise ./test/e2e",
     "unit": "karma start build/karma.unit.config.js",
     "cover": "karma start build/karma.cover.config.js",
+    "types": "tsc -p ./types/test/tsconfig.json",
     "sauce": "karma start build/karma.sauce.config.js",
     "sauce-all": "npm run sauce && npm run sauce -- 1 && npm run sauce -- 2",
     "release": "bash build/release.sh",
@@ -74,6 +75,7 @@
     "rollup": "^0.34.13",
     "rollup-plugin-babel": "^1.0.0",
     "rollup-plugin-replace": "^1.1.0",
+    "typescript": "^2.1.5",
     "uglify-js": "^2.4.24",
     "webpack": "^1.12.2",
     "webpack-dev-server": "^1.12.1"

+ 51 - 0
types/index.d.ts

@@ -0,0 +1,51 @@
+import * as V from './vue';
+import * as Options from './options';
+import * as Plugin from './plugin';
+
+declare global {
+  interface Array<T> {
+    $remove(item: T): Array<T>;
+    $set(index: any, val: T): T;
+  }
+
+  // For the projects/tools that depend on this namespace
+  namespace vuejs {
+    export type PropOption = Options.PropOptions;
+    export type ComputedOption = Options.ComputedOptions<any>;
+    export type WatchOption = Options.WatchOptions;
+    export type DirectiveOption = Options.DirectiveOptions<Vue>;
+    export type Directive = Options.DirectiveInstance<Vue>;
+    export type TransitionOpsion = Options.TransitionOptions;
+    export type ComponentOption = Options.ComponentOptions<any>;
+    export type FilterOption = Options.FilterOptions;
+    export type Vue = V.Vue;
+    export type VueStatic = typeof V.Vue;
+    export type VueConfig = typeof V.Vue.config;
+  }
+}
+
+// `Vue` in `export = Vue` must be a namespace
+// All available types are exported via this namespace
+declare namespace Vue {
+
+  export type Component = Options.Component;
+  export type AsyncComponent = Options.AsyncComponent;
+  export type ComponentOptions<V extends Vue> = Options.ComponentOptions<V>;
+  export type PropOptions = Options.PropOptions;
+  export type ComputedOptions<V extends Vue> = Options.ComputedOptions<V>;
+  export type WatchHandler<V extends Vue> = Options.WatchHandler<V>;
+  export type WatchOptions = Options.WatchOptions;
+  export type DirectiveInstance<V extends Vue> = Options.DirectiveInstance<V>;
+  export type DirectiveFunction<V extends Vue> = Options.DirectiveFunction<V>;
+  export type DirectiveOptions<V extends Vue> = Options.DirectiveOptions<V>;
+  export type FilterOptions = Options.FilterOptions;
+  export type TransitionOpsions = Options.TransitionOptions;
+
+  export type PluginFunction<T> = Plugin.PluginFunction<T>;
+  export type PluginObject<T> = Plugin.PluginObject<T>;
+}
+
+// TS cannot merge imported class with namespace, declare a subclass to bypass
+declare class Vue extends V.Vue { }
+
+export = Vue;

+ 120 - 0
types/options.d.ts

@@ -0,0 +1,120 @@
+import { Vue } from './vue';
+
+type Constructor = {
+  new (...args: any[]): any;
+};
+
+type Dictionary<T> = {
+  [key: string]: T;
+};
+
+export type Component = ComponentOptions<Vue> | typeof Vue;
+export type AsyncComponent = (
+  resolve: (component: Component) => void,
+  reject: (reason?: any) => void
+) => Promise<Component> | Component | void;
+
+export interface ComponentOptions<V extends Vue> {
+  data?: Dictionary<any> | ((this: V) => Dictionary<any>);
+  props?: string[] | Dictionary<PropOptions | Constructor | Constructor[]>;
+  propsData?: Dictionary<any>;
+  computed?: Dictionary<((this: V) => any) | ComputedOptions<V>>;
+  methods?: Dictionary<(this: V, ...args: any[]) => any>;
+  watch?: Dictionary<({ handler: WatchHandler<V> } & WatchOptions) | WatchHandler<V> | string>;
+
+  el?: string | HTMLElement | (() => HTMLElement);
+  template?: string;
+  replace?: boolean;
+
+  init?(this: V): void;
+  created?(this: V): void;
+  beforeCompile?(this: V): void;
+  compiled?(this: V): void;
+  activate?(this: V, done: () => void): void;
+  ready?(this: V): void;
+  attached?(this: V): void;
+  detached?(this: V): void;
+  beforeDestroy?(this: V): void;
+  destroyed?(this: V): void;
+
+  directives?: Dictionary<DirectiveOptions<V> | DirectiveFunction<V>>;
+  elementDirectives?: Dictionary<DirectiveOptions<V> | Function>;
+  filters?: Dictionary<Function | FilterOptions>;
+  components?: Dictionary<Component | AsyncComponent>;
+  transitions?: Dictionary<TransitionOptions>;
+  partials?: Dictionary<string>;
+
+  parent?: Vue;
+  events?: Dictionary<((...args: any[]) => (boolean | void)) | string>;
+  mixins?: (ComponentOptions<Vue> | typeof Vue)[];
+  name?: string;
+}
+
+export interface PropOptions {
+  type?: Constructor | Constructor[] | null;
+  required?: boolean;
+  default?: any;
+  twoWay?: boolean;
+  validator?(value: any): boolean;
+  coerce?(value: any): any;
+}
+
+export interface ComputedOptions<V extends Vue> {
+  get?(this: V): any;
+  set(this: V, value: any): void;
+}
+
+export type WatchHandler<V> = (this: V, val: any, oldVal: any) => void;
+
+export interface WatchOptions {
+  deep?: boolean;
+  immediate?: boolean;
+}
+
+export interface DirectiveInstance<V extends Vue> {
+  el: HTMLElement;
+  vm: V;
+  expression: string;
+  arg?: string;
+  name: string;
+  modifiers: Dictionary<boolean>;
+  descriptor: any;
+  params?: Dictionary<any>;
+}
+
+export type DirectiveFunction<V extends Vue> = (this: DirectiveInstance<V>, newVal: any, oldVal: any) => void;
+
+export interface DirectiveOptions<V extends Vue> {
+  bind?(this: DirectiveInstance<V>): void;
+  update?(this: DirectiveInstance<V>, newVal: any, oldVal: any): void;
+  unbind?(this: DirectiveInstance<V>): void;
+  params?: string[];
+  deep?: boolean;
+  twoWay?: boolean;
+  acceptStatement?: boolean;
+  terminal?: boolean;
+  priority?: number;
+}
+
+export interface FilterOptions {
+  read?: Function;
+  write?: Function;
+}
+
+export interface TransitionOptions {
+  css?: boolean;
+  animation?: string;
+  enterClass?: string;
+  leaveClass?: string;
+  beforeEnter?(el: HTMLElement): void;
+  enter?(el: HTMLElement, done: () => void): void;
+  afterEnter?(el: HTMLElement): void;
+  enterCancelled?(el: HTMLElement): void;
+  beforeLeave?(el: HTMLElement): void;
+  leave?(el: HTMLElement, done: () => void): void;
+  afterLeave?(el: HTMLElement): void;
+  leaveCancelled?(el: HTMLElement): void;
+  stagger?(index: number): number;
+  enterStagger?(index: number): number;
+  leaveStagger?(index: number): number;
+}

+ 10 - 0
types/plugin.d.ts

@@ -0,0 +1,10 @@
+import { Vue as _Vue } from './vue';
+
+export type PluginFunction<T> = (Vue: typeof _Vue, options?: T) => void;
+
+export interface PluginObject<T> {
+  install: PluginFunction<T>;
+  [key: string]: any;
+}
+
+export type Plugin<T> = PluginFunction<T> | PluginObject<T>;

+ 35 - 0
types/test/augmentation-test.ts

@@ -0,0 +1,35 @@
+import Vue = require("../index");
+
+declare module "../vue" {
+  // add instance property and method
+  interface Vue {
+    $instanceProperty: string;
+    $instanceMethod(): void;
+  }
+
+  // add static property and method
+  namespace Vue {
+    const staticProperty: string;
+    function staticMethod(): void;
+  }
+}
+
+// augment ComponentOptions
+declare module "../options" {
+  interface ComponentOptions<V extends Vue> {
+    foo?: string;
+  }
+}
+
+const vm = new Vue({
+  data: {
+    a: true
+  },
+  foo: "foo"
+});
+
+vm.$instanceProperty;
+vm.$instanceMethod();
+
+Vue.staticProperty;
+Vue.staticMethod();

+ 17 - 0
types/test/global-test.ts

@@ -0,0 +1,17 @@
+declare var Vue: vuejs.VueStatic;
+
+var app = new Vue({
+  data: {
+    message: ""
+  }
+});
+
+app.$mount("#app");
+
+class Application extends Vue {}
+
+Vue.component("component", {
+  ready () {
+    this.a
+  }
+} as vuejs.ComponentOption)

+ 118 - 0
types/test/options-test.ts

@@ -0,0 +1,118 @@
+import Vue = require("../index");
+import { ComponentOptions } from "../index";
+
+interface Component extends Vue {
+  a: number;
+}
+
+Vue.component('component', {
+  data() {
+    this.$mount
+    this.a
+    return {
+      a: 1
+    }
+  },
+  props: {
+    size: Number,
+    name: {
+      type: String,
+      default: 0,
+      required: true,
+      validator(value) {
+        return value > 0;
+      }
+    }
+  },
+  propsData: {
+    msg: "Hello"
+  },
+  computed: {
+    aDouble(this: Component) {
+      return this.a * 2;
+    },
+    aPlus: {
+      get(this: Component) {
+        return this.a + 1;
+      },
+      set(this: Component, v: number) {
+        this.a = v - 1;
+      },
+      cache: false
+    }
+  },
+  methods: {
+    plus() {
+      this.a++;
+    }
+  },
+  watch: {
+    'a': function(val: number, oldVal: number) {
+      console.log(`new: ${val}, old: ${oldVal}`);
+    },
+    'b': 'someMethod',
+    'c': {
+      handler(val, oldVal) {
+        this.a = val
+      },
+      deep: true
+    }
+  },
+  el: "#app",
+  template: "<div>{{ message }}</div>",
+
+  beforeCreate() {
+    this.a = 1;
+  },
+  created() {},
+  beforeDestroy() {},
+  destroyed() {},
+  beforeMount() {},
+  mounted() {},
+  beforeUpdate() {},
+  updated() {},
+  activated() {},
+  deactivated() {},
+
+  directives: {
+    a: {
+      bind() {},
+      inserted() {},
+      update() {},
+      componentMounted() {},
+      unbind() {}
+    },
+    b(val, newVal) {
+      this.el.textContent;
+
+      this.name;
+      this.expression;
+      this.arg;
+      this.modifiers["modifier"];
+    }
+  },
+  components: {
+    a: Vue.component(""),
+    b: {} as ComponentOptions<Vue>
+  },
+  transitions: {},
+  filters: {
+    double(value: number) {
+      return value * 2;
+    }
+  },
+  parent: new Vue,
+  mixins: [Vue.component(""), ({} as ComponentOptions<Vue>)],
+  name: "Component",
+  extends: {} as ComponentOptions<Vue>,
+  delimiters: ["${", "}"]
+} as ComponentOptions<Component>);
+
+Vue.component("async-component", (resolve, reject) => {
+  setTimeout(() => {
+    resolve(Vue.component("component"));
+  }, 0);
+  return new Promise((resolve) => {
+    resolve({ } as ComponentOptions<Vue>);
+  })
+});

+ 19 - 0
types/test/plugin-test.ts

@@ -0,0 +1,19 @@
+import Vue = require("../index");
+import { PluginFunction, PluginObject } from "../index";
+
+class Option {
+  prefix: string;
+  suffix: string;
+}
+
+const plugin: PluginObject<Option> = {
+  install(Vue, option) {
+    if (typeof option !== "undefined") {
+      const {prefix, suffix} = option;
+    }
+  }
+}
+const installer: PluginFunction<Option> = function(Vue, option) { }
+
+Vue.use(plugin, new Option);
+Vue.use(installer, new Option);

+ 26 - 0
types/test/tsconfig.json

@@ -0,0 +1,26 @@
+{
+  "compilerOptions": {
+    "target": "es5",
+    "lib": [
+      "es5",
+      "dom",
+      "es2015.promise"
+    ],
+    "module": "commonjs",
+    "noImplicitAny": true,
+    "strictNullChecks": true,
+    "noEmit": true
+  },
+  "files": [
+    "../index.d.ts",
+    "../options.d.ts",
+    "../plugin.d.ts",
+    "../vue.d.ts",
+    "options-test.ts",
+    "plugin-test.ts",
+    "vue-test.ts",
+    "augmentation-test.ts",
+    "global-test.ts"
+  ],
+  "compileOnSave": false
+}

+ 91 - 0
types/test/vue-test.ts

@@ -0,0 +1,91 @@
+import Vue = require("../index");
+
+class Test extends Vue {
+  testProperties() {
+    this.$data;
+    this.$el;
+    this.$options;
+    this.$parent;
+    this.$root;
+    this.$children;
+    this.$refs;
+  }
+
+  // test property reification
+  $refs: {
+    vue: Vue;
+    vues: Vue[];
+  }
+  $els: {
+    element: HTMLInputElement;
+    elements: HTMLInputElement[];
+  }
+  testReification() {
+    this.$refs.vue.$data;
+    this.$refs.vues[0].$data;
+
+    this.$els.element.value;
+    this.$els.elements[0].value;
+  }
+
+  testMethods() {
+    this.$watch("a", (val: number, oldVal: number) => {}, {
+      immediate: true,
+      deep: false
+    })();
+    this.$watch(() => {}, (val: number) => {});
+    this.$get("");
+    this.$set("key", "value");
+    this.$delete("key");
+    this.$eval("");
+    this.$interpolate("");
+    this.$log("");
+
+    this.$on("", () => {});
+    this.$once("", () => {});
+    this.$off("", () => {});
+    this.$emit("", 1, 2, 3);
+    this.$broadcast("", 1, 2, 3);
+    this.$dispatch("", 1, 2, 3);
+
+    this.$appendTo("", () => {});
+    this.$before("", () => {});
+    this.$after("", () => {});
+    this.$remove(() => {});
+    this.$nextTick(function() {
+      this.$nextTick;
+    });
+
+    this.$mount("#app");
+    this.$destroy(true);
+  }
+
+  static testConfig() {
+    const { config } = this;
+    config.debug;
+    config.delimiters;
+    config.unsafeDelimiters;
+    config.silent;
+    config.async;
+    config.devtools;
+  }
+
+  static testMethods() {
+    this.extend({
+      data() {
+        return {
+          msg: ""
+        };
+      }
+    });
+    this.nextTick(() => {});
+    this.set({}, "", "");
+    this.delete({}, "");
+    this.directive("", {bind() {}});
+    this.elementDirective("", {bind() {}});
+    this.filter("", (value: number) => value);
+    this.component("", { data: () => ({}) });
+    this.use;
+    this.mixin(Test);
+  }
+}

+ 4 - 0
types/typings.json

@@ -0,0 +1,4 @@
+{
+  "name": "vue",
+  "main": "index.d.ts"
+}

+ 83 - 0
types/vue.d.ts

@@ -0,0 +1,83 @@
+import {
+  Component,
+  AsyncComponent,
+  FilterOptions,
+  DirectiveOptions,
+  DirectiveFunction,
+  ComponentOptions,
+  WatchHandler,
+  WatchOptions,
+  TransitionOptions
+} from './options';
+import { PluginObject, PluginFunction } from './plugin'
+
+type Dictionary<T> = {
+  [key: string]: T;
+};
+
+type Plugin<T> = PluginFunction<T> | PluginObject<T>;
+type Filter = Function | FilterOptions;
+type Directive = DirectiveFunction<Vue> | DirectiveOptions<Vue>;
+
+export declare class Vue {
+  constructor(options?: ComponentOptions<Vue>);
+
+  $data: any;
+  readonly $el: HTMLElement;
+  readonly $options: ComponentOptions<Vue>;
+  readonly $parent: Vue;
+  readonly $root: Vue;
+  readonly $children: Vue[];
+  readonly $refs: Dictionary<Vue | Vue[]>;
+  readonly $els: Dictionary<Element | Element[]>;
+
+  $watch(
+    expOrFn: string | Function,
+    callback: WatchHandler<this>,
+    options?: WatchOptions
+  ): () => void;
+  $get(expression: string): any;
+  $set<T>(keypath: string, value: T): T;
+  $delete(key: string): void;
+  $eval(expression: string): any;
+  $interpolate(templateString: string): any;
+  $log(keypath?: string): void;
+
+  $on(event: string, callback: Function): this;
+  $once(event: string, callback: Function): this;
+  $off(event: string, callback?: Function): this;
+  $emit(event: string, ...args: any[]): this;
+  $broadcast(event: string, ...args: any[]): this;
+  $dispatch(event: string, ...args: any[]): this;
+
+  $appendTo(elementOrSelector: HTMLElement | string, callback?: Function): this;
+  $before(elementOrSelector: HTMLElement | string, callback?: Function): this;
+  $after(elementOrSelector: HTMLElement | string, callback?: Function): this;
+  $remove(callback?: Function): this;
+  $nextTick(callback: (this: this) => void): void;
+
+  $mount(elementOrSelector?: HTMLElement | string): this;
+  $destroy(remove?: boolean): void;
+
+  static extend(options: ComponentOptions<Vue>): typeof Vue;
+  static nextTick(callback: () => void, context?: any[]): void;
+  static set<T>(object: any, key: string, value: T): T;
+  static delete(object: any, key: string): void;
+  static directive<T extends Directive>(id: string, definition?: T): T;
+  static elementDirective<T extends Directive>(id: string, definition?: T): T;
+  static filter<T extends Filter>(id: string, definition?: T): T;
+  static component<V extends Vue>(id: string, definition?: ComponentOptions<V> | AsyncComponent): ComponentOptions<V>;
+  static transition(id: string, hooks?: TransitionOptions): TransitionOptions;
+  static partial(id: string, partial?: string): string;
+  static use<T>(plugin: Plugin<T>, options?: T): void;
+  static mixin(mixin: Component): void;
+
+  static config: {
+    debug: boolean;
+    delimiters: [string, string];
+    unsafeDelimiters: [string, string];
+    silent: boolean;
+    async: boolean;
+    devtools: boolean;
+  }
+}