• Stars
    star
    218
  • Rank 181,805 (Top 4 %)
  • Language
    TypeScript
  • License
    MIT License
  • Created about 8 years ago
  • Updated almost 3 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A modern, type-safe, idiomatic Vue binding library

Awesome Vue TS Build Status


screenshot

Try av-ts in your browser!

Why:

Awesome Vue.TS aims at getting type safety as much as possible, while still keeping TypeScript concise and idiomatic. To achieve this, av-ts exploits many techniques, tricks and hacks in TypeScript, which makes av-ts a good tour of TypeScript features.

Note: The target vue version is 2.0.

You can read more for av-ts' raison d'etre here

Quick start

  1. Try av-ts in your browser!

  2. use template by vue-cli

npm install vue-cli -g
vue init HerringtonDarkholme/av-ts-template myproject
cd myproject
npm install
npm run dev

Usage:

  1. component is declared via a decorated class. extends should work. (mixin support is added in 0.3.0!)

  2. data, methods and computed can be declared by property initializer, member methods and property accessors in class body, respectively.

  3. props are declared via @Prop decorator and property initializer.

  4. render and life cycle handler like created is declared by decorated methods. They are declared in class body because these handlers use this. But you cannot invoke them on the instance itself. So they are decorated to remind users. When declaring custom methods, you should avoid these reserved names.

  5. watch handlers are declared by @Watch(propName) decorator, handler type is checked by keyof lookup type.

  6. All other options are considered as component's meta info. So users should declare them in the @Component decorator function.

  7. You can also extend av-ts by Component.registering new decorators. Useful for libraries like Vuex, Vue-router.

Example:

import {
  Component, Prop, Watch, Lifecycle, p
} from 'av-ts'

// vue options in `Component` decorator
@Component({
  filters: {},
  name: 'my-component',
  delimiter: ['{{', '}}'],
})
export class MyComponent extends Vue { // extends Vue or your own component
  // instance variable is in `data`
  myData = '123'

  // props declaration
  @Prop myProp = p({
    type: Object,
    required: true,
    default() {
      return {a: 123, b: 456}
    }
  })

  // method is `method`
  myMethod() {
    console.log('myMethod called!')
  }

  // accessor is `computed`
  get myGetter() {
    return this.myProp
  }

  // watch handler is declared by decorator
  myWatchee = 'watch me!'
  @Watch('myWatchee')
  handler(newVal, oldVal) {
    console.log(this.myWatchee + 'changed!')
  }

  // lifecycle hook is speical so it is decorated
  @Lifecycle beforeCreate() {}
}

which is equivalent to

let MyComponent = Vue.extend({
  filters: {},
  name: 'my-component',
  delimiter: ['{{', '}}'],
  data() {
    return {
      myData: '123',
      myWatchee: 'watch me!'
    }
  },
  props: {
    myProp: {
      type: Object,
      required: true,
      default() {
        return {a: 123, b: 456}
      }
    }
  },
  methods: {
    myMethod() {
      console.log('my method called!')
    }
  },
  computed: {
    myGetter: {
      get() {
        return this.myProp
      }
    }
  },
  watch: {
    myWatchee() {
      console.log(this.myWatchee + 'changed!')
    }
  },
  beforeCreate() {}
})

mixin examples

Contrary to other libraries, av-ts supports first class Mixin! Example adapted from here

// define mixin trait by `Trait` decorator
@Trait class VegetableSearchable extends Vue {
  vegetableName = 'tomato'
  searchVegetable() { alert('find vegi!')}
}

@Trait class FruitSearchable extends Vue {
  vegetableName = 'apple'
  searchVegetable() { alert('find fruits!')}
}

// Mixin them!
@Component
class App extends Mixin(VegetableSearchable, FruitSearchable) {}

Voila! No implements, No repeating code. And it looks like real mixins in ES6.

N.B.: Requires TypeScript 2.2.

TSX example

You need to first understand how TypeScript checkes JSX. https://www.typescriptlang.org/docs/handbook/jsx.html You also need to know the difference between VueJSX and React JSX. https://github.com/vuejs/babel-plugin-transform-vue-jsx

import Foo from './foo.vue'

@Component
class Bar extends Vue {
  // $props is JSX.ElementAttributesProperty
  $props: {
    name: string
  }
  defaultName = 'John'
  render() {
    return (<Foo><Bar name={this.defaultName}>name attribute is required</Bar></Foo>)
  }
}

API

For full type signature, please refer to av-ts.d.ts. They are most up-to-date.

Class Decorators


Component


Type: ClassDecorator | (option) => ClassDecorator

It can be directly applied on component class as decorator, or take one option argument and return a decorator function.

@Component
class VueComp extends Vue {}

@Component({
  directives: {},
  components: {},
  filters: {},
  name: 'my-awesome-component',
  delimiters: ['{{', '}}'],
})
class MyComponent extends Vue {}

Trait


