diff --git a/package.json b/package.json index bd1247a848f..4dfaff04059 100644 --- a/package.json +++ b/package.json @@ -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.48.0", @@ -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.20.5", diff --git a/types/index.d.ts b/types/index.d.ts index 28350fcfdbe..da58517f42f 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -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 = Options.ComponentOptions; - export type FunctionalComponentOptions = Options.FunctionalComponentOptions; - export type RenderContext = Options.RenderContext; - export type PropOptions = Options.PropOptions; - export type ComputedOptions = Options.ComputedOptions; - export type WatchHandler = Options.WatchHandler; - export type WatchOptions = Options.WatchOptions; - export type DirectiveFunction = Options.DirectiveFunction; - export type DirectiveOptions = Options.DirectiveOptions; +export { + CreateElement +} from "./vue"; - export type PluginFunction = Plugin.PluginFunction; - export type PluginObject = Plugin.PluginObject; +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"; diff --git a/types/options.d.ts b/types/options.d.ts index b28a228a6c8..7cba394e02e 100644 --- a/types/options.d.ts +++ b/types/options.d.ts @@ -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 | FunctionalComponentOptions; +// we don't support infer props in async component +export type Component, Methods=DefaultMethods, Computed=DefaultComputed, Props=DefaultProps> = + | typeof Vue + | FunctionalComponentOptions + | ThisTypedComponentOptionsWithArrayProps + | ThisTypedComponentOptionsWithRecordProps; interface EsModuleComponent { default: Component } -export type AsyncComponent = ( - resolve: (component: Component) => void, +export type AsyncComponent, Methods=DefaultMethods, Computed=DefaultComputed, Props=DefaultProps> = ( + resolve: (component: Component) => void, reject: (reason?: any) => void -) => Promise | Component | void; +) => Promise | 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` and work backwards. + */ +export type Accessors = { + [K in keyof T]: (() => T[K]) | ComputedOptions +} -export interface ComponentOptions { - 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 = + object & + ComponentOptions> & V) => Data), Methods, Computed, PropNames[]> & + ThisType>>>; + +/** + * This type should be used when an object mapped to `PropOptions` is used for a component's `props` value. + */ +export type ThisTypedComponentOptionsWithRecordProps = + object & + ComponentOptions & V) => Data), Methods, Computed, RecordPropsDefinition> & + ThisType>>; + +type DefaultData = object | ((this: V) => object); +type DefaultProps = Record; +type DefaultMethods = { [key: string]: (this: V, ...args: any[]) => any }; +type DefaultComputed = { [key: string]: any }; +export interface ComponentOptions< + V extends Vue, + Data=DefaultData, + Methods=DefaultMethods, + Computed=DefaultComputed, + PropsDef=PropsDefinition> { + data?: Data; + props?: PropsDef; propsData?: Object; - computed?: { [key: string]: ((this: V) => any) | ComputedOptions }; - methods?: { [key: string]: (this: V, ...args: any[]) => any }; - watch?: { [key: string]: ({ handler: WatchHandler } & WatchOptions) | WatchHandler | string }; + computed?: Accessors; + methods?: Methods; + watch?: Record | WatchHandler | 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 | AsyncComponent }; transitions?: { [key: string]: Object }; filters?: { [key: string]: Function }; @@ -57,22 +97,23 @@ export interface ComponentOptions { parent?: Vue; mixins?: (ComponentOptions | typeof Vue)[]; name?: string; + // TODO: support properly inferred 'extends' extends?: ComponentOptions | typeof Vue; delimiters?: [string, string]; comments?: boolean; inheritAttrs?: boolean; } -export interface FunctionalComponentOptions { +export interface FunctionalComponentOptions> { 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): VNode; } -export interface RenderContext { - props: any; +export interface RenderContext { + props: Props; children: VNode[]; slots(): any; data: VNodeData; @@ -80,26 +121,40 @@ export interface RenderContext { injections: any } -export interface PropOptions { - type?: Constructor | Constructor[] | null; +export type Prop = { (): T } | { new (...args: any[]): T & object } + +export type PropValidator = PropOptions | Prop | Prop[]; + +export interface PropOptions { + type?: Prop | Prop[]; required?: boolean; - default?: any; - validator?(value: any): boolean; + default?: T | null | undefined | (() => object); + validator?(value: T): boolean; } -export interface ComputedOptions { - get?(this: V): any; - set?(this: V, value: any): void; +export type RecordPropsDefinition = { + [K in keyof T]: PropValidator +} +export type ArrayPropsDefinition = (keyof T)[]; +export type PropsDefinition = ArrayPropsDefinition | RecordPropsDefinition; + +export interface ComputedOptions { + get?(): T; + set?(value: T): void; cache?: boolean; } -export type WatchHandler = (this: V, val: T, oldVal: T) => void; +export type WatchHandler = (val: T, oldVal: T) => void; export interface WatchOptions { deep?: boolean; immediate?: boolean; } +export interface WatchOptionsWithHandler extends WatchOptions { + handler: WatchHandler; +} + export type DirectiveFunction = ( el: HTMLElement, binding: VNodeDirective, diff --git a/types/test/augmentation-test.ts b/types/test/augmentation-test.ts index dd569052be4..bb77672103e 100644 --- a/types/test/augmentation-test.ts +++ b/types/test/augmentation-test.ts @@ -1,4 +1,4 @@ -import Vue = require("../index"); +import Vue from "../index"; declare module "../vue" { // add instance property and method @@ -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; } } @@ -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; diff --git a/types/test/options-test.ts b/types/test/options-test.ts index cbd5be1423f..6a94d95c79a 100644 --- a/types/test/options-test.ts +++ b/types/test/options-test.ts @@ -1,14 +1,76 @@ -import Vue = require("../index"); +import Vue from "../index"; import { AsyncComponent, ComponentOptions, FunctionalComponentOptions } from "../index"; +import { CreateElement } from "../vue"; interface Component extends Vue { a: number; } +Vue.component('sub-component', { + components: { + a: Vue.component(""), + b: {} + } +}); + +Vue.component('prop-component', { + props: { + size: Number, + name: { + type: String, + default: '0', + required: true, + } + }, + data() { + return { + fixedSize: this.size.toFixed(), + capName: this.name.toUpperCase() + } + } +}); + +Vue.component('string-prop', { + props: ['size', 'name'], + data() { + return { + fixedSize: this.size.whatever, + capName: this.name.isany + } + } +}); + +class User { + private u: number +} +class Cat { + private u: number +} + +Vue.component('union-prop', { + props: { + primitive: [String, Number], + object: [Cat, User], + regex: RegExp, + mixed: [RegExp, Array], + union: [User, Number] as {new(): User | Number}[] // requires annotation + }, + data() { + this.primitive; + this.object; + this.union; + this.regex.compile; + this.mixed; + return { + fixedSize: this.union, + } + } +}); + Vue.component('component', { data() { this.$mount - this.a + this.size return { a: 1 } @@ -17,25 +79,22 @@ Vue.component('component', { size: Number, name: { type: String, - default: 0, + default: '0', required: true, - validator(value) { - return value > 0; - } } }, propsData: { msg: "Hello" }, computed: { - aDouble(this: Component) { + aDouble(): number { return this.a * 2; }, aPlus: { - get(this: Component) { + get(): number { return this.a + 1; }, - set(this: Component, v: number) { + set(v: number) { this.a = v - 1; }, cache: false @@ -44,6 +103,9 @@ Vue.component('component', { methods: { plus() { this.a++; + this.aDouble.toFixed(); + this.aPlus = 1; + this.size.toFixed(); } }, watch: { @@ -92,15 +154,15 @@ Vue.component('component', { createElement("div", "message"), createElement(Vue.component("component")), createElement({} as ComponentOptions), - createElement({ functional: true, render () {}}), + createElement({ + functional: true, + render(c: CreateElement) { + return createElement() + } + }), createElement(() => Vue.component("component")), createElement(() => ( {} as ComponentOptions )), - createElement(() => { - return new Promise((resolve) => { - resolve({} as ComponentOptions); - }) - }), createElement((resolve, reject) => { resolve({} as ComponentOptions); reject(); @@ -114,7 +176,7 @@ Vue.component('component', { staticRenderFns: [], beforeCreate() { - this.a = 1; + (this as any).a = 1; }, created() {}, beforeDestroy() {}, @@ -160,7 +222,7 @@ Vue.component('component', { name: "Component", extends: {} as ComponentOptions, delimiters: ["${", "}"] -} as ComponentOptions); +}); Vue.component('component-with-scoped-slot', { render (h) { @@ -183,15 +245,15 @@ Vue.component('component-with-scoped-slot', { }, components: { child: { - render (h) { + render (this: Vue, h: CreateElement) { return h('div', [ this.$scopedSlots['default']({ msg: 'hi' }), this.$scopedSlots['item']({ msg: 'hello' }) ]) } - } as ComponentOptions + } } -} as ComponentOptions) +}) Vue.component('functional-component', { props: ['prop'], @@ -205,13 +267,16 @@ Vue.component('functional-component', { context.parent; return createElement("div", {}, context.children); } -} as FunctionalComponentOptions); +}); Vue.component('functional-component-object-inject', { functional: true, inject: { foo: 'bar', baz: Symbol() + }, + render(h) { + return h('div') } }) @@ -220,8 +285,8 @@ Vue.component("async-component", ((resolve, reject) => { resolve(Vue.component("component")); }, 0); return new Promise((resolve) => { - resolve({ functional: true } as FunctionalComponentOptions); + resolve({ functional: true }); }) -}) as AsyncComponent); +})); -Vue.component('async-es-module-component', (() => import('./es-module')) as AsyncComponent) +Vue.component('async-es-module-component', () => import('./es-module')) diff --git a/types/test/plugin-test.ts b/types/test/plugin-test.ts index 211f248ac9e..15055614e80 100644 --- a/types/test/plugin-test.ts +++ b/types/test/plugin-test.ts @@ -1,4 +1,4 @@ -import Vue = require("../index"); +import Vue from "../index"; import { PluginFunction, PluginObject } from "../index"; class Option { diff --git a/types/test/tsconfig.json b/types/test/tsconfig.json index cbc565a7c53..be0fef22ace 100644 --- a/types/test/tsconfig.json +++ b/types/test/tsconfig.json @@ -6,8 +6,7 @@ "es2015" ], "module": "commonjs", - "noImplicitAny": true, - "strictNullChecks": true, + "strict": true, "noEmit": true }, "files": [ diff --git a/types/test/vue-test.ts b/types/test/vue-test.ts index 50605998999..e497c60242a 100644 --- a/types/test/vue-test.ts +++ b/types/test/vue-test.ts @@ -1,4 +1,5 @@ -import Vue = require("../index"); +import Vue, { VNode } from "../index"; +import { ComponentOptions } from "../options"; class Test extends Vue { a: number; @@ -90,9 +91,91 @@ class Test extends Vue { this.directive("", {bind() {}}); this.filter("", (value: number) => value); this.component("", { data: () => ({}) }); - this.component("", { functional: true, render () {}}); + this.component("", { functional: true, render(h) { return h("div", "hello!") } }); this.use; this.mixin(Test); this.compile("
{{ message }}
"); } } + +const HelloWorldComponent = Vue.extend({ + props: ["name"], + data() { + return { + message: "Hello " + this.name, + } + }, + computed: { + shouted(): string { + return this.message.toUpperCase(); + } + }, + methods: { + getMoreExcited() { + this.message += "!"; + } + }, + watch: { + message(a: string) { + console.log(`Message ${this.message} was changed!`); + } + } +}); + +const FunctionalHelloWorldComponent = Vue.extend({ + functional: true, + props: ["name"], + render(createElement, ctxt) { + return createElement("div", "Hello " + ctxt.props.name) + } +}); + +const Parent = Vue.extend({ + data() { + return { greeting: 'Hello' } + } +}); + +const Child = Parent.extend({ + methods: { + foo() { + console.log(this.greeting.toLowerCase()); + } + } +}); + +const GrandChild = Child.extend({ + computed: { + lower(): string { + return this.greeting.toLowerCase(); + } + } +}); + +new GrandChild().lower.toUpperCase(); +for (let _ in (new Test()).$options) { +} +declare const options: ComponentOptions; +Vue.extend(options); +Vue.component('test-comp', options); +new Vue(options); + +// cyclic example +Vue.extend({ + props: { + bar: { + type: String + } + }, + methods: { + foo() {} + }, + mounted () { + this.foo() + }, + // manual annotation + render (h): VNode { + const a = this.bar + return h('canvas', {}, [a]) + } +}) diff --git a/types/tsconfig.json b/types/tsconfig.json new file mode 100644 index 00000000000..dc2f0455c71 --- /dev/null +++ b/types/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "strict": true, + "lib": [ + "es2015", "dom" + ] + }, + "include": [ + "./*.ts" + ] +} \ No newline at end of file diff --git a/types/vue.d.ts b/types/vue.d.ts index d1cecb33093..ae30d2cebb3 100644 --- a/types/vue.d.ts +++ b/types/vue.d.ts @@ -3,50 +3,39 @@ import { AsyncComponent, ComponentOptions, FunctionalComponentOptions, - WatchOptions, + WatchOptionsWithHandler, WatchHandler, DirectiveOptions, - DirectiveFunction + DirectiveFunction, + RecordPropsDefinition, + ThisTypedComponentOptionsWithArrayProps, + ThisTypedComponentOptionsWithRecordProps, + WatchOptions, } from "./options"; import { VNode, VNodeData, VNodeChildren, ScopedSlot } from "./vnode"; import { PluginFunction, PluginObject } from "./plugin"; -export type CreateElement = { - // empty node - (): VNode; - - // element or component name - (tag: string, children: VNodeChildren): VNode; - (tag: string, data?: VNodeData, children?: VNodeChildren): VNode; - - // component constructor or options - (tag: Component, children: VNodeChildren): VNode; - (tag: Component, data?: VNodeData, children?: VNodeChildren): VNode; - - // async component - (tag: AsyncComponent, children: VNodeChildren): VNode; - (tag: AsyncComponent, data?: VNodeData, children?: VNodeChildren): VNode; +export interface CreateElement { + (tag?: string | Component | AsyncComponent, children?: VNodeChildren): VNode; + (tag?: string | Component | AsyncComponent, data?: VNodeData, children?: VNodeChildren): VNode; } -export declare class Vue { - - constructor(options?: ComponentOptions); - - $data: Object; +export interface Vue { readonly $el: HTMLElement; readonly $options: ComponentOptions; readonly $parent: Vue; readonly $root: Vue; readonly $children: Vue[]; - readonly $refs: { [key: string]: Vue | Element | Vue[] | Element[]}; + readonly $refs: { [key: string]: Vue | Element | Vue[] | Element[] }; readonly $slots: { [key: string]: VNode[] }; readonly $scopedSlots: { [key: string]: ScopedSlot }; readonly $isServer: boolean; + readonly $data: Record; + readonly $props: Record; readonly $ssrContext: any; - readonly $props: any; readonly $vnode: VNode; - readonly $attrs: { [key: string] : string }; - readonly $listeners: { [key: string]: Function | Array }; + readonly $attrs: Record; + readonly $listeners: Record; $mount(elementOrSelector?: Element | String, hydrating?: boolean): this; $forceUpdate(): void; @@ -55,12 +44,12 @@ export declare class Vue { $delete: typeof Vue.delete; $watch( expOrFn: string, - callback: WatchHandler, + callback: (this: this, n: any, o: any) => void, options?: WatchOptions ): (() => void); $watch( expOrFn: (this: this) => T, - callback: WatchHandler, + callback: (this: this, n: T, o: T) => void, options?: WatchOptions ): (() => void); $on(event: string | string[], callback: Function): this; @@ -70,8 +59,54 @@ export declare class Vue { $nextTick(callback: (this: this) => void): void; $nextTick(): Promise; $createElement: CreateElement; +} + +export type CombinedVueInstance = Instance & Data & Methods & Computed & Props; +export type ExtendedVue = VueConstructor & Vue>; + +export interface VueConstructor { + new (options?: ThisTypedComponentOptionsWithArrayProps): CombinedVueInstance>; + // ideally, the return type should just contains Props, not Record. But TS requires Base constructors must all have the same return type. + new (options?: ThisTypedComponentOptionsWithRecordProps): CombinedVueInstance>; + new (options?: ComponentOptions): CombinedVueInstance>; + + extend(definition: FunctionalComponentOptions, PropNames[]>): ExtendedVue>; + extend(definition: FunctionalComponentOptions>): ExtendedVue; + extend(options?: ThisTypedComponentOptionsWithArrayProps): ExtendedVue>; + extend(options?: ThisTypedComponentOptionsWithRecordProps): ExtendedVue; + extend(options?: ComponentOptions): ExtendedVue; + + nextTick(callback: () => void, context?: any[]): void; + nextTick(): Promise + set(object: Object, key: string, value: T): T; + set(array: T[], key: number, value: T): T; + delete(object: Object, key: string): void; + delete(array: T[], key: number): void; + + directive( + id: string, + definition?: DirectiveOptions | DirectiveFunction + ): DirectiveOptions; + filter(id: string, definition?: Function): Function; - static config: { + component(id: string): VueConstructor; + component(id: string, constructor: VC): VC; + component(id: string, definition: AsyncComponent): ExtendedVue; + component(id: string, definition: FunctionalComponentOptions, PropNames[]>): ExtendedVue>; + component(id: string, definition: FunctionalComponentOptions>): ExtendedVue; + component(id: string, definition?: ThisTypedComponentOptionsWithArrayProps): ExtendedVue>; + component(id: string, definition?: ThisTypedComponentOptionsWithRecordProps): ExtendedVue; + component(id: string, definition?: ComponentOptions): ExtendedVue; + + use(plugin: PluginObject | PluginFunction, options?: T): void; + use(plugin: PluginObject | PluginFunction, ...options: any[]): void; + mixin(mixin: VueConstructor | ComponentOptions): void; + compile(template: string): { + render(createElement: typeof Vue.prototype.$createElement): VNode; + staticRenderFns: (() => VNode)[]; + }; + + config: { silent: boolean; optionMergeStrategies: any; devtools: boolean; @@ -82,27 +117,6 @@ export declare class Vue { ignoredElements: string[]; keyCodes: { [key: string]: number }; } - - static extend(options: ComponentOptions | FunctionalComponentOptions): typeof Vue; - static nextTick(callback: () => void, context?: any[]): void; - static nextTick(): Promise - static set(object: Object, key: string, value: T): T; - static set(array: T[], key: number, value: T): T; - static delete(object: Object, key: string): void; - static delete(array: T[], key: number): void; - - static directive( - id: string, - definition?: DirectiveOptions | DirectiveFunction - ): DirectiveOptions; - static filter(id: string, definition?: Function): Function; - static component(id: string, definition?: Component | AsyncComponent): typeof Vue; - - static use(plugin: PluginObject | PluginFunction, options?: T): void; - static use(plugin: PluginObject | PluginFunction, ...options: any[]): void; - static mixin(mixin: typeof Vue | ComponentOptions): void; - static compile(template: string): { - render(createElement: typeof Vue.prototype.$createElement): VNode; - staticRenderFns: (() => VNode)[]; - }; } + +export const Vue: VueConstructor; pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy