Rails+Vue.js+TypeScriptな環境を構築する

## はじめに

Rails + Vue.js + TypeScriptで趣味プログラミングしてみようと思って開発環境を構築していたら思いの外ハマったので備忘録として残しておきます。

RailsやWebpackerのバージョンが変わったら設定もまた変わりそうなので（Webpacker3.0系と3.2系でもconfigファイルの書き方が微妙に違うようでした）、以下のバージョンにおいて動作確認しているということをお見知りおきください。

## 環境

- Ruby 2.5.0
- Rails 5.1.4
- Webpacker 3.2.1

## プロジェクトを作成してVue.jsが動くようにする

まずはrailsプロジェクトを作ります。今回はオプション無しでやります。

```bash
$ rails new sample
```

エディタでGemfileを開きwebpacker gemを追記した上で `bundle install` します。成功したら `webpacker:install` しましょう。

```bash
$ emacs Gemfile

# webpackerを追記
gem 'webpacker', '~> 3.0'

$ bundle

# bundle installできたらwebpackerをインストール
$ bundle exec rails webpacker:install
```

問題なければそのままwebpackerでVue.jsをインストールします。

```bash
$ bundle exec rails webpacker:install:vue
```

インストールがうまくいったら、Vue.jsの動作確認のためcontroller、view、routesに以下のように追記（もしくは新規追加）します。

```ruby:app/controllers/home_controller.rb
class HomeController < ApplicationController
  def index
  end
end
```

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

```ruby:config/routes.rb
Rails.application.routes.draw do
  root to: 'home#index'
end
```

サーバを立ち上げて確認してみます。

```bash
$ bundle exec rails s
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/18007/2f193e40-f844-0c90-9780-61c326da1209.png)

ここで `bin/webpack-dev-server` も立ち上げてみようとするとエラーになるので、エラー文に書いてあるとおり実行します。

```bash
# エラーになる
$ bin/webpack-dev-server
Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.

# エラーに書いてあるとおり実行
$ bundle binstubs bundler --force

# 今度はOK
$ bin/webpack-dev-server
 10% building modules 2/2 modules 0 active
Project is running at http://localhost:3035/
webpack output is served from /packs/
（中略）
webpack: Compiled successfully.
```

## TypeScriptのインストールと設定

Vue.jsは動くようになったので今度はwebpackerでTypeScriptをインストールします。

```bash
$ bundle exec rails webpacker:install:typescript
```

TypeScriptが使えるようになったはずなので以下の二箇所に変更を加えてみましょう。

1. Rename: `app/javascript/packs/hello_vue.js` -> `app/javascript/packs/hello_vue.ts`
2. `app/javascript/app.vue` のscript部分にTypeScriptを指定

```diff:app/javascript/app.vue
   </div>
 </template>

-<script>
+<script lang="ts">
 export default {
   data: function () {
     return {
```

これで `bin/webpack-dev-server` を動かしてみますが、次のようなエラーが出ると思います。

```bash
$ bin/webpack-dev-server
（中略）
Failed to compile.

./app/javascript/packs/hello_vue.ts
[tsl] ERROR in /path/to/project/app/javascript/packs/hello_vue.ts(9,17)
      TS2307: Cannot find module '../app.vue'.
```

これはTypeScriptから `.vue` ファイルが読めていないということなので `./config/webpack/loaders/typescript.js` を以下のように編集します。

```diff:config/webpack/loaders/typescript.js
 module.exports = {
   test: /\.(ts|tsx)?(\.erb)?$/,
   use: [{
-    loader: 'ts-loader'
+    loader: 'ts-loader',
+    options: {
+      appendTsSuffixTo: [/\.vue$/]
+    }
   }]
 }
```

`bin/webpack-dev-server` を動かしてCompiled successfullyすればOKです。

```bash
$ bin/webpack-dev-server
（中略）
webpack: Compiled successfully.
```

## Vue.jsの型定義ファイルの追加

今までの流れでRails + Vue.js + TypeScriptの設定はOKだと思ったのですが、適当にコンポーネントファイルでも追加するかと `app/javascript/packs/components/header.vue` のようなファイルを作成して `hello_vue.ts` でインポートしてみたところ

```html:app/javascript/packs/components/header.vue
<template>
  <p>custom header</p>
</template>
```

```diff:app/javascript/packs/hello_vue.ts
 import Vue from 'vue'
 import App from '../app.vue'
+import Header from './components/header.vue'

 document.addEventListener('DOMContentLoaded', () => {
   const el = document.body.appendChild(document.createElement('hello'))
```

また Cannot find module のエラーが出ました。

```bash
$ bin/webpack-dev-server
（中略）
ERROR in ./app/javascript/packs/hello_vue.ts
[tsl] ERROR in /path/to/project/app/javascript/packs/hello_vue.ts(10,20)
      TS2307: Cannot find module './components/header.vue'.
```

この問題を解決するにはどうもVue.jsの型定義ファイルが必要とのことだったので `app/javascript/types/vue.d.ts` あたりに以下のような内容のファイルを追加します。

```ts:app/javascript/types/vue.d.ts
declare module "*.vue" {
    import Vue from 'vue'
    export default Vue
}
```

これで動くようになったはずです。

```bash
$ bin/webpack-dev-server
（中略）
webpack: Compiled successfully.
```

## おわりに

というわけでRails + Vue.js + TypeScriptで開発できるようになりました、多分。

ただ、何故App.vueは型定義ファイルがなくてもhello_vue.tsから読み込めるのか…これが分からない :thinking: 

誰かご教授いただけると幸いです :bow: 

## Reference

- https://qiita.com/naoki85/items/51a8b0f2cbf949d08b11
- https://qiita.com/maeharin/items/99eb03e1fe8ab89f4e4d
- https://qiita.com/YukiYonekura/items/a5b3905fb05b395531af
- https://github.com/rails/webpacker/blob/master/docs/typescript.md
- https://tisnote.com/rails-webpacker-react-typescript/
- https://jp.vuejs.org/v2/guide/typescript.html