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());

『WEB+DB PRESS Vol.107』の「実践CircleCI」を読んだ

職場にあったので何となく目を通してみたら、思ったより初歩から説明してあったのでちゃんと読むことにした。

gihyo.jp

CircleCI、というかCIについてはほとんど何も知らないレベルだったが、ちゃんと入っていけた。
最後のほうは流し読みだから大した知識は身についていないが、取っ掛かりを持てたという意味で、意義はあった。

以前の記事に書いたように自分のサイトを構築し直してCircleCIでビルドやデプロイを行えるようにしたのだが、そのキッカケもこの特集を読んだことだった。
すごく難しい印象があったけど、「複雑なことをやろうとしなければ結構いけるかもしれない」と思えた。実際、いけた。

numb86-tech.hatenablog.com

すごく便利だし、強いプログラマたちに少し近づけた気がして楽しかった。

敷居が高そうな技術でも、興味があったりいずれ習得する必要があると思っているのなら、積極的に触ってみるのが大事だなと学んだ。
初歩的な知識でも、それがあるのとないのとでは大きく違うし、関連する記事を見つけたときも「取り敢えず読んでみよう」と積極的になれる。

以下、メモ。

第1章 はじめてのCircleCI

  • XP(エクストリーム・プログラミング)のプラクティスの一つとして、CI(継続的インテグレーション)がある
    • CIとは、コードを常にテスト、ビルドすることで、プロダクトの品質を保つことを目的としている
    • CD(継続的デリバリ)というものもある
      • デプロイを自動化することで、デプロイに伴う作業負担の減少や、スピーディーなリリースを実現する
  • コードを常にテスト、ビルドする、という部分を自動化するためのツールとしてCIツールがある
  • CircleCIはCIツールのひとつで、SaaS(Software as a Service)として提供されている
    • SaaSなので、自分でサーバーを用意する必要がない
  • CircleCIは、Docker環境の中でCIを実行する
  • CircleCIはワークフローという仕組みにより、並列処理などの複雑な実行が可能になっている
  • CircleCIの利用目的は、ビルド、テスト、デプロイの3つに大別できる
    • ビルドできる状態の維持、テストにパスした状態の維持、CD、を実現する

第2章 環境構築

  • CircleCIはcircleciコマンドでローカルでジョブを実行できる
  • circleciはDocker上で実行するので、まずDockerをインストールしておく必要がある
  • 次にCircleCIのCLIツールをダウンロードする
  • $ circleci update checkでアップデートがないかチェックできる
  • 設定ファイルは.circleci/config.yml
  • $ circleci config validateで、設定ファイルの構文チェックが出来る
  • $ circleci local executeでローカルでジョブを実行できる
    • 以下の.circleci/config.ymlが、シンプルな例
version: 2
jobs:
  build:
    docker:
      - image: circleci/node:8.11.3
    steps:
      - run: echo "hello world"
  • デフォルトではbuildジョブを実行するが--jobオプションでジョブを指定できる
  • -eオプションで環境変数を設定できる
  • $ circleci local executeを以下のようにして試してみる
    • $ circleci local execute --job build2だとnopeが出力される
    • $ circleci local execute --job build2 -e HOGE=fooだとfooが出力される
version: 2
jobs:
  build:
    docker:
      - image: circleci/node:8.11.3
    steps:
      - run: echo "hello world"
  build2:
    docker:
      - image: circleci/node:8.11.3
    steps:
      - run: echo ${HOGE-nope}

第3章 基本設定

  • 設定は全て.circleci/config.ymlに書かれる
  • 主要なキー
    • version
      • CircleCIのバージョンを指定する
    • jobs
      • ジョブを1つ以上定義する。1つしか定義しない場合は必ずbuildという名前にしないといけない。
    • docker
      • ジョブを実行する環境を記述。docker以外にもmachineなども使える。
    • steps
      • 実行したいコマンドを1つ以上定義する
      • ユーザーが自由に記述できるrunと、checkoutのようにCircleCIによって用意されているステップがある
        • runnamecommandの組み合わせという形でも記述できる
version: 2
jobs:
  build:
    docker:
      - image: circleci/node:8.11.3
    steps:
      - run:
        name: Run install
        command: yarn install
      - run: yarn build
  • CircleCIが予め用意しているステップ(の一部)

第4章 ビルド

  • ビルドするためにいつも行っていたコマンドを設定ファイルに記述することで、CircleCIでビルドを行えるようになる
  • CircleCIには、予め用意されている「ビルトイン環境変数」というものがある
    • CircleCIの実行時に最初に表示される
    • アプリケーションにビルド情報を埋め込むのに使ったりする
    • ここで一覧を見れる
  • CircleCIでは各種言語やミドルウェアのプリビルドイメージをDockerHubに用意しているので、それを利用できる
    • 言語イメージはrubynode、サービスイメージはmysqlなどがある
  • アプリケーションのビルドだけでなく、Dockerイメージのビルドも可能

第5章 テスト

  • ビルドと同じようにテストもCircleCIで自動化出来る
  • store_test_resultsというステップを使うことでレポートファイルを保存できる
  • CircleCIの並列実行には、パラレルジョブとマルチコンテナの2種類がある
    • パラレルジョブは、同じジョブを2つ以上のコンテナを使って並列に実行する方法
      • 例えばテストファイルを分割して並列実行することなどが出来る
    • マルチコンテナは、複数のコンテナを使ってジョブを並列実行する方法
      • 依存関係のない処理を並列で走らせることで、実行時間の短縮が期待できる

第6章 デプロイ

  • デプロイには、コードを直接デプロイするケースや、DockerイメージのDockerレジストリへのデプロイ、コンテナのコンテナサービスへのデプロイなど、いくつかの種類がある
  • デプロイの有無やデプロイ先をリポジトリのブランチタグの有無によって切り替えることが出来る
  • triggersを使うことで、タスクの定期実行も設定できる
  • Contextsという機能を使うことで、環境変数の値を切り替えることが出来る
    • 複数のAWSアカウントを使っているときなどに便利