├── .github └── workflows │ ├── build.yml │ └── publish.yml ├── .gitignore ├── .travis.yml ├── README.md ├── demo ├── .gitignore ├── README.md ├── babel.config.js ├── package.json ├── public │ ├── favicon.ico │ └── index.html ├── src │ ├── components │ │ ├── app.ts │ │ ├── app.vue │ │ └── home │ │ │ ├── components │ │ │ ├── counter-panel │ │ │ │ ├── counter-panel.ts │ │ │ │ └── counter-panel.vue │ │ │ └── header-panel │ │ │ │ ├── header-panel.ts │ │ │ │ └── header-panel.vue │ │ │ ├── home.ts │ │ │ └── home.vue │ ├── main.ts │ ├── router.ts │ ├── shims-tsx.d.ts │ ├── shims-vue.d.ts │ └── store │ │ ├── counterModule.ts │ │ └── index.ts └── tsconfig.json ├── package.json ├── rollup.config.js ├── src ├── component │ ├── Filter.ts │ ├── Prop.ts │ ├── Watch.ts │ ├── index.ts │ └── utils.ts ├── types-vue.ts └── vuex │ ├── Action.ts │ ├── Getter.ts │ ├── MapAction.ts │ ├── MapGetter.ts │ ├── Module.ts │ ├── Mutation.ts │ ├── index.ts │ └── utils.ts ├── tsconfig.json └── tslint.json /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | node-version: [8.x, 10.x, 12.x] 11 | steps: 12 | - uses: actions/checkout@v1 13 | - name: Use Node.js ${{ matrix.node-version }} 14 | uses: actions/setup-node@v1 15 | with: 16 | node-version: ${{ matrix.node-version }} 17 | - name: npm install and build 18 | run: | 19 | npm install 20 | npm run build 21 | npm run build:prod 22 | env: 23 | CI: true 24 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v1 12 | - uses: actions/setup-node@v1 13 | with: 14 | node-version: 12 15 | registry-url: https://registry.npmjs.org/ 16 | - name: npm build 17 | run: | 18 | npm install 19 | npm run build 20 | npm run build:prod 21 | - run: | 22 | npm publish --access public 23 | env: 24 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib 3 | **/package-lock.json 4 | **/yarn.lock 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - stable 4 | cache: 5 | directories: 6 | - $HOME/.npm 7 | - node_modules 8 | install: 9 | - npm i --cache-min 600000 10 | script: 11 | - npm run build 12 | - npm run build:prod -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # types-vue 2 | 3 | Binding helpers for Vuex and vue-class-component on steroids 4 | 5 | ## Dependencies 6 | 7 | - [Vue](https://github.com/vuejs/vue) 8 | - [Vuex](https://github.com/vuejs/vuex) 9 | - [vue-class-component](https://github.com/vuejs/vue-class-component) 10 | 11 | ## Based on 12 | 13 | - [vue-property-decorator](https://github.com/kaorun343/vue-property-decorator) 14 | - [vuex-class](https://github.com/ktsn/vuex-class) 15 | - [vuex-module-decorators](https://github.com/championswimmer/vuex-module-decorators) 16 | 17 | ## Installation 18 | 19 | ```bash 20 | $ npm install --save types-vue 21 | # or 22 | $ yarn add types-vue 23 | ``` 24 | 25 | ## Usage 26 | 27 | There are these decorators for a component: 28 | 29 | - Component (from 'vue-class-component') 30 | - Prop 31 | - Watch 32 | - MapGetter 33 | - MapAction 34 | 35 | And these decorators for a vuex module: 36 | 37 | - Module 38 | - Getter 39 | - Mutation 40 | - Action 41 | 42 | ### Component 43 | 44 | #### @Prop 45 | 46 | This decorator adds the field to "props" component collection. It can be invoked in different ways: 47 | 48 | 1. Without parameters 49 | 2. With the type constructor (ex. Number, String, Boolean...) 50 | 3. With PropOptions: 51 | - type: type constructor (ex. Number, String, Boolean...) 52 | - required: boolean 53 | - default: defaul value of the prop 54 | - validator: a validator function 55 | 56 | ```js 57 | import { Vue, Component, Prop } from 'types-vue'; 58 | 59 | @Component 60 | export default class MyComponent extends Vue { 61 | @Prop() 62 | title!: string; 63 | 64 | @Prop(Number) 65 | count!: number; 66 | 67 | @Prop({ 68 | type: String, 69 | required: false, 70 | default: 'someone@mail.com', 71 | validator(value: string): boolean { 72 | return true; 73 | } 74 | }) 75 | email!: string; 76 | } 77 | ``` 78 | 79 | This is like: 80 | 81 | ```js 82 | export default { 83 | props: { 84 | title: { }, 85 | count: Number, 86 | email: { 87 | type: String, 88 | required: false, 89 | default: 'someone@mail.com', 90 | validator(value: string): boolean { 91 | return true; 92 | } 93 | }, 94 | } 95 | } 96 | ``` 97 | 98 | #### @Watch 99 | 100 | This decorator adds the method to "watch" component collection. It can be invoked in different ways: 101 | 102 | 1. With the name of the property to watch. 103 | 2. With the name of the property to watch and WatchOptions: 104 | - deep: boolean 105 | - inmediate: boolean 106 | 107 | ```js 108 | import { Vue, Component, Watch } from 'types-vue'; 109 | 110 | @Component 111 | export default class MyComponent extends Vue { 112 | title: string = 'hello'; 113 | counter: number = 0; 114 | 115 | @Watch('title') 116 | onTitleChanged(value: string): void { 117 | console.log('title is ' + value); 118 | } 119 | 120 | @Watch('counter', { deep: true, immediate: true }) 121 | onCounterChanged(value: number, oldValue: number): void { 122 | console.log('counter is ' + value.toString()); 123 | } 124 | } 125 | ``` 126 | 127 | This is like: 128 | 129 | ```js 130 | export default { 131 | data() { 132 | return { 133 | title: 'hello', 134 | counter: 0 135 | }; 136 | } 137 | watch: { 138 | 'title': { 139 | handler: 'onTitleChanged', 140 | immediate: false, 141 | deep: false 142 | }, 143 | 'counter': { 144 | handler: 'onCounterChanged', 145 | immediate: true, 146 | deep: true 147 | } 148 | }, 149 | methods: { 150 | onTitleChanged(value) { 151 | console.log('title is ' + value); 152 | }, 153 | onCounterChanged(value, oldValue) { 154 | console.log('counter is ' + value.toString()); 155 | } 156 | } 157 | } 158 | } 159 | ``` 160 | 161 | #### @Filter 162 | 163 | This decorator adds the method to "filters" component collection. 164 | 165 | ```js 166 | import { Vue, Component, Watch } from 'types-vue'; 167 | 168 | @Component 169 | export default class MyComponent extends Vue { 170 | 171 | @Filter() 172 | static withExclamation(value: string): string { 173 | return '!' + value; 174 | } 175 | } 176 | ``` 177 | 178 | This is like: 179 | 180 | ```js 181 | export default { 182 | filters: { 183 | withExclamation(value) { 184 | return '!' + value; 185 | } 186 | } 187 | } 188 | } 189 | ``` 190 | 191 | #### @MapGetter 192 | 193 | When you are using Vuex, this decorator adds the field to "computed" as a vuex mapGetter in the component. It can be invoked in different ways: 194 | 195 | 1. Without parameters 196 | 2. With MapGetterOptions: 197 | - namespace: the name of the module namespace 198 | 199 | ```js 200 | import { Vue, Component, MapGetter } from 'types-vue'; 201 | 202 | @Component 203 | export default class MyComponent extends Vue { 204 | @MapGetter() 205 | title: string; 206 | 207 | @MapGetter({ namespace: 'counter' }) 208 | count: number; 209 | } 210 | ``` 211 | 212 | This is like: 213 | 214 | ```js 215 | export default { 216 | computed: { 217 | ...mapGetters(['title']), 218 | ...mapGetters('counter', ['count']) 219 | } 220 | } 221 | ``` 222 | 223 | #### @MapAction 224 | 225 | When you are using Vuex, this decorator adds the field to "methods" as a vuex mapAction in the component. It can be invoked in different ways: 226 | 227 | 1. Without parameters 228 | 2. With MapActionOptions: 229 | - namespace: the name of the module namespace 230 | 231 | ```js 232 | import { Vue, Component, MapAction } from 'types-vue'; 233 | 234 | @Component 235 | export default class MyComponent extends Vue { 236 | @MapAction() 237 | changeTitle: any; 238 | 239 | @MapAction({ namespace: 'counter' }) 240 | increment: (val: number) => void; 241 | } 242 | ``` 243 | 244 | This is like: 245 | 246 | ```js 247 | export default { 248 | methods: { 249 | ...mapActtions(['changeTitle']), 250 | ...mapActtions('counter', ['increment']) 251 | } 252 | } 253 | ``` 254 | 255 | ### Vuex Module 256 | 257 | #### @Module 258 | 259 | If you are using Vuex, and you want to create a new module, you can use the Module decorator and extends the VuexModule class. You can call the Module decorator: 260 | 261 | 1. Without parameters 262 | 2. With ModuleOptions: 263 | - name: the namespace name of the module; 264 | - namespaced: a boolean value to set if you are going to use namespacing in this vuex module; 265 | 266 | ```js 267 | import { Module, VuexModule } from 'types-vue'; 268 | 269 | @Module({ namespaced: true }) 270 | export default class extends VuexModule { 271 | counter: number = 0; 272 | } 273 | ``` 274 | 275 | This is like: 276 | 277 | ```js 278 | export default { 279 | namespaced: true, 280 | state: { 281 | counter: 0 282 | } 283 | } 284 | ``` 285 | 286 | #### @Getter 287 | 288 | This decorator adds the method to "getters" vuex module collection. 289 | 290 | ```js 291 | import { Module, VuexModule, Getter } from 'types-vue'; 292 | 293 | @Module({ namespaced: true }) 294 | export default class extends VuexModule { 295 | _counter: number = 0; 296 | 297 | @Getter() 298 | counter(): number { 299 | return this._counter; 300 | } 301 | } 302 | ``` 303 | 304 | This is like: 305 | 306 | ```js 307 | export default { 308 | namespaced: true, 309 | state: { 310 | _counter: 0 311 | }, 312 | getters: { 313 | counter: function(state) { 314 | return state._counter; 315 | } 316 | } 317 | } 318 | ``` 319 | 320 | You can determine the access mode to the vuex store `Getter`: 321 | 322 | ```js 323 | import { Module, VuexModule, Getter } from 'types-vue'; 324 | 325 | @Module({ namespaced: true }) 326 | export default class extends VuexModule { 327 | _list: string[] = []]; 328 | 329 | @Getter({ mode: 'value'}) 330 | listValue(): string[] { 331 | return this._list; 332 | } 333 | 334 | @Getter({ mode: 'reference'}) 335 | listReference(): string[] { 336 | return this._list; 337 | } 338 | } 339 | ``` 340 | 341 | This is like: 342 | 343 | ```js 344 | export default { 345 | namespaced: true, 346 | state: { 347 | _counter: 0 348 | }, 349 | getters: { 350 | listValue: function(state) { 351 | return state._list; 352 | }, 353 | listReference: function(state) { 354 | return Object.assign({}, state._list); 355 | } 356 | } 357 | } 358 | ``` 359 | 360 | #### @Mutation 361 | 362 | This decorator adds the method to "mutations" vuex module collection. 363 | 364 | ```js 365 | import { Module, VuexModule, Mutation } from 'types-vue'; 366 | 367 | @Module({ namespaced: true }) 368 | export default class extends VuexModule { 369 | _counter: number = 0; 370 | 371 | @Mutation() 372 | increment(value: number): void { 373 | this._counter += value; 374 | } 375 | } 376 | ``` 377 | 378 | This is like: 379 | 380 | ```js 381 | export default { 382 | namespaced: true, 383 | state: { 384 | _counter: 0 385 | }, 386 | mutations: { 387 | increment: function(state, value) { 388 | state._counter += value; 389 | } 390 | } 391 | } 392 | ``` 393 | 394 | #### @Action 395 | 396 | This decorator adds the method to "actions" vuex module collection. 397 | 398 | ```js 399 | import { Module, VuexModule, Mutation, Action } from 'types-vue'; 400 | 401 | @Module({ namespaced: true }) 402 | export default class extends VuexModule { 403 | _counter: number = 0; 404 | 405 | @Mutation() 406 | increment(value: number): void { 407 | this._counter += value; 408 | } 409 | 410 | @Mutation() 411 | decrement(value: number): void { 412 | this._counter -= value; 413 | } 414 | 415 | @Action({ commit: 'increment' }) 416 | incr(value: number): number { 417 | if (value < 0) { 418 | return 0; 419 | } 420 | 421 | return value; 422 | } 423 | 424 | @Action({ useContext: true }) 425 | decr(context: ActionContext, value: number): void { 426 | if (value < 0) { 427 | return 0; 428 | } 429 | 430 | context.commit('decrement', value); 431 | } 432 | } 433 | ``` 434 | 435 | This is like: 436 | 437 | ```js 438 | export default { 439 | namespaced: true, 440 | state: { 441 | _counter: 0 442 | }, 443 | mutations: { 444 | increment: function(state, value) { 445 | state._counter += value; 446 | }, 447 | decrement: function(state, value) { 448 | state._counter += value; 449 | } 450 | }, 451 | actions: { 452 | incr: function(context, value) { 453 | let result = (value) => { 454 | if (value < 0) { 455 | return 0; 456 | } 457 | 458 | return value; 459 | }(); 460 | context.commit('increment', result); 461 | }, 462 | decr: function(context, value) { 463 | let result = (value) => { 464 | if (value < 0) { 465 | return 0; 466 | } 467 | 468 | return value; 469 | }(); 470 | context.commit('decrement', result); 471 | } 472 | } 473 | } 474 | ``` 475 | 476 | ## Examples 477 | 478 | You can see a complete demo project in the demo folder of this project. The code is like the following: 479 | 480 | ### Component 481 | 482 | ```js 483 | // header-panel.ts 484 | import { Vue, Component, Prop, Watch } from 'types-vue'; 485 | 486 | @Component 487 | export default class Header extends Vue { 488 | @Prop() 489 | title!: string; 490 | 491 | @Watch('title') 492 | onTitleChanged(value: string) { 493 | console.log('title is ' + value); 494 | } 495 | } 496 | ``` 497 | 498 | ```html 499 | 500 | 505 | 506 | ``` 507 | 508 | ```js 509 | // demo.ts 510 | import { Vue, Component, Watch } from 'types-vue'; 511 | import HeaderPanel from './header-panel.vue'; 512 | 513 | @Component({ 514 | components: { 515 | 'header-panel': HeaderPanel 516 | } 517 | }) 518 | export default class NotificationPanelComponent extends Vue { 519 | title: string = 'Hello Vue from typescript'; 520 | 521 | @Watch('title') 522 | onTitleChanged(value: string) { 523 | console.log('in demo the title is ' + value); 524 | } 525 | } 526 | ``` 527 | 528 | ```html 529 | 530 | 539 | 540 | ``` 541 | 542 | ### Vuex 543 | 544 | ```js 545 | // counterModule.ts 546 | import { Module, VuexModule, Mutation, Action, Getter } from 'types-vue'; 547 | 548 | @Module({ namespaced: true }) 549 | export default class extends VuexModule { 550 | _counter: number = 0; 551 | 552 | @Getter() 553 | counter(): number { 554 | return this._counter; 555 | } 556 | 557 | @Mutation() 558 | increment(value: number): void { 559 | this._counter += value; 560 | } 561 | 562 | @Mutation() 563 | decrement(value: number): void { 564 | this._counter -= value; 565 | } 566 | 567 | @Action({ commit: 'increment' }) 568 | incr(value: number): number { 569 | if (value < 0) { 570 | return 0; 571 | } 572 | 573 | return value; 574 | } 575 | 576 | @Action({ commit: 'decrement' }) 577 | decr(value: number): number { 578 | if (value < 0) { 579 | return 0; 580 | } 581 | 582 | return value; 583 | } 584 | } 585 | ``` 586 | 587 | ```js 588 | // store.ts 589 | import Vue from 'vue'; 590 | import Vuex, { Store } from 'vuex'; 591 | import counter from './counterModule'; 592 | 593 | Vue.use(Vuex); 594 | 595 | const store = new Store({ 596 | state: {}, 597 | modules: { 598 | counter 599 | } 600 | }); 601 | 602 | export default store; 603 | ``` 604 | 605 | ```js 606 | // counter-panel.ts 607 | import { Vue, Component, MapGetter, MapAction } from 'types-vue'; 608 | 609 | @Component 610 | export default class NotificationPanelComponent extends Vue { 611 | @MapGetter({ namespace: 'counter' }) 612 | counter; 613 | 614 | @MapAction({ namespace: 'counter' }) 615 | incr; 616 | } 617 | ``` 618 | 619 | ```html 620 | 621 | 627 | 628 | ``` 629 | 630 | ## License 631 | 632 | MIT -------------------------------------------------------------------------------- /demo/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw? 22 | -------------------------------------------------------------------------------- /demo/README.md: -------------------------------------------------------------------------------- 1 | # types-vue-demo 2 | 3 | A Vue.js with Typescript demo project 4 | 5 | ## Project setup 6 | 7 | ```bash 8 | npm install 9 | ``` 10 | 11 | ### Compiles and hot-reloads for development 12 | 13 | ```bash 14 | npm run serve 15 | ``` 16 | 17 | ### Compiles and minifies for production 18 | 19 | ```bash 20 | npm run build 21 | ``` 22 | -------------------------------------------------------------------------------- /demo/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "types-vue-demo", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build" 8 | }, 9 | "dependencies": { 10 | "core-js": "^2.6.5", 11 | "vue": "^2.6.10", 12 | "types-vue": "^1.1.0", 13 | "vue-router": "^3.0.3", 14 | "vuex": "^3.0.1" 15 | }, 16 | "devDependencies": { 17 | "@vue/cli-plugin-babel": "^3.0.1", 18 | "@vue/cli-plugin-typescript": "^3.0.1", 19 | "@vue/cli-service": "^3.0.1", 20 | "typescript": "3.2.4", 21 | "vue-template-compiler": "^2.6.10" 22 | }, 23 | "postcss": { 24 | "plugins": { 25 | "autoprefixer": {} 26 | } 27 | }, 28 | "browserslist": [ 29 | "> 1%", 30 | "last 2 versions" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /demo/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fernandoescolar/types-vue/628a640ecd11f61aee761a012c59a0e4c5d687da/demo/public/favicon.ico -------------------------------------------------------------------------------- /demo/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Demo 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /demo/src/components/app.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import { Component } from 'types-vue'; 3 | 4 | @Component 5 | export default class AppComponent extends Vue { 6 | } 7 | -------------------------------------------------------------------------------- /demo/src/components/app.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /demo/src/components/home/components/counter-panel/counter-panel.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import { Component, MapGetter, MapAction } from 'types-vue'; 3 | 4 | @Component 5 | export default class NotificationPanelComponent extends Vue { 6 | @MapGetter({ namespace: 'counter' }) 7 | counter; 8 | 9 | @MapGetter({ namespace: 'counter' }) 10 | listValue; 11 | 12 | @MapGetter({ namespace: 'counter' }) 13 | listReference; 14 | 15 | @MapAction({ namespace: 'counter' }) 16 | incr; 17 | 18 | @MapAction({ namespace: 'counter' }) 19 | decr; 20 | 21 | addItemListValue(): void { 22 | // it should not modify the store object 23 | this.listValue.push('hello'); 24 | } 25 | 26 | addItemListReference(): void { 27 | // it should do modify the store object 28 | this.listReference.push('hello'); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /demo/src/components/home/components/counter-panel/counter-panel.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | -------------------------------------------------------------------------------- /demo/src/components/home/components/header-panel/header-panel.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import { Component, Prop, Watch } from 'types-vue'; 3 | 4 | @Component 5 | export default class Header extends Vue { 6 | @Prop(String) 7 | title!: string; 8 | 9 | @Watch('title') 10 | onTitleChanged(value: string) { 11 | console.log('in header the title is ' + value); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /demo/src/components/home/components/header-panel/header-panel.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | -------------------------------------------------------------------------------- /demo/src/components/home/home.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import { Component, Watch, Filter } from 'types-vue'; 3 | import CounterPanel from './components/counter-panel/counter-panel.vue'; 4 | import HeaderPanel from './components/header-panel/header-panel.vue'; 5 | 6 | @Component({ 7 | components: { 8 | 'counter-panel': CounterPanel, 9 | 'header-panel': HeaderPanel 10 | } 11 | }) 12 | export default class NotificationPanelComponent extends Vue { 13 | title: string = 'Hello Vue from typescript'; 14 | 15 | @Watch('title') 16 | onTitleChanged(value: string) { 17 | console.log('in demo the title is ' + value); 18 | } 19 | 20 | @Filter() 21 | static changeIt(text: string): string { 22 | return '!' + text; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /demo/src/components/home/home.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | -------------------------------------------------------------------------------- /demo/src/main.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import router from './router'; 3 | import store from './store/index'; 4 | import App from './components/app.vue'; 5 | 6 | const vue = new Vue({ 7 | el: '#app', 8 | router: router, 9 | store: store, 10 | render: h => h(App), 11 | }); -------------------------------------------------------------------------------- /demo/src/router.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Router from 'vue-router'; 3 | import HomePage from './components/home/home.vue'; 4 | 5 | Vue.use(Router); 6 | 7 | const router = new Router({ 8 | mode: 'history', 9 | routes: [ 10 | { path: '/', name: 'home', component: HomePage } 11 | ] 12 | }); 13 | 14 | export default router; 15 | -------------------------------------------------------------------------------- /demo/src/shims-tsx.d.ts: -------------------------------------------------------------------------------- 1 | import Vue, { VNode } from 'vue' 2 | 3 | declare global { 4 | namespace JSX { 5 | // tslint:disable no-empty-interface 6 | interface Element extends VNode {} 7 | // tslint:disable no-empty-interface 8 | interface ElementClass extends Vue {} 9 | interface IntrinsicElements { 10 | [elem: string]: any 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /demo/src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import Vue from 'vue' 3 | export default Vue 4 | } 5 | -------------------------------------------------------------------------------- /demo/src/store/counterModule.ts: -------------------------------------------------------------------------------- 1 | import { Module, VuexModule, Mutation, Action, Getter } from 'types-vue'; 2 | import { ActionContext } from 'vuex'; 3 | 4 | @Module({ namespaced: true }) 5 | export default class extends VuexModule { 6 | _counter: number = 0; 7 | _list: string[] = []; 8 | 9 | @Getter() 10 | counter(): number { 11 | return this._counter; 12 | } 13 | 14 | @Getter({ mode: 'value'}) 15 | listValue(): string[] { 16 | return this._list; 17 | } 18 | 19 | @Getter({ mode: 'reference'}) 20 | listReference(): string[] { 21 | return this._list; 22 | } 23 | 24 | @Mutation() 25 | increment(value: number): void { 26 | this._counter += value; 27 | } 28 | 29 | @Mutation() 30 | decrement(value: number): void { 31 | this._counter -= value; 32 | } 33 | 34 | @Action({ commit: 'increment' }) 35 | incr(value: number): number { 36 | if (value < 0) { 37 | return 0; 38 | } 39 | 40 | return value; 41 | } 42 | 43 | @Action({ commit: 'decrement', useContext: true }) 44 | decr(context: ActionContext, value: number): number { 45 | if (value < 0) { 46 | return 0; 47 | } 48 | 49 | return value; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /demo/src/store/index.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuex, { Store } from 'vuex'; 3 | import counter from './counterModule'; 4 | 5 | Vue.use(Vuex); 6 | 7 | const store = new Store({ 8 | state: {}, 9 | modules: { 10 | counter 11 | } 12 | }); 13 | 14 | export default store; 15 | -------------------------------------------------------------------------------- /demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": false, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "experimentalDecorators": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "sourceMap": true, 13 | "baseUrl": ".", 14 | "types": [ 15 | "webpack-env" 16 | ], 17 | "paths": { 18 | "@/*": [ 19 | "src/*" 20 | ] 21 | }, 22 | "lib": [ 23 | "esnext", 24 | "dom", 25 | "dom.iterable", 26 | "scripthost" 27 | ] 28 | }, 29 | "include": [ 30 | "src/**/*.ts", 31 | "src/**/*.tsx", 32 | "src/**/*.vue", 33 | "tests/**/*.ts", 34 | "tests/**/*.tsx" 35 | ], 36 | "exclude": [ 37 | "node_modules" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "types-vue", 3 | "version": "1.1.0", 4 | "description": "Vue typescript decorators", 5 | "main": "lib/types-vue.umd.js", 6 | "types": "lib/types-vue.d.ts", 7 | "scripts": { 8 | "build": "tsc", 9 | "build:prod": "rollup -c" 10 | }, 11 | "files": [ 12 | "lib" 13 | ], 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/fernandoescolar/types-vue" 17 | }, 18 | "keywords": [ 19 | "vue", 20 | "typescript", 21 | "decorators" 22 | ], 23 | "author": { 24 | "name": "Fernando Escolar", 25 | "email": "f.escolar@hotmail.com", 26 | "url": "http://fernandoescolar.github.io/" 27 | }, 28 | "license": "MIT", 29 | "dependencies": { 30 | "vue-class-component": "^6.2.0" 31 | }, 32 | "devDependencies": { 33 | "rollup": "^0.50.0", 34 | "rollup-plugin-commonjs": "^8.2.6", 35 | "rollup-plugin-typescript": "^0.8.1", 36 | "typescript": "^2.6.1", 37 | "vue": "^2.5.16", 38 | "vuex": "^3.0.1" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | input: 'lib/types-vue.js', 3 | output: { 4 | name: 'VueTypescript', 5 | file: 'lib/types-vue.umd.js', 6 | format: 'umd', 7 | globals: { 8 | 'vue': 'Vue', 9 | 'vuex': 'Vuex', 10 | 'vue-class-component': 'VueClassComponent' 11 | } 12 | }, 13 | plugins: [ 14 | { "process.env.NODE_ENV" : "production" } 15 | ], 16 | external: [ 17 | 'vue', 'vuex', 'vue-class-component' 18 | ] 19 | } -------------------------------------------------------------------------------- /src/component/Filter.ts: -------------------------------------------------------------------------------- 1 | import { createExDecorator } from './utils'; 2 | 3 | export function Filter(): MethodDecorator { 4 | return createExDecorator((componentOptions, k, descriptor) => { 5 | if (typeof componentOptions.filters !== 'object') { 6 | componentOptions.filters = Object.create(null); 7 | } 8 | 9 | (componentOptions.filters as any)[k] = descriptor.value; 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /src/component/Prop.ts: -------------------------------------------------------------------------------- 1 | import { PropOptions } from 'vue'; 2 | import { createDecorator } from 'vue-class-component'; 3 | import { Constructor } from 'vue/types/options'; 4 | 5 | export function Prop(options: (PropOptions | Constructor[] | Constructor) = {}): PropertyDecorator { 6 | return createDecorator((componentOptions, k) => { 7 | if (typeof componentOptions.props !== 'object') { 8 | componentOptions.props = Object.create(null); 9 | } 10 | 11 | (componentOptions.props as any)[k] = options; 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /src/component/Watch.ts: -------------------------------------------------------------------------------- 1 | import { WatchOptions } from 'vue'; 2 | import { createDecorator } from 'vue-class-component'; 3 | 4 | export function Watch(path: string, options: WatchOptions = {}): MethodDecorator { 5 | const { deep = false, immediate = false } = options; 6 | 7 | return createDecorator((componentOptions, handler) => { 8 | if (typeof componentOptions.watch !== 'object') { 9 | componentOptions.watch = Object.create(null); 10 | } 11 | 12 | (componentOptions.watch as any)[path] = { handler, deep, immediate }; 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /src/component/index.ts: -------------------------------------------------------------------------------- 1 | import { Filter } from './Filter'; 2 | import { Prop } from './Prop'; 3 | import { Watch } from './Watch'; 4 | 5 | export { 6 | Filter, 7 | Prop, 8 | Watch 9 | } 10 | -------------------------------------------------------------------------------- /src/component/utils.ts: -------------------------------------------------------------------------------- 1 | import Vue, { ComponentOptions } from "vue"; 2 | import { createDecorator, VueDecorator } from 'vue-class-component'; 3 | 4 | export function createExDecorator (factory: (options: ComponentOptions, key: string, descriptor: TypedPropertyDescriptor) => void): VueDecorator { 5 | return (target: Vue | typeof Vue, key?: any, descriptor?: any) => { 6 | const c = createDecorator((o: ComponentOptions, k: string) => { 7 | return factory(o, k, descriptor); 8 | }); 9 | 10 | return c(target, key); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/types-vue.ts: -------------------------------------------------------------------------------- 1 | import Vue, {ComponentOptions} from 'vue'; 2 | import Component, { createDecorator, VueDecorator, mixins } from 'vue-class-component'; 3 | 4 | export * from './component/index'; 5 | export * from './vuex/index'; 6 | export { Vue, ComponentOptions, Component, createDecorator, VueDecorator, mixins }; 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/vuex/Action.ts: -------------------------------------------------------------------------------- 1 | import { Action as Act, ActionContext, Payload } from 'vuex'; 2 | import { createVuexDecorator } from "./utils"; 3 | 4 | export interface ActionOptions { 5 | commit?: string; 6 | useContext?: boolean; 7 | } 8 | 9 | function checkOptionsCommit(options: ActionOptions, context: ActionContext, value: any) { 10 | if (options && options.commit) { 11 | context.commit(options.commit, value); 12 | } 13 | } 14 | 15 | function createActionFunction(key: string, value: Function, options: ActionOptions): Act { 16 | const action: Act = function (context: ActionContext, payload: Payload) { 17 | try { 18 | const arrPayload = Array.isArray(payload) ? payload as any[] : [ payload ]; 19 | const parameters = options.useContext ? [context, ...arrPayload] : arrPayload; 20 | const callResult = value.call(context, ...parameters); 21 | if (Promise.resolve(callResult) !== callResult) { // it is not a promise 22 | checkOptionsCommit(options, context, callResult); 23 | return callResult; 24 | } 25 | 26 | return new Promise((resolve, reject) => { 27 | callResult.then( 28 | result => { 29 | checkOptionsCommit(options, context, result); 30 | resolve(result); 31 | }, 32 | error => { 33 | reject(error); 34 | }) 35 | }); 36 | } catch (ex) { 37 | console.error('Could not perform action ' + key.toString() + ' - ' + ex ); 38 | } 39 | }; 40 | 41 | return action; 42 | } 43 | 44 | export function Action(options: ActionOptions = {}): MethodDecorator { 45 | return createVuexDecorator((componentOptions, k, description) => { 46 | if (typeof componentOptions.actions !== 'object') { 47 | componentOptions.actions = Object.create(null); 48 | } 49 | 50 | (componentOptions.actions as any)[k] = createActionFunction(k, description.value, options); 51 | }); 52 | } 53 | -------------------------------------------------------------------------------- /src/vuex/Getter.ts: -------------------------------------------------------------------------------- 1 | import { Getter as Get } from 'vuex'; 2 | import { createVuexDecorator } from './utils'; 3 | 4 | export interface GetterOptions { 5 | mode?: 'reference' | 'value'; 6 | } 7 | 8 | function createGetterFunction(value: Function, options: GetterOptions): Get { 9 | const getter: Get = function (state: T) { 10 | let referenceResult = value.call(state); 11 | if(options.mode === 'reference' && typeof referenceResult !== 'object'){ 12 | throw Error('The property is not an object so it cannot be passed as a reference.'); 13 | } 14 | else if (options.mode === 'value' && typeof referenceResult === 'object') { 15 | let valueResult = Object.assign({}, referenceResult); 16 | return valueResult; 17 | } 18 | 19 | return referenceResult; 20 | }; 21 | return getter; 22 | } 23 | 24 | export function Getter(options: GetterOptions = {}): MethodDecorator { 25 | return createVuexDecorator((componentOptions, k, description) => { 26 | if (typeof componentOptions.getters !== 'object') { 27 | componentOptions.getters = Object.create(null); 28 | } 29 | 30 | (componentOptions.getters as any)[k] = createGetterFunction(description.value, options); 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /src/vuex/MapAction.ts: -------------------------------------------------------------------------------- 1 | import { mapActions } from 'vuex'; 2 | import { createDecorator } from 'vue-class-component'; 3 | 4 | export interface MapActionOptions { 5 | namespace?: string; 6 | } 7 | 8 | export function MapAction(options: MapActionOptions = {}): PropertyDecorator { 9 | const { namespace = undefined } = options; 10 | 11 | return createDecorator((componentOptions, k) => { 12 | if (typeof componentOptions.methods !== 'object') { 13 | componentOptions.methods = Object.create(null); 14 | } 15 | 16 | const mapArray = [ k ]; 17 | (componentOptions.methods as any)[k] = namespace !== undefined 18 | ? mapActions(namespace, mapArray)[k] 19 | : mapActions(mapArray)[k]; 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /src/vuex/MapGetter.ts: -------------------------------------------------------------------------------- 1 | import { mapGetters } from 'vuex'; 2 | import { createDecorator } from 'vue-class-component'; 3 | 4 | export interface MapGetterOptions { 5 | namespace?: string; 6 | } 7 | 8 | export function MapGetter(options: MapGetterOptions = {}): PropertyDecorator { 9 | const { namespace = undefined } = options; 10 | 11 | return createDecorator((componentOptions, k) => { 12 | if (typeof componentOptions.computed !== 'object') { 13 | componentOptions.computed = Object.create(null); 14 | } 15 | 16 | const mapArray = [ k ]; 17 | (componentOptions.computed as any)[k] = namespace !== undefined 18 | ? mapGetters(namespace, mapArray)[k] 19 | : mapGetters(mapArray)[k]; 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /src/vuex/Module.ts: -------------------------------------------------------------------------------- 1 | import { ActionTree, GetterTree, Module as Mod, ModuleTree, MutationTree } from 'vuex'; 2 | 3 | export interface ModuleOptions { 4 | name?: string; 5 | namespaced?: boolean; 6 | } 7 | 8 | function isVuexModuleKey(key: string): boolean { 9 | return ['namespaced', 'state', 'getters', 'actions', 'mutations', 'modules'].indexOf(key) >= 0; 10 | } 11 | 12 | function shouldAddPropertyToState(state: any, key: string): boolean { 13 | return state.hasOwnProperty(key) && 14 | typeof state[key] !== 'function' && 15 | !isVuexModuleKey(key); 16 | } 17 | 18 | function moduleDecoratorFactory (modOrOpt: ModuleOptions | Function & Mod) { 19 | return function (constructor: TFunction): TFunction | void { 20 | const module: Function & Mod = constructor; 21 | const state = new (module.prototype.constructor)({}); 22 | 23 | if (typeof module.state !== 'object') { 24 | module.state = Object.create(null); 25 | } 26 | 27 | module.namespaced = modOrOpt && modOrOpt.namespaced; 28 | Object.keys(state).forEach((key: string) => { 29 | if (shouldAddPropertyToState(state, key)) { 30 | (module.state as any)[key] = state[key]; 31 | } 32 | }); 33 | }; 34 | } 35 | 36 | export class VuexModule, R = any> implements Mod { 37 | static namespaced?: boolean; 38 | static state?: any | (() => any); 39 | static getters?: GetterTree; 40 | static actions?: ActionTree; 41 | static mutations?: MutationTree; 42 | static modules?: ModuleTree; 43 | 44 | namespaced?: boolean; 45 | state?: S | (() => S); 46 | getters?: GetterTree; 47 | actions?: ActionTree; 48 | mutations?: MutationTree; 49 | modules?: ModuleTree; 50 | 51 | constructor(module: Mod) { 52 | this.actions = module.actions; 53 | this.mutations = module.mutations; 54 | this.state = module.state; 55 | this.getters = module.getters; 56 | this.namespaced = module.namespaced; 57 | this.modules = module.modules; 58 | } 59 | } 60 | 61 | 62 | export function Module (module: Function & Mod): void; 63 | export function Module (options: ModuleOptions): ClassDecorator; 64 | export function Module (modOrOpt: ModuleOptions | Function & Mod) { 65 | if (typeof modOrOpt === 'function') { 66 | return moduleDecoratorFactory({})(modOrOpt); 67 | } else { 68 | return moduleDecoratorFactory(modOrOpt); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/vuex/Mutation.ts: -------------------------------------------------------------------------------- 1 | import { Mutation as Mut, Payload } from 'vuex'; 2 | import { createVuexDecorator } from './utils'; 3 | 4 | function createMutationFunction(value: Function): Mut { 5 | const mutation: Mut = (state: T, payload: Payload) => { 6 | value.call(state, payload); 7 | }; 8 | return mutation; 9 | } 10 | 11 | export function Mutation(): MethodDecorator { 12 | return createVuexDecorator((componentOptions, k, description) => { 13 | if (typeof componentOptions.mutations !== 'object') { 14 | componentOptions.mutations = Object.create(null); 15 | } 16 | 17 | (componentOptions.mutations as any)[k] = createMutationFunction(description.value); 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /src/vuex/index.ts: -------------------------------------------------------------------------------- 1 | import { VuexModule, Module } from './Module'; 2 | import { Action } from './Action'; 3 | import { Mutation } from './Mutation'; 4 | import { Getter } from './Getter'; 5 | import { MapAction } from './MapAction'; 6 | import { MapGetter } from './MapGetter'; 7 | 8 | export { 9 | VuexModule, 10 | Module, 11 | Action, 12 | Mutation, 13 | Getter, 14 | MapAction, 15 | MapGetter 16 | }; 17 | -------------------------------------------------------------------------------- /src/vuex/utils.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import { Module } from 'vuex'; 3 | 4 | export function createVuexDecorator(factory: (options: Module, key: string, descriptor: TypedPropertyDescriptor) => void): any { 5 | return (target: Vue | typeof Vue, key: any, descriptor: TypedPropertyDescriptor) => { 6 | const options = typeof target === 'function' 7 | ? target as Module 8 | : target.constructor as Module; 9 | factory(options, key, descriptor); 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "experimentalDecorators": true, 5 | "lib": [ 6 | "es5", 7 | "es2015", 8 | "dom", 9 | "scripthost" 10 | ], 11 | "module": "es2015", 12 | "moduleResolution": "node", 13 | "noImplicitAny": false, 14 | "skipDefaultLibCheck": true, 15 | "sourceMap": false, 16 | "strict": true, 17 | "strictFunctionTypes": false, 18 | "declaration": true, 19 | "outDir": "./lib" 20 | }, 21 | "exclude": [ 22 | "dist", 23 | "node_modules" 24 | ], 25 | "include": [ 26 | "./src/**/*" 27 | ] 28 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "align": [ 4 | false 5 | ], 6 | "array-type": false, 7 | "arrow-parens": false, 8 | "ban": false, 9 | "class-name": true, 10 | "comment-format": [ 11 | true, 12 | "check-space", 13 | { 14 | "ignore-words": [ 15 | "TODO" 16 | ] 17 | } 18 | ], 19 | "curly": false, 20 | "eofline": true, 21 | "forin": true, 22 | "indent": [true, "spaces", 4], 23 | "interface-name": [ 24 | false 25 | ], 26 | "jsdoc-format": true, 27 | "label-position": true, 28 | "max-line-length": [ 29 | true, 30 | 140 31 | ], 32 | "member-access": false, 33 | "member-ordering": [ 34 | true, 35 | { 36 | "order": [{ 37 | "kinds": [ 38 | "public-static-field", 39 | "protected-static-field", 40 | "public-static-method", 41 | "protected-static-method" 42 | ], 43 | "name": "static non-private" 44 | }, 45 | "static-field", 46 | "instance-field", 47 | "constructor", 48 | "public-instance-method", 49 | "protected-instance-method", 50 | "private-instance-method" 51 | ] 52 | } 53 | ], 54 | "no-any": false, 55 | "no-arg": true, 56 | "no-bitwise": true, 57 | "no-conditional-assignment": true, 58 | "no-consecutive-blank-lines": [ 59 | true, 60 | 2 61 | ], 62 | "no-console": [ 63 | true, 64 | "debug", 65 | "info", 66 | "time", 67 | "timeEnd", 68 | "trace" 69 | ], 70 | "no-construct": true, 71 | "no-debugger": true, 72 | "no-duplicate-variable": true, 73 | "no-empty": false, 74 | "no-eval": true, 75 | "no-implicit-dependencies": false, 76 | "no-inferrable-types": [ 77 | false 78 | ], 79 | "no-internal-module": true, 80 | "no-irregular-whitespace": true, 81 | "no-null-keyword": false, 82 | "no-require-imports": false, 83 | "no-shadowed-variable": true, 84 | "no-string-literal": false, 85 | "no-submodule-imports": false, 86 | "no-switch-case-fall-through": true, 87 | "no-trailing-whitespace": true, 88 | "no-unused-expression": true, 89 | "no-var-keyword": true, 90 | "no-var-requires": false, 91 | "object-literal-shorthand": false, 92 | "object-literal-sort-keys": false, 93 | "one-line": [ 94 | false, 95 | "check-open-brace", 96 | "check-catch", 97 | "check-else", 98 | "check-finally", 99 | "check-whitespace" 100 | ], 101 | "only-arrow-functions": false, 102 | "ordered-imports": false, 103 | "prefer-const": true, 104 | "prefer-object-spread": false, 105 | "quotemark": [ 106 | true, 107 | "single", 108 | "avoid-escape" 109 | ], 110 | "radix": false, 111 | "semicolon": [ 112 | true, 113 | "always" 114 | ], 115 | "switch-default": true, 116 | "trailing-comma": [ 117 | true, 118 | { 119 | "multiline": false, 120 | "singleline": "never" 121 | } 122 | ], 123 | "triple-equals": [ 124 | true, 125 | "allow-null-check" 126 | ], 127 | "typedef-whitespace": [ 128 | true, 129 | { 130 | "call-signature": "nospace", 131 | "index-signature": "nospace", 132 | "parameter": "nospace", 133 | "property-declaration": "nospace", 134 | "variable-declaration": "nospace" 135 | }, 136 | { 137 | "call-signature": "space", 138 | "index-signature": "space", 139 | "parameter": "space", 140 | "property-declaration": "space", 141 | "variable-declaration": "space" 142 | } 143 | ], 144 | "variable-name": [ 145 | true, 146 | "check-format", 147 | "allow-leading-underscore", 148 | "ban-keywords", 149 | "allow-pascal-case" 150 | ], 151 | "whitespace": [ 152 | true, 153 | "check-branch", 154 | "check-decl", 155 | "check-operator", 156 | "check-separator", 157 | "check-type", 158 | "check-preblock", 159 | "check-type-operator", 160 | "check-module", 161 | "check-rest-spread" 162 | ] 163 | }, 164 | "rulesDirectory": [ 165 | "node_modules/tslint" 166 | ] 167 | } --------------------------------------------------------------------------------