30歳からのプログラミング

30歳無職から独学でプログラミングを開始した人間の記録。

Webpacker の基本的な仕組み

webpackではなく、Rails の gem であるWebpackerの話。

Webpackerを止める方法について書く予定だったが、前提であるWebpackerの説明だけで長くなってしまったので別の記事として切り出した。

脱 Webpacker についてはこちら。 numb86-tech.hatenablog.com

rails newから始めて、WebpackerVueを使う方法について説明していく。

以下のバージョンで作業している。

プロジェクトの作成

まず、$ rails new sample_app --webpack --skip-turbolinksでプロジェクトを作成する。

--webpackオプションによって、Webpackerがインストールされた状態でプロジェクトが作成される。

作成されたプロジェクトを見ると、app/javascriptというディレクトリがある。これが、Webpackerに関するディレクトリ。そして、app/javascript/packsに入っているファイルがエントリファイルになる。

以下で、動作確認用の適当なデータを用意する。

$ bin/rails g scaffold post title:string
$ bin/rails db:migrate

$ bin/rails sでサーバーを起動してhttp://localhost:3000/postsにアクセスすれば、ページが表示されるはず。
この画面に、Webpackerでビルドした内容を反映させていく。

javascript_pack_tag

Webpackerでビルドしたスクリプトを読み込むためにはjavascript_pack_tagを使う。

例えば ERB ファイルのなかで<%= javascript_pack_tag 'application' %>とすると、app/javascript/packs/application.jsが読み込まれる。
試しにこれをapp/views/posts/index.html.erbに書いてみる。

app/javascript/packs/application.jsにはconsole.log('Hello World from Webpacker')とあるので、これが実行される。

app/javascript/packs/application.jsを更新してからブラウザをリロードすれば、更新した内容が実行される。
変更を検知して自動的にビルドやブラウザのリロードを行うには、webpack-dev-serverを使う。具体的には、rails sを走らせているときに別プロセスで$ ./bin/webpack-dev-serverを実行すればよい。

ビルドが行われると、public/packs/manifest.jsonが生成・更新される。

{
  "application.js": "/packs/application-xxxxx.js",
  "application.js.map": "/packs/application-xxxxx.js.map"
}

このファイルによって、app/javascript/packsに入っているエントリファイルと、public/packsに生成されたダイジェスト値付きのバンドルファイルを紐づけている。

そして、テンプレートファイル(app/views/posts/index.html.erb)を元に作られたHTMLファイルを見ると、ちゃんとダイジェスト付きのファイルを読み込んでいることが分かる。

<script src="/packs/application-xxxxx.js"></script>

以上のように、ダイジェスト付きのバンドルファイルをビルドし、マニフェストファイルによってバンドルファイルとHTMLを紐付けるのが、Webpackerが果たしている役割である。

なお、$ ./bin/webpack-dev-server実行時に生成されるのはpublic/packs/manifest.jsonのみで、バンドルファイルはメモリ上に存在するだけで実際には生成されない。

プロダクション環境で確認する

先程はデベロップメント環境での確認だったので、プロダクションでも確認してみる。
まずは、ローカルでプロダクション環境を起動するための準備を行う。

  1. config/master.keyが存在するかを確認する。
  2. 環境変数RAILS_SERVE_STATIC_FILESに適当な値を入れる。$ export RAILS_SERVE_STATIC_FILES=1
  3. $ bin/rails db:migrate RAILS_ENV=productionを実行して、プロダクション環境のデータベースにpostsテーブルを作成する。

これでプロダクション環境を利用できるようになる。

まず、分かりやすくするためにpublic/packsを丸ごと削除してみる。
次に、$ bin/rails assets:precompile RAILS_ENV=productionを実行する。デフォルトでは、このコマンドでWebpackerによるビルドも行われる。
public/packsマニフェストファイルとバンドルファイルが生成されているのを確認できる。

$ bin/rails s -e productionでプロダクション環境でサーバーが起動される。
http://localhost:3000/postsを表示すると、正しくバンドルファイルが読み込まれているのを確認できる。

stylesheet_pack_tag

Webpackerでは最初から、.css.sassなどのスタイルシートにも対応している。
stylesheet_pack_tagを使うことで利用できる。

試しにapp/javascript/packs/application.sassを作成し、app/views/posts/index.html.erbで読み込ませてみる。

// app/javascript/packs/application.sass
h1
  border-bottom: 3px solid #000
<%= stylesheet_pack_tag 'application' %>

