さかなソフトブログ

プログラミングやソフトウェア開発に関する情報

Web

Rails4からあるassets配下の既存のJSコードをRails5.1より公式サポートしたwebpackerを利用してnode.js配下に移行する

更新日:

先日、Rails4→5.15.1→5.2.0.rc2にアプリを互換性を保ってアップデートしてみましたがそろそろちゃんと新しいバージョンの恩恵を受けられるようにwebpackerを導入してみたいと思います。

スポンサーリンク

正方形336


自分のアプリの現状を把握する

まず、現状の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_pathapp/javascriptフォルダにwebpackerで管理したいJS/CSS/Imageファイルパス、source_entry_pathpacksは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_pathasset_pack_path
  • stylesheet_link_tagstylesheet_pack_tag
  • javascript_include_tagjavascript_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に慣れていきたいと思います。

参考リンク

正方形336

正方形336

-Web
-, , , ,

Copyright© さかなソフトブログ , 2019 All Rights Reserved.