Type: ClassDecorator | (option) => ClassDecorator

An alias of Component,used for defining Vue traits to be mixed in. At runtime, these decorators transform constructor to vue option and then feed to Vue.extend. So there is no semantic difference between Component and Trait. Placing Component on a class to be used as mixin just feels too strange. This alias is solely for API aesthetic.

To use a Trait, declare a class that extends Mixin(...Traits). See example in Mixin section.

Property Decorators


Prop


Type: PropertyDecorator

Decorated properties should be the return value of utility function p. p is a function takes property option and return a fake type placeholder that will specify the property type. The fake type placeholder, at runtime, is just the config option object you feed to the argument.

@Prop
myProp = p({
  type: Number,
  default: 123
})

// p(option) returns a `number` type placeholder
// so the following code compiles
var num: number = p({
  type: Number
})

// will print {type: Number}
console.log(num)

// you can also use a shorthand form of `p`
@Prop shortHand = p(String)

Watch


Same as vue-typescript, @Watch is applied to a watched handler. Watch takes watchee name, or an array of key-path to a nested property, as the first argument, and an optional config object as the second one.

// watch handler is declared by decorator
properyBeingWatched = 123
@Watch('properyBeingWatched', {deep: true})
handler(newVal, oldVal) {
  console.log('the delta is ' + (newVal - oldVal))
}

// the key path length is 4 at most
@Watch(['nested', 'path', 'property'])
handler(newVal, oldVal) {
  console.log('the delta is ' + (newVal - oldVal))
}
// ....

is equivalent to

watch: {
  properyBeingWatched: {
    handler: function(newVal, oldVal) {
      console.log('the delta is ' + (newVal - oldVal))
    },
    deep: true
  }
}

Lifecycle and Render


Type: TypedPropertyDecorator

mark decorated methods as special hooks in vue. Caveat: You cannot call lifecycle/render in other methods.

// lifecycle hook is speical so it is decorated
@Lifecycle mounted() {
  console.log('called in lifecycle code!')
}

// this decorator can only decorate method with name same as lifecycle
// @Lifecycle willNotCompile() {}

Lifecycle from vue-router is also supported as

import {
  Component, Lifecycle, NextFunc, NextFuncVm, p, Prop
} from 'av-ts'
import { Route } from 'vue-router'

@Component({
  name: 'my-page',
})
export default class MyPage extends Vue {
  @Prop id = p({ type: String, required: true })

  // "beforeRouteEnter" can use "NextFuncVm<T>"
  @Lifecycle
  async beforeRouteEnter(to: Route, from: Route, next: NextFuncVm<MyPage>) {
    if (normalCase) {
      next()
    } else if (redirection) {
      next({ name: 'other-page' })
    } else if (cancel) {
      next(false)
    } else if (needCallback) {
      next((vm) => {
        console.log(vm.id)
      })
    }
  }

  // "beforeRouteUpdate" & "beforeRouteLeave" can only use "NextFunc"
  @Lifecycle
  async beforeRouteUpdate(to: Route, from: Route, next: NextFunc) {
    if (normalCase) {
      next()
    } else if (redirection) {
      next({ name: 'other-page' })
    } else if (cancel) {
      next(false)
    }
  }
}

Transition


Type: TypedPropertyDecorator

mark method as a callback of transition component. method is still called in other instance methods. This decorator is solely for type checking.

// solely for type checking! beforeEnter can be called in other methods
@Transition beforeEnter(el: HTMLElement) {
  el.style.opacity = 0
  el.style.height = 0
}

Data


Type: TypedPropertyDecorator

Collecting instance properties is heavy and hacky. It needs to find all props and other properties for you. If you want to make instance creation faster you can skip data collection. Here comes the Data decorator. When Data decorator is applied to a method, the method will be extracted as data function in vue's option, with this injected. And none instance property is counted as data option.

Example:

@Component
class TestData extends Vue {
  @Prop a = p(Number)
  b =  456 // this initializer will be ignored

  @Data data() {
    return {
      b: this.a // b will be initialized to prop value
    }
  }
}

let instance = new TestData({propsData: {a: 777}})
instance.b === 777 // true

Utility Functions


Mixin

has roughly type: <V>(parentConstructor: typeof Vue, ...traitConstructor: (typeof Vue)[]): {new(): V}

a function to mix all Traits decorated constructors into one Vue constructor.

To use Mixin correctly, you need to declare one interface to extend all traits you need. Then pass it as a generic type argument to Mixin<MixedInterface>(...traits). This is TypeScript's limitation.

In new version, you can just use Mixin(trait1, trait2). Note: Mixin supports at most four traits. More traits requires manuall type argument annotation.

It's return value is parentConstructor.extend({mixins: traitConstructor}): extending the first trait as parentConstructor and pack all remaining traits in mixins option.

See source for more specific type.

Example:

@Trait class Pen extends Vue {
  havePen() { alert('I have a pen')}
}
@Trait class Apple extends Vue {
  haveApple() { alert('I have an apple')}
}

