こんにちは!12月に子供が生まれたばかりの鈴木( @suzan2go ) です。現在は週2~3日リモートで子供の成長を片目にみつつコードを書いています。うちの子はガラピコぷ〜がお気に入りです。
さて今回はRailsでのフロントエンド開発についてです。 昨今のフロントエンドの進化はめまぐるしく、Rails標準のSprocketsというgemでJavaScriptやCSSをコンパイルする仕組みでは以下のような要望に答えられなくなってきています。*1
- ECMAScript6で書きたい!
- フロントエンドのライブラリ管理にnpmを使いたい!
で、上記に対応するにはおおまかに分類すると以下のような方法があります。
browserify-rails を使う
github.com これが一番導入が簡単ですし、既存のRailsアプリに突っ込むならこれが選択肢としては手堅いと思います。 ただ開発中のビルドがめっちゃ遅いのでJSをメインで書かなければいけない機能の開発では結構ストレスがたまります。
ビルドしたものを Sprockets のパスに突っ込む
browserify / webpack などで一度ビルドしたものをSprocketsのパスに突っ込み、最終的なビルドはSprocketsに任せます。
この方法なら開発中のビルドはwatchifyなどを使って差分で実施できるのでbrowserify-railsに比べてビルド時間はかなり短くできます( crowdworks は現在この方法で一部のJSのビルドを行っています。)。
ただ自分の開発環境のせいかもしれませんが何度かビルドし直す / F5連打しないと更新されなかったりといった問題がありますし、Sprocketsに一度読み込ませる方式だと後述する Hot Module Replacemet といった便利な機能がそのまま使用できません。
Sprocketsは使わずにビルドしたものをpublicに出力してRailsから読み込む
シンプルにSprocketsは使わず、ビルドを全てNodeの世界で行うことで、上記の問題を全て解決することができ、かつ後述する開発効率を上げる便利な機能を使用することが出来ます。 当然、Sprocketsでやっていたことは自前で全部やる必要がありますが、webpackを使ってみるとそこまで大変でもなかったのでご紹介させていただきます!
そもそもwebpackとは
全部入りのJSビルドツールです。JSビルドツールと書きましたが、CSSや画像イメージも require
できたり、開発用のサーバが提供されたりと本当に全部入りです。
webpack.github.io
browserify と比べると、webpack はそれ単体で様々な機能が提供されているため、gulpなどを使わなくともwebpack の設定ファイルを書くだけで大体のことが実現可能です。
所感ですが、GitHubを見ていてもビルドにはbrowserifyよりも webpack 使っているレポジトリの方が最近では多い気がします。 https://www.google.com/trends/explore#q=Webpack%2C%20Browserify&cmpt=q&tz=
webpackでSprocketsの機能を置き換える
今回のサンプルコードはこちら
Sprocketsをwebpackで代替するために以下を実施していきます。
digest付きのassetsを生成する
Sprocketsはproduction環境ではファイル名の後に、application-xxxxxxxxxxxx.js のようにファイルのdigest値を計算し付与しています。ファイルが更新された場合にはdigest値が変わり自動で読み込み先が変わるので、キャッシュ無効化などを考える必要がないようになっています。
これを webpack で実現するための plugin として、 webpack-manifest-plugin というプラグインが提供されています。 www.npmjs.com このプラグインを使用すると、digest値付きのassetsファイルと、manifest.json というdigest値付きのファイルと元のファイル名の対応が記録されたファイルが作成されます。 この manifest.json を Rails 側から読み出せば、digest値付きのビルドされたファイルを読みだす事ができるというわけです。*2
productionではJSを圧縮して生成する
webpack の標準コマンドで JS を圧縮して生成出来るようになっています。
ビルド時にオプション -p
を渡して上げればOKです。
webpack -p
webpackでCSSも生成する
先に述べたとおり、webpack では css ファイルも require 出来るようになっています。どういうことかというと、
このようにCSSを書いて、
// frontend/stylesheets/application.scss $icon-font-path: "~bootstrap-sass/assets/fonts/bootstrap/"; @import "~bootstrap-sass/assets/stylesheets/_bootstrap.scss";
JS側から import すると
import '../stylesheets/application';
こんな感じで、JSがCSSの内容を展開してくれます。
自分で作った小さなコンポーネントならコレでも良いのですが、bootstrapなど大きなライブラリを含む場合には css ファイルは分けて読み込みたいという場合のほうが多いと思います。それを実現するためのプラグインとして extract-text-webpack-plugin というものが用意されています。
これにより CSS ファイルを個別に生成して別ファイルとして読み込むことが可能です。
Railsからwebpackの作成したJS / CSSを読み込む
webpack-manifest-plugin が生成する manifest.json をRailsから読み込み、digest値付きの JS / CSS を読み込みます。
まずは initializer で manifest.json を読みだし、
# config/initializers/assets.rb Rails.application.config.assets.webpack_manifest = if File.exist?(Rails.root.join('public', 'assets', 'webpack-manifest.json')) JSON.parse(File.read(Rails.root.join('public', 'assets', 'webpack-manifest.json'))) end
webpack が生成したファイルへのパスを返すヘルパーを定義してあげます。
# app/helpers/application_helper.rb def webpack_asset_path(path) # development 環境で参照先を変えているのは後述する Hot Module Replacement 機能を利用するためです。 # Railsではなく、webpack_dev_server というwebpackが提供する開発用のサーバを参照しにいきます。 if Rails.env.development? return "http://localhost:3500/assets/#{path}" end host = Rails.application.config.action_controller.asset_host manifest = Rails.application.config.assets.webpack_manifest path = manifest[path] if manifest && manifest[path].present? "#{host}/assets/#{path}" end
あとはViewからこれらのファイルを読みだしてあげればOKです。
// app/views/layouts/application.html.slim = stylesheet_link_tag webpack_asset_path('main.css') = javascript_include_tag webpack_asset_path('main.js')
この辺りはWantedlyの高松さんのスライドを多分に参考にさせていただきました! speakerdeck.com
webpackならではの機能を使って開発を効率化する
別にwebpackでなくても実現可能なものもありますが、webpackならシンプルにプラグインを追加するだけで開発を効率化する様々な機能が使えるようになります。 今回は Hot Module Replacement(HMR) という機能を紹介します。説明するよりも見た方が早いので、以下をご覧ください。
このようにリロード無しで、ブラウザにレンダーされている要素が変わっていっています。「ソースを更新してF5を押して…あ、まだJSがビルドされてなかった!!もう一回F5や!あーダメだ再ビルド!」みたいな不毛なことをせずに済みますし、ブラウザ上で操作をした後でも対象のコンポーネントがリロード無しに変更されていくので、かなりスムーズに開発を進めていくことができます。
また HMR が何らかの理由で出来ない場合には、 自動でブラウザをリロードしてくれます。
この機能を使用するためには、ローカル開発環境で Rails を立ち上げる以外に webpack-dev-serverを立ち上げる必要があります(サンプルコードでは npm run dev
で起動させることが出来ます)。当然毎回 Rails と webpack-dev-server を手動で立ち上げるのは面倒なので、 foreman などを使用して一つのコマンドで立ち上げるようにするのがよいでしょう。
まとめ
webpack を使用すると、Sprockets の置き換えも意外とシンプルに実現できそうだということが見えてきました。フロントエンドの開発効率を上げる強力な機能が使えるようになるので、結構アリなんじゃないかと考えています。 現在は新規のプロダクトで試験的に使用している段階ですが、 上手く行くようであれば今後は徐々に crowdworks.jp の方にも広めていきたいな…!!
クラウドソーシングのクラウドワークスでは、フロントエンドをモダンに開発していきたいエンジニアを募集しています!
*1:Sprockets4でES6で書けるようになるらしい
*2:今調べてみるとwebpackの標準機能で実現出来る内容かも https://webpack.github.io/docs/long-term-caching.html