VueJS+TypeScript の開発環境を作ってみた

Vue.js を始めました。TypeScript の開発環境を作ってみました。

はじめに

これは自力でやる場合のサンプル程度に考えてください。

もうしばらくすると、公式の webpack テンプレートが TypeScript に対応しそうです。

Ref. TypeScript template? · Issue #263 · vuejs/vue-cli

セットアップ

まっさらから自分で必要なライブラリ−やツールを入れていくこともできますが、Vue ではプロジェクトテンプレートが用意されているので、それを使うことにします。

Ref. CLI - インストール - Vue.js

プロジェクトテンプレート: vuejs-templates

これからは PWA が良いのかもしれませんが、まだ調べきれていないので、今回は定番の webpack を選びました。

webpack のページの手順に従ってインストールします:

$ npm install -g vue-cli
$ vue init webpack <my-project>

この後、いろいろと質問されますが、わからなければ、とりあえずデフォルトで良いと思います。

生成されたら、依存パッケージをインストールして、実行してみます:

$ cd <my-project>
$ npm install
$ npm run dev

なお、この状態では、TypeScript は使えません。

一応、Productionビルドも試してみます:

$ npm run build

NodeJSの簡易HTTPサーバーで動かしてみます(入ってなければ npm i -g http-server):

$ cd dist
$ http-server -o

ユニットテストも実行してみます:

$ npm run unit

(テストが失敗すると、npmパッケージのエラーになるんですね。ちょっと焦りました。)

TypeScript のセットアップ

事前に以下の公式ページに目を通しておいた方が良いと思います:

TypeScript のサポート - Vue.js

まずは、TypeScript 本体と、Webpackでコンパイルするために ts-loader をインストールします:

$ npm i -D typescript ts-loader

tsconfig.json を生成します:

$ ./node_modules/.bin/tsc --init

公式ページの「TypeScript のサポート - Vue.js」に従い、tsconfig.json の設定を変更します:

// tsconfig.json
{
  "compilerOptions": {
    "allowSyntheticDefaultImports": true,
    "lib": [
      "dom",
      "es5",
      "es2015.promise"
    ],
    "module": "es2015",
    "moduleResolution": "node"
    .....
  }
}

この状態だとまだエラーが出るので修正します。

TypeScript 用の修正

*.ts ファイルが認識されるようにする

*.js ファイルの拡張子を ts に変更したら、それが Webpack で認識されるように build/webpack.base.conf.js の設定を変更します:

