2017/09/21

vue-cliを使ってとにかく楽してVue.jsでTypeScriptを使いたい

2年以上前にVue.jsでTypeScriptを使ってChrome拡張機能をつくった経験がある。
フロントエンドについて学びはじめた頃なのでTypeScriptはv1.4、Vue.jsはv0.11とかそんな時代。当時、相当苦労した思い出がある。

今では、Vue.jsも公式で型定義を提供している。TypeScriptのサポートも手厚くなり、型のある環境での開発が楽になっているらしいが……。
  • gulp書きたくない
  • webpack触りたくない
  • *-loaderの設定したくない
  • その他細かな設定をしたくない


だから、とにかく楽したい!!!


かといって、いわゆるget startedのような最小構成にはしたくない。

ということで、vue-cliのwebpackテンプレートをベースに、とにかく楽してVue.jsプロジェクトでTypeScriptを使えるようにする。

環境は以下のとおり。
  • Mac OSX
  • npm v5.3.0
  • vue-cli v2.8.2
  • vue.js v2.4.2
  • typescript v2.4.2


vue2.5以降でvue-class-componentのデコレータを使わない方法は、以下の記事を参照ください。

vue-cliを導入する


vue-cliとは、コマンド1つでScaffolding(雛形生成)してくれるcliツールだ。
まずはvue-cliをglobalにインストールする。

$ npm i -g vue-cli

詳細は、以下の記事を参照してほしい。



テンプレートを使ってプロジェクトディレクトリを作成する


vue-cliは、公式でいくつかのテンプレートを公開している。
今回は一番メジャーなwebpackのテンプレートをベースにする。
$ vue init webpack vue-ts-project

# ビルドに必要なライブラリをインストールする
$ cd vue-ts-project
$ npm install

これでScaffoldingは終了!
このままnpm run buildをすればローカルwebサーバが起動してVue.jsのサンプルページを見ることができる。

Commit: vue-cli init webpackで雛形をつくる · BcRikko/vuejs-typescript@a64449d · GitHub



TypeScriptでかけるように設定する


webpackのテンプレートをベースに、必要なモノだけを追加してTypeScriptを使えるようにする。

typescript, ts-loader, vue-class-componentをインストールする


$ npm i -D typescript ts-loader vue-class-component

typescriptとts-loaderはTypeScriptをビルドする用。
vue-class-componentはクラススタイルのVueコンポーネントにする用。(使い方は後述する)

Commit: typescript, ts-loader, vue-class-componentをインストールする · BcRikko/vuejs-typescript@9fe88b0 · GitHub


tsconfig.jsonを追加し、webpackでビルドできるように修正する

次に、TypeScriptをビルドするための設定をする。
まずはtsconfig.jsonから。
// tsconfig.json
{
  "compilerOptions": {
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "module": "es2015",
    "moduleResolution": "node",
    "noImplicitAny": true,
    "target": "es6",
    "lib": ["dom", "es6", "es2015.promise"]
  },
  "include": [
    "./src/**/*.ts"
  ]
}

targetはes6(es2015)にしておく。
allowSyntheticDefaultImports: trueで、import Vue from 'vue'という記述ができるようになる。
Vue.jsでは、型宣言をESモジュール形式で提供する予定なので、require(...)ではなくimport ... from ...推奨されている

Commit: tsconfig.jsonを追加する · BcRikko/vuejs-typescript@2c0f83e · GitHub


