この記事について
ついに Vue CLI 3.0 がリリースされました
Vue CLI 3.0 is here! – The Vue Point – Medium
Vue CLI 3.0 で Vue.js in TypeScript なプロジェクトをさくっとつくってみたので、その記録です。
3.0 では、プロジェクト作成時に TypeScript を選べるようになっており、Vue.js in TypeScript での開発に必要な諸々をまるっと入れてくれるので、新規開発で TypeScript を導入するハードルがグッと下がってるんじゃないか、と期待が膨らみます。
概要
- Vue CLI: 3.0.0
- TypeScript: 3.0.0
- webpack: 4.16.5
Vue CLI 3.0 でプロジェクト作成すると TypeScript と webpack は上記のバージョンがインストールされます。
ドキュメントはこちらです。
手順
1. Vue CLI 3 のインストール
手順はこちらに載っています( 2.x とはパッケージが別で、 @vue/cli
となっているので注意が必要です)。
$ yarn global add @vue/cli
私は、ひとまずグローバルに入れたくなかったので、適当なディレクトリを切ってそこに入れました。
$ vue -V
3.0.0
2. プロジェクトをつくる
$ vue create vue-ts-example
(プロジェクト名は適宜置き換えてください)
今回は TypeScript で書きたいので、下の「Manually select feature」を選択します。
Enter/Return すると、選択肢が出てきます。
お好みでオン/オフしてください。
以下、"class-style component syntax" を有効にしたパターンと無効にしたパターンに分けて記載します。
2.1. Use class-style component syntax? Yes にしたパターン
いくつか質問されますが、基本的にデフォルトでいいと思います。
package.json の中身
"dependencies": {
(snip)
"vue-class-component": "^6.0.0",
"vue-property-decorator": "^7.0.0",
(snip)
この2つのパッケージが、 class-style component に必要なものです。
HelloWorld.vue の中身
デコレータを使って、クラス定義ができるようになっています。
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
@Component
export default class HelloWorld extends Vue {
@Prop() private msg!: string;
}
</script>
2.2. Use class-style component syntax? No にしたパターン
package.json の中身
Yes にしたプロジェクトのと diff を取ると
14a15,16
> "vue-class-component": "^6.0.0",
> "vue-property-decorator": "^7.0.0",
この2つのパッケージがないことが分かります。
HelloWorld.vue の中身
シンプルに Vue.extend しています。
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
name: 'HelloWorld',
props: {
msg: String,
},
});
</script>
3. ファイル構成
デフォルトでは以下のようになっています。
$ tree -L 2
.
├── README.md
├── babel.config.js
├── cypress.json
├── jest.config.js
├── node_modules
(...)
├── package.json
├── public
│ ├── favicon.ico
│ ├── img
│ ├── index.html
│ ├── manifest.json
│ └── robots.txt
├── src
│ ├── App.vue
│ ├── assets
│ ├── components
│ ├── main.ts
│ ├── registerServiceWorker.ts
│ ├── router.ts
│ ├── shims-tsx.d.ts
│ ├── shims-vue.d.ts
│ ├── store.ts
│ └── views
├── tests
│ ├── e2e
│ └── unit
├── tsconfig.json
├── tslint.json
└── yarn.lock
まず目を引くのが、 build, config といったディレクトリがなくなっていることです。
デフォルトの webpack の設定は、 @vue
スコープのパッケージに分散して置かれており、 node_modules/@vue
の下にある cli-plugin-*
とか cli-service
とかで別々に定義されて webpack-chain という仕組みで連結されているようです(詳細は以下のドキュメントをご覧ください)。
Working with Webpack | Vue CLI 3
他にも色々変わってて、たとえば、サーバ実行のコマンドが
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
から
"serve": "vue-cli-service serve",
に変わってたり、
src/registerServiceWorker.js とかいうファイルが追加されてたり。
今回の趣旨は TypeScript なので詳しくは割愛します。
4. TypeScript 書いていく
簡単なカウンターを実装していきます。
コンポーネントの記述については "Use class-style component syntax?" に Yes と答えたパターンと No と答えたパターンがあります。
4.1. Template
<template>
<div>
<p>{{ counter }}</p>
<button @click="handleClick">Up</button>
</div>
</div>
</template>
4.2. Component
4.2.1. Use class-style component syntax? Yes にしたパターン
@Mutation
デコレータを使いたかったので、vue-class パッケージを追加しました。
kaorun343/vue-property-decorator: Vue.js and Property Decorator
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator'
import { mapGetters, mapMutations } from 'vuex'
import { Mutation } from 'vuex-class'
@Component({
computed: {
...mapGetters({ counter: 'current' })
},
methods: {
...mapMutations(['increment'])
}
})
export default class HelloWorld extends Vue {
@Prop() private value!: number
@Mutation('increment') increment!: () => void
handleClick () {
this.increment()
}
}
</script>
4.2.2. Use class-style component syntax? No にしたパターン
<script lang="ts">
import Vue, { ComponentOptions } from 'vue';
import { mapGetters, mapMutations } from 'vuex'
interface HelloWorldComponent extends Vue {
counter: number,
increment: () => void
}
export default {
name: 'HelloWorld',
props: {
msg: String,
},
computed: {
...mapGetters({ counter: 'current' })
},
methods: {
...mapMutations(['increment']),
handleClick () {
this.increment()
}
}
} as ComponentOptions<HelloWorldComponent>
4.3. Store
Store は interface つくってから、オブジェクトに型を割り当てることもできますが、今回は直接 GetterTree<S, R>
などの Vuex で定義されている型を指定しました。
import Vue from 'vue';
import Vuex, { StoreOptions, GetterTree, MutationTree, ActionTree } from 'vuex';
Vue.use(Vuex);
interface CounterState {
counter: number
}
const state: CounterState = {
counter: 1
}
const getters: GetterTree<CounterState, CounterState> = {
current (state: CounterState): number {
return state.counter
}
}
const mutations: MutationTree<CounterState> = {
increment (state: CounterState): void {
state.counter++
}
}
const actions: ActionTree<CounterState, CounterState> = {}
const store: StoreOptions<CounterState> = {
state,
getters,
mutations,
actions,
}
export default new Vuex.Store<CounterState>(store)
4.4. Router
Router も TS で書かれてましたが、今回は手付かずです(そのまま載せておきます)。
import Vue from 'vue';
import Router from 'vue-router';
import Home from './views/Home.vue';
Vue.use(Router);
export default new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
{
path: '/',
name: 'home',
component: Home,
},
{
path: '/about',
name: 'about',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ './views/About.vue'),
},
],
});
おわりに
コマンド叩いてポチポチするだけであまりに簡単に Vue.js in TypeScript なプロジェクトの雛形ができてしまって感動しました。
短時間で雑に書いたので、間違いがあるかもしれません。何かあればコメント欄にてご指摘いただけると助かります