Edited at

Vue CLI 3.0 で TypeScript な Vue.js プロジェクトをつくってみる

More than 1 year has passed since last update.


この記事について

ついに Vue CLI 3.0 がリリースされました :tada:

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 は上記のバージョンがインストールされます。

ドキュメントはこちらです。

Overview | Vue CLI 3


手順


1. Vue CLI 3 のインストール

手順はこちらに載っています( 2.x とはパッケージが別で、 @vue/cli となっているので注意が必要です)。

Installation | Vue CLI 3

$ yarn global add @vue/cli

私は、ひとまずグローバルに入れたくなかったので、適当なディレクトリを切ってそこに入れました。

$ vue -V

3.0.0


2. プロジェクトをつくる

$ vue create vue-ts-example

(プロジェクト名は適宜置き換えてください)

image.png

今回は TypeScript で書きたいので、下の「Manually select feature」を選択します。

Enter/Return すると、選択肢が出てきます。

image.png

お好みでオン/オフしてください。

以下、"class-style component syntax" を有効にしたパターンと無効にしたパターンに分けて記載します。


2.1. Use class-style component syntax? Yes にしたパターン

image.png

いくつか質問されますが、基本的にデフォルトでいいと思います。

package.json の中身


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

他にも色々変わってて、たとえば、サーバ実行のコマンドが


package.json

    "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",


から


package.json

    "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 なプロジェクトの雛形ができてしまって感動しました。

短時間で雑に書いたので、間違いがあるかもしれません。何かあればコメント欄にてご指摘いただけると助かります :bow: