先日、Rails4→5.1、5.1→5.2.0.rc2にアプリを互換性を保ってアップデートしてみましたがそろそろちゃんと新しいバージョンの恩恵を受けられるようにwebpackerを導入してみたいと思います。
スポンサーリンク
目次
自分のアプリの現状を把握する
まず、現状のGemfileで関係ありそうなところを把握してみたいと思います:
gem 'less-rails'
gem 'sass-rails'
gem 'coffee-rails'
gem 'jquery-rails'
gem "twitter-bootstrap-rails", '~> 2.x'
gem 'therubyracer'
gem 'libv8'
gem 'uglifier'
2012年にRails3系で初めてプロジェクト作成しただけあってなんとも歴史を感じさせる古めかしいカオスなgemが並んでいます。
LESSとSASSどちらも利用しちゃってるのはBootstrap2系のgemにtwitter-bootstrap-railsを選択したあたりからLESS依存になって、therubyracerとlibv8も一緒について来たっぽいです。webpacker移行と一緒に何とか外しておきたいところです。
jquery-railsはjquery-ujsを使ってるので利用してますがRails5.1でrails-ujsとなりjQuery依存が解消されるのでこれも廃止しておきたい。
assets内はbootstrapのためのみ、bootstrap.css.lessとbootstrap.js.coffeeファイルが有りそれ程コード量は無いので移行時に書き換えて廃止しとけば良さそうです。他にはjQuery 1.8.6とかjQuery UI等のJSライブラリコードが直接置かれているのでnode_modules配下に管理しておいて利用出来る様にしたいです。
Webpackerとは
Webpackerは名前から想像できるようにWebpackをRailsで利用出来る様にするgemで、Rails 5.1より公式でrails new myapp --webpack
という様にセットアップ可能となっています。
今まで複数のJSファイルをまとめたり最適化したりするのはSprockets gemでやっていました(実際のアプリではSprocketsではrequireは使って無くて全部javascript_script_tagでassetsで読み込んでいました)が、RubyからJSを扱う関係上therubyracerやlibv8などNativeビルドが必要なV8エンジンを利用するgemが必要になってこれが安定して無かった。JSを扱うならgemではなくNode.jsでバージョン管理・最適化を行うのは理に叶っていると思います。
また、Babelを利用しているのでブラウザの互換性を気にすること無くES2015文法を利用してJSコードを記述しておくことが出来ます。
Webpackerには他にもCSSや画像もリソース管理可能なようになっているのでやろうと思えばSprocketsから完全移行は可能なようです。
Webpackerでなにを移行するか
とはいえ、良い感じに枯れたSprockertsを捨てるつもりは無く、RubyでなるべくJSを取り扱う事を廃止するようにしたいと思います。以下の対応を行います。
- Webpackerのインストール
- LESSの利用を廃止してSass(Scss)に全部移行する
- CoffeeScriptの利用を廃止する
- assetsにあるJSコードのみ、webpacker配下に移行する
- jQuery・jQuery UIのJSライブラリはassetsから削除してnode_modulesでインストールして利用する
- jquery-railsを廃止してrails-ujsに移行する
移行後の関連するGemfile部分は以下の様になります:
gem 'sass-rails'
gem 'font-awesome-rails'
gem 'bootstrap-sass'
gem 'uglifier'
それでは移行作業を進めていきます。
移行作業
Webpackerのインストール
WebpackerはNode.jsとパッケージ管理はYarnを使います(yarnの記事)。今回はCentOS6上で環境を整えるので先にインストールしておきます。因みにCentOS6デフォルトのnodejsは6.6とちょっと古くて遅そうなので最新版のv9.9.0が入るようにgcc/makeも一緒にインストールしてます:
$ sudo yum install gcc-c++ make
$ sudo curl -sL https://rpm.nodesource.com/setup_9.x | sudo bash -
$ sudo yum install nodejs
$ node -v
v9.9.0
$ npm -v
5.6.0
$ sudo npm i -g yarn
次にwebpacker gemをGemfileに追加します:
gem 'webpacker', '~> 3.4'
あとはgemのインストールとwebpackerインストールタスクが用意されているのでそれを実行します:
$ bundle install
$ rails webpacker:install
設定ファイルなどで競合があればダイアログ出ますが今回は出ませんでした。ここで生成されたconfig/webpacker.yml
のデフォルト設定をみてみます:
default: &default
source_path: app/javascript
source_entry_path: packs
public_output_path: packs
cache_path: tmp/cache/webpacker
source_path
のapp/javascript
フォルダにwebpackerで管理したいJS/CSS/Imageファイルパス、source_entry_path
のpacks
はJSファイルパスでapp/javascript/packs
フォルダに置きます。public_output_path
はプリコンパイルで生成したファイルを出力するパスでpublic/packs
に置かれます。cache_path
は恐らくは更新したかの差分監視などに利用するキャッシュ置き場だと思われます。
webpackerはdevelopment環境でもpublic_outputにJSをトランスパイルしてデプロイしないといけないので、railsサーバを起動する前にbin/webpack
を実行するか、bin/webpack-dev-server
を起動しておいてJSコード更新監視をしておきます。
まだWebpackerにはなにも移行してないので動くはずなんですが・・・以下の様なエラーが出て上手く行きませんでした:
何故かnode_modulesからsassでパースしようとして失敗しているようです。原因を辿ると、Rails5.1アップデート時にconfig/initializers/assets.rb
が作成されて、
Rails.application.config.assets.paths << Rails.root.join('node_modules')
とnode_modulesがassetsプリコンパイル対象となっているようで、これがあるとassets以下でerbやlessをプリロードするときにnode-sassが動作してしまうようです。node_modulesはSprocketsでは利用しない様にしたいので外しておきます:
-Rails.application.config.assets.paths << Rails.root.join('node_modules')
+# Rails.application.config.assets.paths << Rails.root.join('node_modules')
これで他は影響なしでWebpackerのインストールができました。実際のコミットはこれ。
LESSの利用を廃止してSass(Scss)に全部移行する
LESSはJSベースで開発されていることもあってJSとは親和性高く以前はよかったのかもしれませんがlibv8を強要するtherubyracerに依存してしまうとか今はあまりいいことがありませんので廃止してしまいます。
今回のこの廃止によって一番対応量が多かったのはBootstrapにLESS依存のtwitter-bootstrap-rails
gemをしていた部分で、今回はこちらをまずSass版のBootstrapであるbootstrap-sass
にgemを置き換えてからtwitter-bootstrap-rails依存のimportコードを変更していきました。Gemfileは以下のように修正しています:
-gem 'less-rails'
gem 'sass-rails'
-gem "twitter-bootstrap-rails", '~> 2.x'
+gem "font-awesome-rails"
+gem "bootstrap-sass", '2.3.2'
gem 'webpacker', '~> 3.4'
-gem 'therubyracer'
-gem 'libv8'
+gem 'uglifier'
font-awesome-rails
はtwitter-bootstrap-rails利用時に使っていましたがbootstrap-sassには含まれてないので追加してあります。bundle install
後、bootstrap読み込み部分をlessファイルからscssファイル(application.css.scss)に移してbootstrap-sass向けのimportに切り替えました:
-@import "twitter/bootstrap/bootstrap";
+@import "bootstrap";
-@import "fontawesome/font-awesome";
+@import "font-awesome";
次にモーダル表示にBootstrapのJSを使っているのでこちらはWebpacker側にもライブラリを追加します:
$ yarn add bootstrap-sass@2.3.2
あとは、twitter-bootstrap-rails時にはhtml.erbに直接スクリプトを読み込んでおいた部分
- <%= javascript_include_tag "twitter/bootstrap" %>
を、app/javascript/packs/application.js
からrequireして利用することにします:
+require("bootstrap-sass")
対応自体はこれでOKですが、これによって動作確認するとbootstrap-sass: Undefined variable: “$baseLineHeight”
というエラーが出て表示出来なくなってしましました。原因を探ってみるとRails.application.config.assets.precompile += %w( *.js *.css )
という設定ではどうもbootstrap-sassに含まれる_accordion.css.scss
のようなアンダーバーが頭につくassetsがプリコンパイル対象から外れてしまっているようで読み込めなくなってしまっていたようです。こちらも対象に含めるように以下に設定をconfig/initializers/assets.rb
に修正することで起動するようになりました:
Rails.application.config.assets.precompile += [/^[-_a-zA-Z0-9]*\..*/]
CoffeeScriptの利用を廃止する
こちらはほぼ.js.coffee
を.js
にリネームして中身のCoffeeScriptコードを書き換えるだけだったので特に言及することはないです。修正コミットはこれ。
assetsにあるJSコードのみ、webpacker配下に移行する
漸くassetsからwebpackerにJSを引っ越します。この作業で検討したことは3つで、packsソースのパスを示すヘルパはどうなるのか
・.js.erbはどうするのか
・パスがおかしく読めなくなったJSがある
。それぞれ見ていきます。
まず、assetsからpacksにファイルを移動した場合当然読み込み先が違うようになるのですが、こちらはヘルパが用意されています:
asset_path
はasset_pack_path
stylesheet_link_tag
はstylesheet_pack_tag
javascript_include_tag
はjavascript_pack_tag
次に、webpackerではerbがどうなるのかなんですが、セットアップ方法が公式で用意されているのでコマンドを打ちます:
$ rails webpacker:install:erb
これでほぼ終わりですが、自分の場合、内部でassets_pathを使っていてMethod Missingが出ました。これはActionViewのヘルパをデフォルトでは読み込まないからだけのようで、erbファイルの最初でincludeすることで動作出来ました:
<% include ActionView::Helpers %>
最後に以下のようなものをヘルパを変えるだけではちょっとおかしい名前でファイルが見つからないと言われました:
<%= javascript_include_tag "jquery.ui.touch-punch.min" %>
これに関しては、拡張子以前の.
は許されないようですので、ファイル名側をハイフンに変えていてヘルパで読み込みました:
<%= javascript_pack_tag "jquery-ui-touch-punch-min" %>
jQuery・jQuery UIのJSライブラリはassetsから削除してnode_modulesでインストールして利用する
今まではJSライブラリはassets配下にハードデプロイしてましたがwebpackerに移行するタイミングでNode.js側で管理して利用したいと思います。特にjQueryはpacks配下のJSコードならrequireしなくても必ず利用できるようにWebpack Provide Plugin
なるもので登録しておきます。
JSライブラリはyarnで登録します:
$ yarn add jquery jquery-ui-bundle jquery-ui-touch-punch
jQueryは全JSで利用できるようにデフォルトで呼び出せるようにconfig/webpack/environment.js
に追記します:
const webpack = require('webpack')
environment.plugins.prepend('Provide',
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
"window.jQuery": 'jquery',
jquery: 'jquery'
})
)
jQuery UIはjquery-ui
もありますが、requireしても各モジュールを読み込まないので全部読み込むjquery-ui-bundle
を利用します。あとは必要なJSにrequireします:
require("jquery-ui-bundle")
require("jquery-ui-touch-punch")
またこの対応に伴ってjQueryが1.8系から一気に3系になってdeprecatedだった.size()
が削除されたので.length
に修正するように既存JSコードを修正しました。他は特に影響は無かったです。
修正コミットはこれ(他の修正も入ってるので見にくい)。
jquery-railsを廃止してrails-ujsに移行する
最後にjquery-ujs.jsをやっつけます。jquery-ujsはもともとremote: true
を実現するためのRails備え付けのライブラリですがjQuery依存が外れたrails-ujsになっているのでこちらに変えます。webpackから使うにはまず、
$ yarn add rails-ujs
でJSライブラリをインストールして、JSコードでセットアップを呼び出したrails-ujs.js
を作成して
import Rails from 'rails-ujs'
Rails.start()
requireして使います。
require("./rails-ujs.js")
まとめ
いかがだったでしょうか。Node.jsにJSコードを移行するのは大いに賛成なのですが、トランスパイル後のデバッグどうするのかとかSprocketsいつか廃止してしまうんだろうかとかまだ心配な部分はいくつもあります。
個人的にはJSさえ移行出来ればそれで良いと思っていて、世のフロントエンド完全独立志向な方からするとまだ切り離されてないじゃんか!って辺りは余り気になりません。それも含めて今後どうなっていくのかも見守りつつ徐々にWebpackerに慣れていきたいと思います。
参考リンク
- node.js 8.x をyumでインストールする(centos7.x) - Qiita
- rails/webpacker: Use Webpack to manage app-like JavaScript modules in Rails
- webpackerの導入・設定メモ - Qiita
- Sprockets再考 モダンなJSのエコシステムとRailsのより良い関係を探す - Qiita
- Rails アプリに Webpacker を導入する
- Unable to access Rails view helpers · Issue #33 · usabilityhub/rails-erb-loader
- Rails5.1.3でsassとjsの管理をSprocketsからWebpackerに移行した - Qiita
- rails-ujs選手、npm移籍へ - komagataのブログ
- ruby on rails - bootstrap-sass: Undefined variable: "$baseLineHeight" - Stack Overflow
- .size() | jQuery API Documentation