次にwebpackでTypeScriptをビルドできるようにするため、build/webpack.base.conf.jsの一部を変更する。
// build/webpack.base.conf.js
module.exports = {
    entry: {
      // エントリポイントの拡張子を.js→.tsに変更
      app: './src/main.ts'
    },
    output: {
      path: config.build.assetsRoot

// ----- 略 -----

    },
    resolve: {
      // extensionsに'.ts'を追加
      extensions: ['.js', '.vue', '.json', '.ts'],
      alias: {
        'vue$': 'vue/dist/vue.esm.js',
        '@': resolve('src'),

// ----- 略 -----

      },
      // ↓追加
      {
        test: /\.ts$/,
        loader: 'ts-loader',
        include: [resolve('src'), resolve('test')],
        options: {
          appendTsSuffixTo: [/\.vue$/]
        }
      },
      // ↑追加
      {
        test: /\.js$/,
        loader: 'babel-loader',

Commit: webpackでtsを読み込めるように修正する · BcRikko/vuejs-typescript@1de599e · GitHub


TypeScriptで書くために拡張子などを変更する

ここまででTypeScriptをビルドする環境はできた。
次は、TypeScriptで書けるように拡張子などを変更する。

コマンドじゃなくても良いので、srcディレクトリ配下の.jsファイルを.tsに変更する。
$ mv ./src/main.js ./src/main.ts
$ mv ./src/router/index.js ./src/router/index.ts

Vueファイルでの使用言語を指定する。
src/App.vuesrc/components/Hello.vueのscriptタグにlang="ts"を追加する。
<template>
  <!-- 略 -->
</template>

<script lang="ts">
export default {
  // 略
}
</script>

Commit: tsで書けるように拡張子等を変更する · BcRikko/vuejs-typescript@0394620 · GitHub


しかし、このままではvueファイル をimportするときにCannot find module "./App"のようなエラーが出てしまうので、vueファイルの型定義(src/sfc.d.ts)を作成する。
// src/sfc.d.ts
declare module "*.vue" {
  import Vue from 'vue'
  export default Vue
}

Commit: vueファイルを読み込めるように定義を追加する · BcRikko/vuejs-typescript@ebf8165 · GitHub


続いて、import部分を修正する。
// src/main.ts
import Vue from 'vue'
// 拡張子をつける ./App → ./App.vue
import App from './App.vue'
import router from './router'

// 略

// src/router/index.ts
import Vue from 'vue'
import Router from 'vue-router'
// 拡張子をつける @/components/Hello → @/components/Hello.vue
import Hello from '@/components/Hello.vue'

Vue.use(Router)
  
// 略

Commit: vueファイルが`Cannot find module`になるので読み込めるように拡張子を指定する · BcRikko/vuejs-typescript@a84903b · GitHub


vue-class-componentを使ってTypeScriptで開発する


準備は整ったので、あとはvue-class-componetを使って開発していくだけだ。ためしにサンプルに手を加える。

まずはsrc/App.vueから。
<script lang="ts">
import Vue from 'vue'
import Component from 'vue-class-component'

@Component({
  name: 'app'
})
export default class App extends Vue {
}
</script>

次に、src/components/Hello.vue
<script lang="ts">
import Vue from 'vue'
import Component from 'vue-class-component'
@Component({
  name: 'hello'
})
export default class Hello extends Vue {
  msg = 'Welcome to Your Vue.js App'
}
</script>

最後にビルドして、ちゃんと表示されるか確認する。
$ npm run dev

Commit: vue-class-componentでクラススタイルのVueコンポーネントに変更する · BcRikko/vuejs-typescript@03d1f6a · GitHub



おまけ: もうちょっとvue-class-componentを触ってみる


src/App.vue
<template>
  <div id="app">
    <img src="./assets/logo.png">
    <router-view propsMessage="vue-router"></router-view>
    <hello
      propsMessage="hello tag"
      clickEvent="clicked"
      @clicked="showChildMessage">
    </hello>
  </div>
</template>

<script lang="ts">
import Vue from 'vue'
import Component from 'vue-class-component'
import Hello from './components/Hello.vue'
@Component({
  name: 'app',
  components: { Hello }
})
export default class App extends Vue {
  showChildMessage (val: string) {
    alert(val + ' from child')
  }
}
</script>


src/components/Hello.vue
<template>
  <div class="hello">
    <h1>{{ propsMessage }}</h1>
    <input type="text" v-model="input">
    <h2>{{ input }}</h2>
    <button @click="onClick">onClick</button>
    <ul>
      <li v-for="item in filteredList" v-bind:key="item">{{ item }}</li>
    </ul>
  </div>
</template>

<script lang="ts">
import Vue from 'vue'
import Component from 'vue-class-component'
@Component({
  name: 'hello',
  props: {
    propsMessage: String,
    clickEvent: String
  }
})
export default class Hello extends Vue {
  // local state
  msg = 'Welcome to Your Vue.js App'
  input = 'a'
  list:string[] = ['apple', 'apricot', 'avocado', 'banana', 'bilberry', 'blackberry', 'blackcurrant', 'blueberry', 'boysenberry']
  // props
  propsMessage: string
  clickEvent: string
  // mounted
  mounted () {
    console.log('mounted')
  }
  // computed
  get filteredList () {
    return this.list.filter(a => a.indexOf(this.input) > -1)
  }
  // methods
  onClick () {
    window.alert('clicked ' + this.propsMessage)
    this.$emit(this.clickEvent, this.input)
  }
}
</script>
Commit: コンポーネントをいろいろいじってみる · BcRikko/vuejs-typescript@0d69480 · GitHub

propsは、@Componentとメンバ変数に定義。
dataは、クラスのプロパティとして定義。
computedは、getterとして定義。
methodsは、メンバメソッドとして定義。
その他のオプション(mouted, createdなどはそのままの名前で定義。

だいたいこんな感じで開発できる。
VuexのstoreもTypeScriptで書きたいので、後日記事を書こうと思う。



残る闇部分はいくつかある


  • 型定義の闇
  • eslint → tslintへの移行
  • vueファイル内のtslintが正常に動作しない
  • npm run buildするとUglifyでエラーになる
  • などなど…


いずれ解決したらブログに書こうと思う。



以上

written by @bc_rikko

0 件のコメント :

コメントを投稿