概要
RailsをAPIサーバにしてVueのクライアントアプリと疎通する構成ではなく、rails webpacker:install:vue
でRailsにWebpackを通してVueを入れたプロジェクトで出てくるあれこれについてまとめます。
私自身、Vue初心者でありフロントエンドスキルがないため手探りで調べて知ったことについてまとめるので、間違っている点はコメントいただけると嬉しいです。
注意事項
本記事で解説している内容を参考に各々で試される際は、必ず自己責任でお願いいたします。
論文のように正確に調査して資料をもとにまとめているわけではなく、私が試してわかったことをまとめているだけに過ぎません。
Vue2とVue3のインストール方法について
とある記事でrails webpacker:install:vue
だとVue2がインストールされるため、以下の手順でVue3がインストールできると解説されていました。
$ 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は、GitHubのIssuesを確認したところv15.xの最新がVue2に最適そうだったので15.9.8
にしました。
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がインストールされたことを確認できました。
$ 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
でコメントアウトに書かれている内容を翻訳してもよく理解できなかったので、いろんな方の記事をググって読み解いていました。
/* 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 }
// })
// })
ググって以下の記事を参考にすると次のことがわかりました。
イメージ
以下のように3パターンを実行して確認して次のように全部VueでViewを実装するのか、部分的にVueを使用してViewを実装する手順について把握することができました。
部分的にVueを試した2パターンは実行してもどちらも表示上は同じ結果で、コメントアウトしているためhello.vue
が適応されているかされていないかしか(デフォルトのサンプルは簡単なプログラムであるためどちらでも支障がないという意味で)変化はなかった。
なので、必要に応じてapp/javascript/packs
内のJSコードだけで実装するか、一部Vueコードを分けてcomponents
管理するかは、実装する画面やプロダクトの規模感などによって変わるイメージ。
全部Vue
部分的にVue
Vue2 → Vue3
先ほどはダウングレードでしたが、次は開発中のVueをアップデートする方法についてだけど、まだ本腰でやったことないからそのうち以下の記事などをもとにやる💪
VueプロジェクトからRailsプロジェクトにリソースをマージする
理想では、Railsプロジェクトでフロントエンドももバックエンドも開発できるとスッキリだけど、組織やメンバーの都合上、フロントエンドとバックエンドはリポジトリを分けて並行して開発する手法が取られるケースがある。
このときのメリットとして、
- 双方の開発上で発生する問題を分けて柔軟に取り組むことができる。
- どちらかの問題で双方の作業が止まることを避けられる。
などが考えられる反面、デメリットとして、
- VueのリソースをRailsへマージする作業が必要
- マージ作業をどのタイミングで実施するかや作業の属人化など諸々対策が必要
- 何らかの問題によってマージ漏れ問題が起きてしまう
- 二重管理になってしまい何らかの問題で片方だけ最新で古いなどの問題が起きてしまう
なども考えられる。
開発規模や組織規模が小さいのであれば、この手法のメリットは薄いもののその逆でデメリットを対策できるだけの力があれば、この手法で開発できるのかもしれない。
サンプル
などから適当なサンプルを手元で用意して実際にVue → Railsへリソースのマージ作業をやってみる。
実際にリソースと画面について以下のものを使用しました。
マージ
リソースをRailsプロジェクト内にマージするにあたって、最初にリソースの中で扱っているnode_modules
でどれをマージするのか確認する。
package.json/babel.config.js
確認するとほぼほぼdependencies
とdevDependencies
をRailsプロジェクトのpackage.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
が発生したので、一旦書き換えずにそのままで使ってみることに。
main.js
Vueプロジェクトのsrc/main.js
のマージ先は、app/javascript/packs
配下でmain.js
のままではなくファイル名をその画面に適した名称に変える。
今回は、table_vue.js
として以下のようにまずは、全部Vueとして使ってみる。
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
次にVueコードのマージ先は、app/javascript
配下にtable.vue
とファイル名を変えて中身は全く一緒で配置する。
画面の実装が増えると.vue
ファイルがこの配下に大量にできてしまうため、その際はapp/javascript/vue
などのディレクトリを用意して管理すればいいが、今回はそのままで。
.vue/.js/.html.erb
この2点は、app/javascript
配下にそれぞれ同様にcomponents
とplugins
のディレクトリ作って、ファイル名もそのままでそのままコピーするだけ。
最後に確認ようとして今回はわかりやすくtables/vue
というパスを準備して、全部Vueとして確認したいため上記同様に以下のようにindexを準備する。
<%= 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.
上記の対応だけで動作確認したところ、コンパイルは問題ないものの以下のようにブラウザ上でエラーになってしまった。
ググったところ原因は、上手くVuetifyが適応できていないんだとか。
を参考に実装を以下のように変更してみる。
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を上書きしておかしくなっている
のどちらかか両方で問題になってこの結果になってしまったのか、調査が面倒なのでまた後日対応することにする。
実際に現場でこれが起きたらすぐにフロントエンドのメンバーに連絡して、一緒に調査&対策すればいい。