// compiles under TS2.2
@Component class ApplePen extends Mixin(Apple, Pen) {
  Uh() {
    this.havePen()
    this.haveApple()
    alert('Apple pen')
  }
}

is equivalent to

var Pen = Vue.extend({
  methods: {
    havePen() { alert('I have a pen')}
  }
})
var Apple = Vue.extend({
  methods: {
    haveApple() { alert('I have an apple')}
  }
})

var Mixin = Pen.extend({
  mixins: [ Apple ]
})

var ApplePen = Mixin.extend({
  methods: {
    Uh() {
      this.havePen()
      this.haveApple()
      alert('Apple pen')
    }
  }
})

Implementing PineapplePen and PenPineappleApplePen is left for exercise.

Explicit annotation example:

// Five traits and more reuire explicit annotation
interface GodLike extends FirstBlood, DoubleKill, KillingSpree, Rampage, Unstoppable {}

@Component
class LegendaryClass extends Mixin<GodLike>(FirstBlood, DoubleKill, KillingSpree, Rampage, Unstoppable) {
  dominate() {
    console.log('Mooooooooonster Kill')
  }
}

Component.register


// has type
Component.register: (key: $$Prop, logic: DecoratorProcessor) => void
// $$Prop is a special string type that means you have to prefix the key with `$$`
// DecoratorProcessor can access prototype, instance and options of the decorated class
// where
type $$Prop = string & {'$$Prop Brand': never}
type DecoratorProcessor = (proto: Vue, instance: Vue, options: ComponentOptions<Vue>) => void;

Component.register is for advanced users.

Sometimes you need to extend Vue's functionality by adding new instance option. Those new options usually are not type-safe. For example, render is a special method can access this but cannot be put in methods option at the same time.

To implement a new decorator. You need first to know how av-ts works underhood. The comment is quite a good start. Also you can find some example implmentation.

common tricks

Please see FAQ

Difference

Added Feature:

  • new decorator @Transition for typechecking transition hooks!
  • tsx support

More Repositories

1

vue-compiler

Vue's template compiler reimplemented in Rust!
Rust
1,004
star
2

yats.vim

Yet Another TypeScript Syntax: The most advanced TypeScript Syntax Highlighting in Vim
Vim Script
639
star
3

typescript-repl

An upgraded TypeScript REPL
TypeScript
209
star
4

vue-ts-loader

Type-check your script in your vue-loader
JavaScript
90
star
5

vue-advanced-programming

A collection of tricks in Vue
JavaScript
82
star
6

kilimanjaro

typed vuex 2.0
TypeScript
62
star
7

gulp-progeny

Make Gulp Fast Again
CoffeeScript
53
star
8

vue-ts-example

An example repo for Vue2.0 and TypeScript2.0
Vue
39
star
9

vim-worksheet

Worksheet for Vim
Python
34
star
10

ts-css-plugin

TypeScript
28
star
11

compositional-xstate

XState's hook/compositional API
TypeScript
20
star
12

Pat-Mat

pattern match in Javascript
CoffeeScript
7
star
13

atom-vue

vue-language-server demo for Atom
JavaScript
6
star
14

vivio

TypeScript
6
star
15

awesome-tree-sitter

A curated list of tree-sitter resources.
6
star
16

av-ts-template

av-ts template for vue-cli
JavaScript
6
star
17

herringtondarkholme.github.io

HTML
4
star
18

jedi-syntax

vim syntax for jedi lang
Vim Script
4
star
19

JS-regex

A collection of useful RegExp
JavaScript
3
star
20

Python-SignatureSal

Python implementation of signature saliency
Python
2
star
21

gulp-jedi

Compile jedi templates to PHP
PHP
2
star
22

angry.im

prototype
TypeScript
2
star
23

rs-perf-talk

Slides for Rust performance in JavaScript land
Vue
2
star
24

lovelive

mock Google+ Valentine effect
JavaScript
2
star
25

js-crush

Vue
2
star
26

diana

TypeScript
1
star
27

Vimfile

JavaScript
1
star
28

scalatra-activerecord-boilerplate

template repo
Scala
1
star
29

byon

Angular2 demo
HTML
1
star
30

paranoid-router

typed router for vue
1
star
31

ast-grep

placeholder
HTML
1
star
32

HerringtonDarkholme

1
star
33

sponsors

Appreciate all my sponsors for helping me make web faster!
TypeScript
1
star
34

syn-checker

syn-checker
JavaScript
1
star
35

ImgSearchGesture

JavaScript
1
star
36

SharePicToGp

This repo is under construction !
CoffeeScript
1
star
37

rectangular

TypeScript
1
star
38

printheart

Happy Birthday!
CSS
1
star
39

Vim--Monokai-Terminal

Terminal Color Palette for Monokai Theme
Vim Script
1
star
40

android-example

scala example
Scala
1
star