個人的 Rails × Vue まとめ

## 概要

RailsをAPIサーバにしてVueのクライアントアプリと疎通する構成ではなく、`rails webpacker:install:vue`でRailsにWebpackを通してVueを入れたプロジェクトで出てくるあれこれについてまとめます。

私自身、Vue初心者でありフロントエンドスキルがないため手探りで調べて知ったことについてまとめるので、間違っている点はコメントいただけると嬉しいです。

### 注意事項

本記事で解説している内容を参考に各々で試される際は、必ず自己責任でお願いいたします。

論文のように正確に調査して資料をもとにまとめているわけではなく、私が試してわかったことをまとめているだけに過ぎません。


## Vue2とVue3のインストール方法について

とある記事で`rails webpacker:install:vue`だとVue2がインストールされるため、以下の手順でVue3がインストールできると解説されていました。

```bash
$ yarn add vue@next vue-loader@next @vue/compiler-sfc
```

しかし、Rails 7 & Webpack 4/5 の環境で`rails webpacker:install:vue`を実行したところ、`package.json`を確認すると以下のように問題なくVue3がインストールされていました。

あるバージョンから対応されたのか、もしかするとバージョンコードが書かれているだけで中身が全てVue3に対応していない可能性もあるためさらに調査が必要そうです。

```
"vue": "^3.2.36",
"vue-loader": "^17.0.0",
"vue-template-compiler": "^2.6.14",
```

もし、Vue2を使いたい場合は、以下のようにバージョンコードを変えて`node_modules`ディレクトリを削除して、再度`yarn install`してダウングレードできました。