@@ -9,7 +9,7 @@ function resolve (dir) {

 module.exports = {
   entry: {
-    app: './src/main.js'
+    app: './src/main.ts'
   },
   output: {
     path: config.build.assetsRoot,
@@ -19,7 +19,7 @@ module.exports = {
       : config.dev.assetsPublicPath
   },
   resolve: {
-    extensions: ['.js', '.vue', '.json'],
+    extensions: ['.ts', '.js', '.vue', '.json'],
     alias: {
       'vue$': 'vue/dist/vue.esm.js',
       '@': resolve('src')
@@ -47,6 +47,15 @@ module.exports = {
         include: [resolve('src'), resolve('test')]
       },
       {
+        test: /\.ts$/,
+        loader: 'ts-loader',
+        include: [resolve('src'), resolve('test')],
+        options: {
+          /* Make ts-loader recognize TypeScript code in *.vue files */
+          appendTsSuffixTo: [/\.vue$/]
+        }
+      },
+      {
         test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
         loader: 'url-loader',
         options: {

ts-loader の appendTsSuffixTo の設定は、単一ファイルコンポーネント内の TypeScript コードが認識されるようにするために必要となります。

@ の解決

一部の import に @ が使用されています:

import Hello from '@/components/Hello'

これは Webpack のエイリアスの機能で、./src を指すように定義されています:

alias: {
  'vue$': 'vue/dist/vue.esm.js',
  '@': resolve('src')
}

TypeScript は Webpack の定義を認識しないため、tsconfig.json でも定義する必要があります:

@@ -37,8 +37,10 @@

     /* Module Resolution Options */
     "moduleResolution": "node",               /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
-    // "baseUrl": "./",                       /* Base directory to resolve non-absolute module names. */
-    // "paths": {},                           /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
+    "baseUrl": "./",                          /* Base directory to resolve non-absolute module names. */
+    "paths": {                                /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
+      "@": ["src"]
+    },
     // "rootDirs": [],                        /* List of root folders whose combined content represents the structure of the project at runtime. */
     // "typeRoots": [],                       /* List of folders to include type definitions from. */
     // "types": [],                           /* Type declaration files to be included in compilation. */

なお、paths を指定する場合、baseUrl の指定も必要になります。

Ref. Webpack resolve.alias does not work with typescript? - Stack Overflow

*.vue ファイルの import の解決

同じく、import Hello from '@/components/Hello' の箇所ですが、これは Hello.vue ファイルをインポートしようとしていますが、TypeScript ではファイルが認識できないため、エラーになります。

これを解決するためには、src/types/index.t.ds などに型定義を置く必要があります:

declare module "*.vue" {
  import Vue from 'vue'
  export default typeof Vue
}

これで、import Hello from '@/components/Hello.vue' とやると、インポートできるようになります。

追記(2017-08-12)

src/types にある型定義ファイルを認識させるには、tsconfig.json に設定が必要でした:

    "paths": {
      .....
      "*": [
        "src/types/*"
      ]
    },

追記終了

Ref.

型指定

*.ts ファイルで型の不一致が出るので修正します:

src/main.ts:

@@ -1,7 +1,7 @@
 // The Vue build version to load with the `import` command
 // (runtime-only or standalone) has been set in webpack.base.conf with an alias.
-import Vue from 'vue'
-import App from './App'
+import Vue, { ComponentOptions } from 'vue'
+import App from './App.vue'
 import router from './router'

 Vue.config.productionTip = false
@@ -12,4 +12,4 @@ new Vue({
   router,
   template: '<App/>',
   components: { App }
-})
+} as ComponentOptions<Vue>)

src/router/index.ts:

@@ -1,6 +1,6 @@
 import Vue from 'vue'
-import Router from 'vue-router'
-import Hello from '@/components/Hello'
+import Router, { RouteConfig } from 'vue-router'
+import Hello from '@/components/Hello.vue'

 Vue.use(Router)

@@ -10,6 +10,6 @@ export default new Router({
       path: '/',
       name: 'Hello',
       component: Hello
-    }
+    } as RouteConfig
   ]
 })

*.vue ファイルの修正

*.vue ファイルについては、とりあえず、<script lang="ts"> に変更して問題なく動きました。

TSLint の追加(2017-08-07 追記)

$ npm i -D tslint tslint-loader
$ ./node_modules/.bin/tslint --init

webpack.base.conf.js に設定を追加、変更:

.....
  module: {
    rules: [
      {
        test: /\.js$/,
        loader: 'eslint-loader',
        .....
      },
      {
        test: /\.ts$/,
        loader: 'tslint-loader',
        enforce: 'pre',
        include: [resolve('src'), resolve('test')],
        exclude: /(node_modules)/
      },
.....

vue-loader.conf.js の設定を変更:

module.exports = {
  loaders: Object.assign({},  // 追加
    utils.cssLoaders({
      sourceMap: isProduction
        ? config.build.productionSourceMap
        : config.dev.cssSourceMap,
      extract: isProduction
    }),
    { ts: 'ts-loader!tslint-loader' }  // 追加
  ),
  transformToRequire: {
    video: 'src',
    source: 'src',
    img: 'src',
    image: 'xlink:href'
  }
}

*.vuevue-loader 側で処理させるようにします。

あと、VS Code で TSLint を有効にするには、TSLintプラグイン をインストールする必要があります。

なお、Vue で TSLint をかけると、うまく修正できない箇所が出てくるので、ルールを変更する必要があります:

  "rules": {
    "no-consecutive-blank-lines": [
      false
    ],
    "no-unused-expression": [
      true,
      "allow-new"
    ],
    "ordered-imports": [
      false
    ]
  },

no-consecutive-blank-lines: *.vue<script> の内部以外の部分が空行として残るようなので、オフにしました。

no-unused-expression: new Vue(...) のような記述を許容するためです。

ordered-imports: これは好みですが、imports をアルファベット順にしないと怒られるのでオフにしました。

Ref. Support linting vue/html file · Issue #2099 · palantir/tslint

VS Code

最後に VS Code の設定です。

プロジェクトを VS Code で開き、*.vue ファイルを表示するとシンタックスハイライトが効かず、拡張機能Vetur が推奨されるのでインストールします。

Vetur は vuejs で提供されているので公式プラグインなんですね。

再度 *.vue ファイルを開くと、シンタックスハイライトが効くようになりました。

あとがき

まとまった情報がなく結構悩みましたが、わかってみれば、それほど面倒ではないですね。

TypeScript や Webpack の設定について少し理解が深まったので、その点ではためになりました。

おまけ1:npmパッケージのアップデート(失敗)

npmパッケージが古くなっている場合があるのでアップデートを試みてみました。

バージョン確認:

$ npm outdated

メジャーバージョンが上がっていると npm update で更新できないので、npm-check-updates というツールを使います:

$ npm install -g npm-check-updates
$ ncu -u
$ npm update

依存関係でワーニングが出る場合があります:

npm WARN eslint-config-standard@10.2.1 requires a peer of eslint-plugin-import@>=2.2.0 but none was installed.
npm WARN eslint-config-standard@10.2.1 requires a peer of eslint-plugin-node@>=4.2.2 but none was installed.
npm WARN karma-sinon-chai@1.3.1 requires a peer of chai@^3.5.0 but none was installed.
npm WARN karma-sinon-chai@1.3.1 requires a peer of sinon@^2.1.0 but none was installed.
npm WARN sinon-chai@2.12.0 requires a peer of sinon@^1.4.0 || ^2.1.0 but none was installed.

この場合は、node_modules ディレクトリーにある該当パッケージを削除して、再度 npm install してみます。

それでも、ワーニングが出る場合は、個別にインストールしてみます(package.json には入れません):

$ npm install chai@3.5.0
.....
├─┬ chai@3.5.0
│ └── type-detect@1.0.0
└── UNMET PEER DEPENDENCY sinon@3.0.0

バージョン指定が不整合を起こしていますね。(chai@3.5.0 では sinon@3.0.0 が必要なのに、karma-sinon-chai@1.3.1 では sinon@^2.1.0 が必要。)

仕方がないので node_modules を一旦すべて削除してやり直してみます。

$ rm -r node_modules
$ npm install

やはり、依存関係のワーニングが出ますね。諦めて戻しました。

公式テンプレートでアップデートされるのを待つしかないようですね。