Rails5.1.1でVuejs+TypeScript+ESLintな環境を作る

rails5.1がリリースされ、フロントエンド周りがかなり変わったということなので試してみました。
変更点の詳細は以下の記事が詳しいです

- [Rails 5.1の変更点まとめ](http://qiita.com/jnchito/items/b0b47ecfc33de4f9b72f)

## 実行環境
- ruby v2.4.1
- node v7.8.0
- yarn v0.24.4
- rails v5.1.1

## Railsアプリケーションの作成
`webpack`オプションを指定してアプリケーションを作成します。

```
$ rails new rails-vue-ts-sample --webpack
```

完了すると、以下のようなwebpack関連のファイルが出力されています。

```
app/javascript
├── packs
│   └── application.js
bin
├── webpack
├── webpack-dev-server
config/webpack
├── configuration.js
├── development.js
├── loaders
│   ├── assets.js
│   ├── babel.js
│   ├── coffee.js
│   ├── erb.js
│   └── sass.js
├── production.js
├── shared.js
└── test.js
```

### 参考
- https://github.com/rails/webpacker#installation

## Vueのインストール
以下のコマンドを実行してVueをインストールします。

```
$ bin/rails webpacker:install:vue
```

実行するとpackage.jsonが更新され、以下のファイルが追加されます。

- app/javascript/packs/app.vue
- app/javascript/packs/hello_vue.js
- config/webpack/loaders/vue.js

package.json差分

``` diff:package.json
+    "vue": "^2.3.3",
+    "vue-loader": "^12.2.1",
+    "vue-template-compiler": "^2.3.3",
```

### 参考
- https://github.com/rails/webpacker#vue


## コントローラの追加
確認のため、適当なコントローラを作成します。

```
$ rails g controller Home index
```

erbファイルを以下のように編集

``` diff:views/home/index.html.erb
- <h1>Home#index</h1>
- <p>Find me in app/views/home/index.html.erb</p>
+ <%= javascript_pack_tag 'hello_vue' %>
```


## ビルド
以下のコマンドでビルドを実行します。
ビルドを忘れると`Webpacker::FileLoader::NotFoundError`が発生します。

```
$ bin/webpack
```

ビルドが完了したら、サーバを起動し動作を確認してみます。

```
$ bin/rails s
```

![ss.png](https://qiita-image-store.s3.amazonaws.com/0/148095/e0bef4fc-2bc5-73a2-3dc6-479e8092211e.png "ss.png")

## TypeScript+Pugを導入してみる
続いて、TypeScriptとPug(テンプレートエンジン)を追加してみます。

```
$ yarn add typescript ts-loader pug pug-loader
```

tsconfig.jsonを追加します。

``` tsconfig.json
{
	"compilerOptions": {
		"allowSyntheticDefaultImports": true,
		"declaration": false,
		"emitDecoratorMetadata": true,
		"experimentalDecorators": true,
		"lib": [
			"es6",
			"dom"
		],
		"module": "es6",
		"moduleResolution": "node",
		"sourceMap": true,
		"target": "es5"
	},
	"exclude": [
		"**/*.spec.ts",
		"node_modules",
		"vendor",
		"public"
	],
	"compileOnSave": false
}
```

app/javascript/packs/app.vueを以下のように修正

``` diff:app/javascript/packs/app.vue
-<template>
-  <div id="app">
-    <p>{{ message }}</p>
-  </div>
+<template lang="pug">
+  #app
+    p {{ message }}
 </template>

-<script>
+<script lang="ts">
 export default {
  data: function () {
    return {
      message: "Hello Vue!"
    }
  }
}
</script>

<style scoped>
p {
  font-size: 2em;
  text-align: center;
}
</style>
```

続いて、`app/javascript/packs/hello_vue.js`を`hello_vue.ts`にリネーム

```
$ mv app/javascript/packs/hello_vue.js app/javascript/packs/hello_vue.ts
```

ここで、ビルドを実行するとエラーが発生。
hello_vue.tsファイルからapp.vueファイルが読めないようです。

```
ERROR in ./app/javascript/packs/hello_vue.ts
(8,17): error TS2307: Cannot find module './app.vue'.
```

以下のファイルを追加して、ts-loaderの設定を追加します。

``` config/webpack/loaders/typescript.js
module.exports = {
  test: /\.ts$/,
  loader: 'ts-loader',
  options: {
    appendTsSuffixTo: [/\.vue$/]
  }
}
```

これでビルドが通るようになりました。

### 参考
- https://github.com/TypeStrong/ts-loader#appendtssuffixto-regexp-default

## eslintを導入する
せっかくなので、eslintも導入してみます。

```
$ yarn add babel-eslint eslint eslint-config-standard eslint-friendly-formatter eslint-loader eslint-plugin-html eslint-plugin-import eslint-plugin-node eslint-plugin-promise eslint-plugin-standard --dev
```

.eslintrc.jsを追加します

``` .eslintrc.js
module.exports = {
  root: true,
  parser: 'babel-eslint',
  parserOptions: {
    sourceType: 'module'
  },
  env: {
    browser: true
  },
  extends: 'standard',
  plugins: [
    'html'
  ],
  'rules': {
    // allow paren-less arrow functions
    'arrow-parens': 0,
    // allow async-await
    'generator-star-spacing': 0,
    // allow debugger during development
    'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
  }
}
```

以下のファイルを追加して、eslint-loaderの設定を追加します。

``` config/webpack/loaders/eslint.js
module.exports = {
  test: /\.(js|ts|vue)$/,
  loader: 'eslint-loader',
  enforce: 'pre',
  options: {
    formatter: require('eslint-friendly-formatter')
  }
}
```

これでeslintの設定は完了です。
動作を確認してみます。

app.vueファイルを以下のように編集

``` diff:app/javascript/packs/app.vue
<script lang="ts">
export default {
  data: function () {
+    // 未使用の変数を追加
+    const foo:string = 'bar'

    return {
      message: 'Hello Vue!'
    }
  }
}
</script>
```

ビルドするとエラーが発生します。
ちゃんと動いているようです。

```
ERROR in ./app/javascript/packs/app.vue

  ✘  http://eslint.org/docs/rules/no-unused-vars  'foo' is assigned a value but never used
  app/javascript/packs/app.vue:10:11
      const foo :string = 'bar'
             ^


✘ 1 problem (1 error, 0 warnings)
```



## vueファイルのstyleが適用されていない?
これで終わりと思いきや、vueファイルのstyleが適用されていないことに気づきました。

![ss.png](https://qiita-image-store.s3.amazonaws.com/0/148095/e0bef4fc-2bc5-73a2-3dc6-479e8092211e.png "ss.png")

``` app/javascript/packs/app.vue
<template lang="pug">
  #app
    p {{ message }}
</template>

〜〜〜

<style scoped>
p {
  font-size: 5em;
  text-align: center;
}
</style>
```

vue-loaderの`extractCSS`の設定によるものでした。
`config/webpack/loaders/vue.js`を編集して、extractCSSをfalseとしても良いですが、以下のように`stylesheet_pack_tag`を追加することで
スタイルが適用されるようになりました。

``` diff:views/home/index.html.erb
+ <%= stylesheet_pack_tag 'hello_vue' %>
<%= javascript_pack_tag 'hello_vue' %>
```

### 参考
- https://github.com/rails/webpacker#inside-views
- https://github.com/vuejs/vue-loader/blob/master/docs/en/configurations/extract-css.md

## 終わりに
とりあえずですが、Vuejs+TypeScript+eslintな環境を作ることができました。
上記では都度`bin/webpack`を実行していましたが、[webpack-dev-server](https://github.com/rails/webpacker#webpack-dev-server)を設定してあげれば変更時に自動でビルドが実行されるようになります。