[vue-loader](https://github.com/vuejs/vue-loader)は、GitHubのIssuesを確認したところv15.xの最新がVue2に最適そうだったので`15.9.8`にしました。
[vue-template-compiler](https://github.com/vuejs/vue/tree/main/packages/vue-template-compiler)は、Vueのリポジトリ内で管理されており、Vueのバージョンと同じようだったので`2.6.14`に合わせました。

```
"vue": "^2.6.14",
"vue-loader": "^15.9.8",
"vue-template-compiler": "^2.6.14",
```

ただ、上記で確認した際にもしかしたら`yarn add vue`の方でインストールした可能性があるため、再度`rails _7.0.3_ new`からGemfileにWebpacker(v5.4.3)を追記して`bundle install`したのちに`webpacker:install`コマンドで確認したところ、やはりVue3がインストールされたことを確認できました。

```bash
$ rails webpacker:install:vue
...
success Saved 15 new dependencies.
info Direct dependencies
├─ vue-loader@17.0.0
├─ vue-template-compiler@2.6.14
└─ vue@3.2.36
info All dependencies
├─ @vue/compiler-sfc@3.2.36
├─ @vue/reactivity-transform@3.2.36
├─ @vue/reactivity@3.2.36
├─ @vue/runtime-core@3.2.36
├─ @vue/runtime-dom@3.2.36
├─ @vue/server-renderer@3.2.36
├─ csstype@2.6.20
├─ de-indent@1.0.2
├─ hash-sum@2.0.0
├─ he@1.2.0
├─ nanoid@3.3.4
├─ sourcemap-codec@1.4.8
├─ vue-loader@17.0.0
├─ vue-template-compiler@2.6.14
└─ vue@3.2.36
✨  Done in 9.44s.
Webpacker now supports Vue.js 🎉
```


## 全部なのか部分的のどちらでVueを使うのか

デフォルトで生成される以下の`hello_vue.js`でコメントアウトに書かれている内容を翻訳してもよく理解できなかったので、いろんな方の記事をググって読み解いていました。

```javascript
/* eslint no-console: 0 */
// Run this example by adding <%= javascript_pack_tag 'hello_vue' %> (and
// <%= stylesheet_pack_tag 'hello_vue' %> if you have styles in your component)
// to the head of your layout file,
// like app/views/layouts/application.html.erb.
// All it does is render <div>Hello Vue</div> at the bottom of the page.
// （Google翻訳）
// この例を<％= javascript_pack_tag 'hello_vue'％>を追加して実行します（および
// <％= styleSheet_pack_tag 'hello_vue'％>コンポーネントにスタイルがある場合）
// レイアウトファイルのヘッドに、
// app/views/layouts/application.html.erbのように。
// それがするのは、ページの下部に<div> hello vue </div>をレンダリングすることだけです。

import Vue from 'vue'
import App from '../app.vue'

document.addEventListener('DOMContentLoaded', () => {
  const app = new Vue({
    render: h => h(App)
  }).$mount()
  document.body.appendChild(app.$el)

  console.log(app)
})


// The above code uses Vue without the compiler, which means you cannot
// use Vue to target elements in your existing html templates. You would
// need to always use single file components.
// To be able to target elements in your existing html/erb templates,
// comment out the above code and uncomment the below
// Add <%= javascript_pack_tag 'hello_vue' %> to your layout
// Then add this markup to your html template:
// （Google翻訳）
// 上記のコードはコンパイラなしでVUEを使用します。つまり、できないことを意味します
// VUEを使用して、既存のHTMLテンプレートの要素をターゲットにします。するでしょう
// 常に単一のファイルコンポーネントを使用する必要があります。
// 既存のHTML/ERBテンプレートの要素をターゲットにできるようにするために、
// 上記のコードをコメントして、以下を除外してください
// <％= javascript_pack_tag 'hello_vue'％>をレイアウトに追加します
// 次に、このマークアップをHTMLテンプレートに追加します。

//
// <div id='hello'>
//   {{message}}
//   <app></app>
// </div>
//
// import Vue from 'vue/dist/vue.esm'
// import App from '../app.vue'
//
// document.addEventListener('DOMContentLoaded', () => {
//   const app = new Vue({
//     el: '#hello',
//     data: {
//       message: "Can you say hello?"
//     },
//     components: { App }
//   })
// })
//
//
//
// If the project is using turbolinks, install 'vue-turbolinks':
//
// yarn add vue-turbolinks
//
// Then uncomment the code block below:
// （Google翻訳）
// プロジェクトがTurbolinksを使用している場合は、「Vue-Turbolinks」をインストールします。
//
// yarn add vue-turbolinks
//
// 次に、以下のコードブロックを除外してください。

//
// import TurbolinksAdapter from 'vue-turbolinks'
// import Vue from 'vue/dist/vue.esm'
// import App from '../app.vue'
//
// Vue.use(TurbolinksAdapter)
//
// document.addEventListener('turbolinks:load', () => {
//   const app = new Vue({
//     el: '#hello',
//     data: () => {
//       return {
//         message: "Can you say hello?"
//       }
//     },
//     components: { App }
//   })
// })

```

ググって以下の記事を参考にすると次のことがわかりました。

https://qiita.com/TTTaison/items/05c3e8b5023496b130d7

https://qiita.com/kyokucho1989/items/5a285256aafe7fee8b5c

https://zenn.dev/cumet04/articles/rails-vue-app

https://qiita.com/kuroda@github/items/940338b5f6f46da2c5f1#gem-%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8-vue-rails-form-builder

### イメージ

以下のように3パターンを実行して確認して次のように全部VueでViewを実装するのか、部分的にVueを使用してViewを実装する手順について把握することができました。
部分的にVueを試した2パターンは実行してもどちらも表示上は同じ結果で、コメントアウトしているため`hello.vue`が適応されているかされていないかしか（デフォルトのサンプルは簡単なプログラムであるためどちらでも支障がないという意味で）変化はなかった。
なので、必要に応じて`app/javascript/packs`内のJSコードだけで実装するか、一部Vueコードを分けて`components`管理するかは、実装する画面やプロダクトの規模感などによって変わるイメージ。

![スクリーンショット 2022-05-27 15.22.30.png.001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82601/609344ec-4417-1615-952f-db33835db356.png)

### 全部Vue

![スクリーンショット 2022-05-27 14.30.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82601/5cdb8941-a0c6-50cf-7272-62690ad15086.png)


### 部分的にVue

![スクリーンショット 2022-05-27 14.18.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82601/cb4a3387-a38f-b2ce-a1ff-6b16dd5c81b0.png)

## Vue2 → Vue3

先ほどはダウングレードでしたが、次は開発中のVueをアップデートする方法についてだけど、まだ本腰でやったことないからそのうち以下の記事などをもとにやる💪

https://zenn.dev/t0yohei/articles/59afcde9bb7c5f

https://qiita.com/yuki_0920/items/2eab16aadbe2f3a8d73e

https://madogiwa0124.hatenablog.com/entry/2021/07/10/105927

https://engineer.crowdworks.jp/entry/migrate-vue3

## VueプロジェクトからRailsプロジェクトにリソースをマージする

理想では、Railsプロジェクトでフロントエンドももバックエンドも開発できるとスッキリだけど、組織やメンバーの都合上、フロントエンドとバックエンドはリポジトリを分けて並行して開発する手法が取られるケースがある。

このときのメリットとして、

- 双方の開発上で発生する問題を分けて柔軟に取り組むことができる。
- どちらかの問題で双方の作業が止まることを避けられる。

などが考えられる反面、デメリットとして、

- VueのリソースをRailsへマージする作業が必要
- マージ作業をどのタイミングで実施するかや作業の属人化など諸々対策が必要
- 何らかの問題によってマージ漏れ問題が起きてしまう
- 二重管理になってしまい何らかの問題で片方だけ最新で古いなどの問題が起きてしまう

なども考えられる。
開発規模や組織規模が小さいのであれば、この手法のメリットは薄いもののその逆でデメリットを対策できるだけの力があれば、この手法で開発できるのかもしれない。

### サンプル

https://vuejsexamples.com/tag/table/

https://bestofvue.com/repo/linmasahiro-vue3-table-lite-vuejs-table

などから適当なサンプルを手元で用意して実際にVue → Railsへリソースのマージ作業をやってみる。
実際にリソースと画面について以下のものを使用しました。

https://github.com/gremito/vue-js-custom-datatable

![スクリーンショット 2022-06-01 12.20.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82601/d1e15298-1645-7f51-a9e5-51ef513913d1.png)

#### マージ

リソースをRailsプロジェクト内にマージするにあたって、最初にリソースの中で扱っている`node_modules`でどれをマージするのか確認する。

##### package.json/babel.config.js

https://github.com/gremito/vue-js-custom-datatable/blob/master/package.json

確認するとほぼほぼ`dependencies`と`devDependencies`をRailsプロジェクトの`package.json`に移して良さそう。

ただし、すでに移行先に同じモジュールが存在してバージョンに差分がある場合、フロントエンドを開発したメンバーに確認が必要で、今回は、最新のバージョンに変え、細かいことはわからないため一旦以下のような形になった。

```json
{
...
    "dependencies": {
        "@hotwired/stimulus": "^3.0.1",
        "@hotwired/turbo-rails": "^7.1.3",
        "@popperjs/core": "^2.11.5",
        "@rails/webpacker": "5.4.3",
        "bootstrap": "^5.1.3",
        "bootstrap-icons": "^1.8.3",
        "sass": "^1.52.1",
        "sass-loader": "^10.0.0",
        "vue": "^2.6.14",
        "vue-loader": "^15.9.8",
        "vue-template-compiler": "^2.6.14",
        "webpack": "^4.46.0",
        "webpack-cli": "^3.3.12",
        "core-js": "^3.8.3",
        "date-fns": "^2.28.0",
        "vuetify": "^2.6.0",
        "vuetify-loader": "^1.7.0",
        "vue-cli-plugin-vuetify": "~2.4.8",
        "vue-template-compiler": "^2.6.14"
    },
    "devDependencies": {
        "@babel/core": "^7.12.16",
        "@babel/eslint-parser": "^7.12.16",
        "eslint": "^7.32.0",
        "eslint-plugin-vue": "^8.0.3"
    },
...
}
```

`npm install`すると自動的に`babel.config.js`が生成され、サンプルの`babel.config.js`の中身を上書きしても`presets`に`'@vue/cli-plugin-babel/preset'`を記述するだけのどちらも`ERR_MODULE_NOT_FOUND`が発生したので、一旦書き換えずにそのままで使ってみることに。

https://github.com/gremito/vue-js-custom-datatable/blob/master/babel.config.js


##### main.js

https://github.com/gremito/vue-js-custom-datatable/blob/master/src/main.js

Vueプロジェクトの`src/main.js`のマージ先は、`app/javascript/packs`配下で`main.js`のままではなくファイル名をその画面に適した名称に変える。
今回は、`table_vue.js`として以下のようにまずは、全部Vueとして使ってみる。

```javascript:app/javascript/packs/table_vue.js
import Vue from 'vue'
import Table from '../table.vue'
import vuetify from '../plugins/vuetify'

Vue.config.productionTip = false

document.addEventListener('DOMContentLoaded', () => {
    const app = new Vue({
        vuetify,
        render: h => h(Table)
    }).$mount()
    document.body.appendChild(app.$el)
})
```

##### App.vue

https://github.com/gremito/vue-js-custom-datatable/blob/master/src/App.vue

次にVueコードのマージ先は、`app/javascript`配下に`table.vue`とファイル名を変えて中身は全く一緒で配置する。
画面の実装が増えると`.vue`ファイルがこの配下に大量にできてしまうため、その際は`app/javascript/vue`などのディレクトリを用意して管理すればいいが、今回はそのままで。


##### .vue/.js/.html.erb

https://github.com/gremito/vue-js-custom-datatable/blob/master/src/components/DataTable.vue

https://github.com/gremito/vue-js-custom-datatable/blob/master/src/plugins/vuetify.js

この2点は、`app/javascript`配下にそれぞれ同様に`components`と`plugins`のディレクトリ作って、ファイル名もそのままでそのままコピーするだけ。

最後に確認ようとして今回はわかりやすく`tables/vue`というパスを準備して、全部Vueとして確認したいため上記同様に以下のようにindexを準備する。

```erb:app/views/tables/vue/index.html.erb
<%= javascript_pack_tag 'table_vue' %>
```


#### `Unknown custom element: <v-app> - did you register the component correctly? For recursive components, make sure to provide the "name" option.`

上記の対応だけで動作確認したところ、コンパイルは問題ないものの以下のようにブラウザ上でエラーになってしまった。

![スクリーンショット 2022-06-01 15.18.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82601/51caf4cf-a7ed-6c38-b412-cba6837ce4dd.png)


ググったところ原因は、上手くVuetifyが適応できていないんだとか。

https://qiita.com/Ryoga_aoym/items/e1d91351389904240594#vuetify%E3%81%AE%E8%AA%AD%E3%81%BF%E8%BE%BC%E3%81%BF

を参考に実装を以下のように変更してみる。

```javascript:app/javascript/plugins/vuetify.js
import Vue from 'vue';
import Vuetify from 'vuetify';
import "vuetify/dist/vuetify.min.css";

Vue.use(Vuetify);

export default new Vuetify({});
```


#### 動作確認

左がVueプロジェクトで実行した画面で右がマージ作業後のRailsプロジェクト上で実行した画面で少しデザインが変わっていることがわかる。

おそらく、

- マージした際にCSSの適応が何らかの問題で外れている
- すでにRails内で使っていたCSSがVueで使うCSSを上書きしておかしくなっている

のどちらかか両方で問題になってこの結果になってしまったのか、調査が面倒なのでまた後日対応することにする。
実際に現場でこれが起きたらすぐにフロントエンドのメンバーに連絡して、一緒に調査＆対策すればいい。

<img width="350" alt="スクリーンショット 2022-06-01 16.16.06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82601/8faf9473-1470-cd01-87e0-c6e3f12f613c.png">　<img width="350" alt="スクリーンショット 2022-06-01 16.04.22.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82601/b365e64d-6497-3a6d-6084-9398b20e376e.png">