http://localhost:3000/postsを表示すると、<link rel="stylesheet" media="screen" href="/packs/application-xxxxx.css" />でビルドされたスタイルシートを読み込んでいるのが分かる。

f:id:numb_86:20190109105819p:plain

もちろんpublic/packs/manifest.jsonにも追加されている。

Vue の導入

Webpackerを使えば、React や Vue などのライブラリを簡単に導入できる。 今回は Vue を使ってみる。

$ bin/rails webpacker:install:vueを実行すれば、Vue を使えるようになる。

サンプルファイルとしてapp/javascript/app.vueapp/javascript/packs/hello_vue.jsが自動生成されるので、これを読み込ませてみる。

diff --git a/app/views/posts/index.html.erb b/app/views/posts/index.html.erb
 <%= link_to 'New Post', new_post_path %>
 
 <%= javascript_pack_tag 'application' %>
 <%= stylesheet_pack_tag 'application' %>
+<%= javascript_pack_tag 'hello_vue' %>

ビルドされたpacks/hello_vue-xxxxx.jsが読み込まれ、Hello Vue!と表示されているのが分かる。

f:id:numb_86:20190109105857p:plain

しかし、スタイルが反映されていない。
app/javascript/app.vueには以下の記述があるので、pタグについては中央寄せになっていないとおかしい。

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

マニフェストファイルを見ると、hello_vue.jsだけでなくhello_vue.cssも出力されているのが分かる。

{
  "application.css": "/packs/application-xxxxx.css",
  "application.css.map": "/packs/application-xxxxx.css.map",
  "application.js": "/packs/application-xxxxx.js",
  "application.js.map": "/packs/application-xxxxx.js.map",
  "hello_vue.css": "/packs/hello_vue-xxxxx.css",
  "hello_vue.css.map": "/packs/hello_vue-xxxxx.css.map",
  "hello_vue.js": "/packs/hello_vue-xxxxx.js",
  "hello_vue.js.map": "/packs/hello_vue-xxxxx.js.map"
}

実はデフォルトでは、Webpackerスタイルシートは別ファイルとして抽出してビルドするようになっている。
そのため、スタイルシートも別途読み込む必要がある。

diff --git a/app/views/posts/index.html.erb b/app/views/posts/index.html.erb
 <%= javascript_pack_tag 'application' %>
 <%= stylesheet_pack_tag 'application' %>
 <%= javascript_pack_tag 'hello_vue' %>
+<%= stylesheet_pack_tag 'hello_vue' %>

これで、スタイルシートも反映される。

f:id:numb_86:20190109105912p:plain

画像の利用

次は画像の扱いを見てみる。

次の2つの画像を用意し、それぞれ、スタイルシートと Vue のコンポーネントで使用する。

diff --git a/app/javascript/app.vue b/app/javascript/app.vue
index e304dc1..cb37cff 100644
--- a/app/javascript/app.vue
+++ b/app/javascript/app.vue
@@ -1,6 +1,7 @@
 <template>
   <div id="app">
     <p>{{ message }}</p>
+    <img src="./images/special/usa.png">
   </div>
 </template>
 
diff --git a/app/javascript/packs/application.sass b/app/javascript/packs/application.sass
index de2c914..c613a49 100644
--- a/app/javascript/packs/application.sass
+++ b/app/javascript/packs/application.sass
@@ -1,2 +1,4 @@
 h1
   border-bottom: 3px solid #000
+p
+  background-image: url('../images/bg.png')

こうすると、次のように表示される。

f:id:numb_86:20190109105929p:plain

マニフェストファイルには、画像について追加されている。

"images/bg.png": "/packs/images/bg-xxxxx.png",
"images/special/usa.png": "/packs/images/special/usa-xxxxx.png"

ユニットテストの導入

Vue コンポーネントユニットテストも導入できる。
詳しくは以下の記事を参照。 numb86-tech.hatenablog.com

Webpacker の設定

Webpackerの設定を確認するには、config/webpack/environment.jsを見ればよい。
このファイルの中に出てくるenvironmentに設定が入っている。

const { environment } = require('@rails/webpacker');

module.exports = environment;

だがこれはWebpacker専用の特殊なオブジェクトなので、environment.toWebpackConfig()webpack本来の設定に置き換える必要がある。

diff --git a/config/webpack/development.js b/config/webpack/development.js
index c5edff9..617a568 100644
--- a/config/webpack/development.js
+++ b/config/webpack/development.js
@@ -2,4 +2,7 @@ process.env.NODE_ENV = process.env.NODE_ENV || 'development'
 
 const environment = require('./environment')
 
+console.log(environment.toWebpackConfig());