この記事は
の続編です。
今回の記事では、これまで環境構築をしてきたRailsアプリケーションにVue.jsを導入する方法について記載します。
Rails6の初期装備に従って進めていますので、turbolinksを使いますし、Webpackerを使います。
turbolinksを使いたくない、または、Webpackerを使いたくない、という方にとっては参考にならない情報である可能性が高いです。また、この一連の記事の「想定読者、方針、前提」については1つ目の記事の冒頭に記載しているのでご確認ください
ではやっていきましょう。
事前準備
これまでの記事で作ってきたRailsアプリケーションは、アプリケーションのロジック自体にはまだ手を加えていないので、「Yay! You're on Rails!」の画面(以後、Welcome画面と呼びます)しか表示されません。これではこのあとVue.jsを導入するにあたって説明しにくいので、準備として適当なリソースをscaffoldで作っておくことにします。
(すでに何らかの手を加えていてWelcome画面以外が表示されるようになっている場合は、この事前準備はskipしてOKです)
現状確認
docker-compose up
で起動して、現状を確認してみましょう。
$ docker-compose up
docker-compose-rails-sample_db_1 is up-to-date
Creating docker-compose-rails-sample_web_1 ... done
Attaching to docker-compose-rails-sample_db_1, docker-compose-rails-sample_web_1
db_1 |
db_1 | PostgreSQL Database directory appears to contain a database; Skipping initialization
db_1 |
...(略)...
web_1 | => Booting Puma
web_1 | => Rails 6.0.3.1 application starting in development
web_1 | => Run `rails server --help` for more startup options
web_1 | Puma starting in single mode...
web_1 | * Version 4.3.5 (ruby 2.7.1-p83), codename: Mysterious Traveller
web_1 | * Min threads: 5, max threads: 5
web_1 | * Environment: development
web_1 | * Listening on tcp://0.0.0.0:3000
web_1 | Use Ctrl-C to stop
localhost:3000 にアクセスするとWelcome画面が表示されるはずです。
Usersリソースを作る
今後の説明の準備として、適当なリソースをscaffoldで作っていきます。
まずはWebコンテナの中に入りましょう。
$ docker-compose exec web bash
root@2a2467ff3a66:/myapp#
コンテナの中でコマンドを実行していきます。
ここでは第2章 Toyアプリケーション - Railsチュートリアルに倣ってUsersリソースを作ることにします。(ほかに作りたいリソースがあればそれでも構いません)
root@2a2467ff3a66:/myapp# bundle exec rails generate scaffold User name:string email:string
invoke active_record
create db/migrate/20200613085052_create_users.rb
create app/models/user.rb
invoke test_unit
create test/models/user_test.rb
create test/fixtures/users.yml
invoke resource_route
route resources :users
invoke scaffold_controller
create app/controllers/users_controller.rb
invoke erb
create app/views/users
create app/views/users/index.html.erb
create app/views/users/edit.html.erb
create app/views/users/show.html.erb
create app/views/users/new.html.erb
create app/views/users/_form.html.erb
invoke test_unit
create test/controllers/users_controller_test.rb
create test/system/users_test.rb
invoke helper
create app/helpers/users_helper.rb
invoke test_unit
invoke jbuilder
create app/views/users/index.json.jbuilder
create app/views/users/show.json.jbuilder
create app/views/users/_user.json.jbuilder
invoke assets
invoke scss
create app/assets/stylesheets/users.scss
invoke scss
create app/assets/stylesheets/scaffolds.scss
migrationファイルが作られてますね、migrationしましょう。
root@2a2467ff3a66:/myapp# bundle exec rails db:migrate
== 20200613085052 CreateUsers: migrating ======================================
-- create_table(:users)
-> 0.0613s
== 20200613085052 CreateUsers: migrated (0.0614s) =============================
ついでに、root pathにアクセスが来たらUser一覧画面を表示するようにルーティングを書き換えておきます。
Rails.application.routes.draw do
resources :users
- # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
+
+ root to: 'users#index'
end
動作確認
localhost:3000 にアクセスするとUser一覧画面が表示されるはずです。
Vue.jsを動かす
RailsアプリケーションにVue.jsをインストールして動かしてみましょう
Vue.jsのインストール
bundle exec rails webpacker:install:vue
で簡単にインストールできます。
(ref: webpacker/integrations.md at master · rails/webpacker)
root@2a2467ff3a66:/myapp# bundle exec rails webpacker:install:vue
Copying vue loader to config/webpack/loaders
create config/webpack/loaders/vue.js
Adding vue loader plugin to config/webpack/environment.js
insert config/webpack/environment.js
insert config/webpack/environment.js
Adding vue loader to config/webpack/environment.js
insert config/webpack/environment.js
insert config/webpack/environment.js
Updating webpack paths to include .vue file extension
insert config/webpacker.yml
Copying the example entry file to /myapp/app/javascript/packs
create app/javascript/packs/hello_vue.js
Copying Vue app file to /myapp/app/javascript/packs
create app/javascript/app.vue
Installing all Vue dependencies
run yarn add vue vue-loader vue-template-compiler from "."
yarn add v1.13.0
[1/4] Resolving packages...
[2/4] Fetching packages...
info fsevents@2.1.3: The platform "linux" is incompatible with this module.
info "fsevents@2.1.3" is an optional dependency and failed compatibility check. Excluding it from installation.
info fsevents@1.2.13: The platform "linux" is incompatible with this module.
info "fsevents@1.2.13" is an optional dependency and failed compatibility check. Excluding it from installation.
[3/4] Linking dependencies...
warning " > webpack-dev-server@3.11.0" has unmet peer dependency "webpack@^4.0.0 || ^5.0.0".
warning "webpack-dev-server > webpack-dev-middleware@3.7.2" has unmet peer dependency "webpack@^4.0.0".
warning " > vue-loader@15.9.2" has unmet peer dependency "css-loader@*".
warning " > vue-loader@15.9.2" has unmet peer dependency "webpack@^3.0.0 || ^4.1.0 || ^5.0.0-0".
[4/4] Building fresh packages...
success Saved lockfile.
warning Your current version of Yarn is out of date. The latest version is "1.22.4", while you're on "1.13.0".
success Saved 12 new dependencies.
info Direct dependencies
├─ vue-loader@15.9.2
├─ vue-template-compiler@2.6.11
└─ vue@2.6.11
info All dependencies
├─ @vue/component-compiler-utils@3.1.2
├─ consolidate@0.15.1
├─ de-indent@1.0.2
├─ he@1.2.0
├─ merge-source-map@1.1.0
├─ prettier@1.19.1
├─ vue-hot-reload-api@2.3.4
├─ vue-loader@15.9.2
├─ vue-style-loader@4.1.2
├─ vue-template-compiler@2.6.11
├─ vue-template-es2015-compiler@1.9.1
└─ vue@2.6.11
Done in 25.23s.
Webpacker now supports Vue.js 🎉
hello_vue.jsを動かす
先ほどのコマンドでapp/javascript/packs/hello_vue.js
が作られています。
これはサンプルファイルで、こんな感じで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.
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)
})
コードコメントを訳すと
「このサンプルはレイアウトファイル(例えばapp/views/layouts/application.html.erb
)のheadに<%= javascript_pack_tag 'hello_vue' %>
を追記することで実行できるよ。(コンポーネントに独自のstyleを適用したい場合は<%= stylesheet_pack_tag 'hello_vue' %>
も追記してね)」
ということですね。
コード自体は「画面の読み込み時に、Appコンポーネントの中身をbodyタグに追記する」という処理になっています。
Appコンポーネントの中身はこれで、id="app"
なdivタグの中に"Hello Vue!!"
を表示するだけの単純なやつです。
<template>
<div id="app">
<p>{{ message }}</p>
</div>
</template>
<script>
export default {
data: function () {
return {
message: "Hello Vue!"
}
}
}
</script>
<style scoped>
p {
font-size: 2em;
text-align: center;
}
</style>
ではhello_vue.js
のコードコメントに書かれている通り、レイアウトファイルに<%= javascript_pack_tag 'hello_vue' %>
を追記してみましょう。
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
+ <%= javascript_pack_tag 'hello_vue' %>
</head>
<body>
その状態で localhost:3000 にアクセスするとこうなります。
お〜、よさそう?
問題発生
一見よさそうに見えるのですが、「New User」のリンクをクリックして画面遷移すると"Hello Vue!!"
の表示が消えてしまいます。
その状態で画面をリロードすると"Hello Vue!!"
の表示が出てきます。
不可解な挙動になっていますね。
それもそのはず、Rails6の初期装備ではturbolinks
というライブラリが有効になっており、無駄な画面の読み込みをなくし、高速に表示できるようになっているのです。
(ref: turbolinks/turbolinks: Turbolinks makes navigating your web application faster)
hello_vue.js
の実装に戻ってみると
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)
})
document.addEventListener('DOMContentLoaded', ...
を使っていますね。
これは画面を読み込んだ時だけ走るので、画面をリロードすると"Hello Vue!!"
の表示が出てくる、という挙動になっています。
この問題を解消するには、turbolinksの仕組みに合わせた実装にする必要がありそうです。
vue-turbolinksを使う
実はhello_vue.js
の下の方にこのような記載があります。
// If the project is using turbolinks, install 'vue-turbolinks':
//
// yarn add vue-turbolinks
//
// Then uncomment the code block below:
//
// 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 }
// })
// })
「もしturbolinksを使ってるなら、vue-turbolinks
をインストールしよう。yarn add vue-turbolinks
だ。そしてこの下のコードを有効化してくれい」
なるほど。よし、vue-turbolinks
を入れましょう。
root@2a2467ff3a66:/myapp# yarn add vue-turbolinks
コメントアウトされているサンプルコードを参考に、hello_vue.js
を書き換えてみます。
import TurbolinksAdapter from 'vue-turbolinks'
import Vue from 'vue'
import App from '../app.vue'
Vue.use(TurbolinksAdapter)
document.addEventListener('turbolinks:load', () => {
const app = new Vue({
render: h => h(App)
}).$mount()
document.body.appendChild(app.$el)
console.log(app)
})
TurbolinksAdapter
をimportしてVue.use
し、イベントリスナーを'turbolinks:load'
に書き換えただけです。
この状態で localhost:3000 にアクセスして、ポチポチと動作確認をしてみましょう。
画面をリロードしなくても、常に"Hello Vue!!"
が表示されるようになっていますね!
User画面をVue.jsで書き換えてみる
ここまででVue.jsの動作確認をすることができました。
あとはVue.jsのガイド等を読みながら、必要に応じてVue.jsを使っていただければOKです。
この記事では一例としてusers#show
で表示される画面をVue.jsに書き換えてみます。
*あくまで一連の流れを示すための例で、コンポーネントの切り方やディレクトリ構成は雑です。というかあまりフロントエンドに詳しくないので正直なところ雰囲気でやっています。「こうしたほうがいいよ〜」等あればコメントください!
既存の画面の確認
view_templateはこうなっています。
<p id="notice"><%= notice %></p>
<p>
<strong>Name:</strong>
<%= @user.name %>
</p>
<p>
<strong>Email:</strong>
<%= @user.email %>
</p>
<%= link_to 'Edit', edit_user_path(@user) %> |
<%= link_to 'Back', users_path %>
5つのデータ(notice
, @user.name
, @user.email
, edit_user_path(@user)
, users_path
)を表示に使っていますね。
これらのデータをjsで処理してVueコンポーネントに渡して表示する、という一連の流れを実装していきます。
書き換えていく
まずはview_templateをこのように書き換えましょう。
<div
class="js-users-show"
data-notice="<%= notice %>"
data-name="<%= @user.name %>"
data-email="<%= @user.email %>"
data-edit-user-path="<%= edit_user_path(@user) %>"
data-users-path="<%= users_path %>"
></div>
js-users-show
クラスのdivタグに、data属性として先ほどの5つのデータを持たせました。
続いてVueコンポーネントを実装します。
今回はapp/javascript
の下にcomponents/users
ディレクトリを作り、UsersShow.vue
というコンポーネントを作ることにします。
<template>
<div>
<p id="notice">{{ notice }}</p>
<p>
<strong>Name:</strong>
{{ name }}
</p>
<p>
<strong>Email:</strong>
{{ email }}
</p>
<a :href="editUserPath">Edit</a> |
<a :href="usersPath">Back</a>
</div>
</template>
<script>
export default {
name: 'UsersShow',
props: {
notice: {
type: String,
required: true
},
name: {
type: String,
required: true
},
email: {
type: String,
required: true
},
editUserPath: {
type: String,
required: true
},
usersPath: {
type: String,
required: true
}
}
}
</script>
<style scoped>
</style>
5つのデータをプロパティとして受け取り、<templete>
にはもともとのview_templateにあった内容をコピーしてきて、それぞれのプロパティをそれぞれの位置に配置しているだけです。
続いて、view_templateからVueコンポーネントにデータを受け渡す処理を書いていきます。packs
配下にusers.js
を実装します。
/* eslint no-console: 0 */
import TurbolinksAdapter from 'vue-turbolinks'
import Vue from 'vue'
import UsersShow from '../components/users/UsersShow.vue'
Vue.use(TurbolinksAdapter)
const mountUsersShow = () => {
const el = document.querySelector('.js-users-show')
if (el == null) {
return
}
const { notice, name, email, editUserPath, usersPath } = el.dataset
const props = {
notice: notice,
name: name,
email: email,
editUserPath: editUserPath,
usersPath: usersPath
}
new Vue({
el: el,
render: h => h(UsersShow, { props })
}).$mount()
}
document.addEventListener('turbolinks:load', () => {
mountUsersShow()
})
js-users-show
クラスの要素を取得して、その要素のdata属性をprops
としてUsersShowコンポーネントに渡し、js-users-show
クラスの要素をマウントする、という実装になっています。
最後に、レイアウトファイルに<%= javascript_pack_tag 'users' %>
を追記したら完了です。
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
<%= javascript_pack_tag 'hello_vue' %>
+ <%= javascript_pack_tag 'users' %>
</head>
<body>
画面の見た目は最初とまったく変わらないはずです。
余談
この例ではusers#show
の画面を書き換えてみましたが、実際にはこの画面のように「動的なDOMの切り替えがない画面」をVue.jsに書き換えるメリットはあまりないかもしれません。(Vue.js用のコンポーネントライブラリを使ってデザインをあてたい、とか、静的な画面についてもjsでテスト(コンポーネントの単体テストなど)を書いておきたい、などのシーンではメリットがあるかもですが)
やはりメリットがあるのは、例えばユーザの画面クリックに応じて表示するコンポーネントを切り替えたり、フォームへの入力内容に応じてテキストを表示したりとか、そういった動的なDOMの切り替えが必要な画面かなぁと思います。
今回、はじめは入力フォームapp/views/users/_form.html.erb
を書き換えようかと思ったのですが、これをやるとCSRFトークンなどの話も含まないといけなくなり、全体の流れが伝わりにくくなりそうな予感がしたのでやめました。また改めて書こうかと思います。
まとめ
今回の記事では、これまでの記事で環境構築をしてきたRailsアプリケーションに、Vue.jsを導入する方法について記載しました。参考になれば幸いです。
次の記事はどうしようか迷っていますが、開発中にjsをいじった後に画面にアクセスすると、コンパイルが走って読み込みに時間が掛かるのがつらいので、webpack-dev-serverの設定について書こうかなと思います。予定は未定です。
- Docker Composeを使った初期装備なRails6/PostgreSQL環境の作り方 - Qiita
- Docker Composeのvolumesを使ってもっと効率的に - Qiita
- Docker Composeで作った初期装備なRails6にVue.jsを導入する (今回の記事)
- Docker Composeでwebpack-dev-serverを動かしてもっと快適に (未定)
ではまた〜。