Railsでは5.1からWebpackerが利用可能となっており、簡単にwebpackとの統合が行えるようになっています。一方、フロントエンドでのバンドルツールとしては、ここ最近parcelが注目を浴びています。
それでは、Railsでparcelを利用する場合にはどのような作業が必要となるのでしょうか?
parcelは実戦投入には時期尚早感はありますが、Webpackerが導入された状態からparcelによるビルドに置き換えることができるのかを勉強がてら試してみました。
何を置き換えるか?
Webpacker(webpack)がやってくれることは多数ありますが、以下をparcelを利用した状態で実現してみることにしました。
- ES6/Reactのビルド
- fingerprintの付いたファイルの出力
- manifestファイルを元にしたfingerprint付きのファイルの読み込み
なお、Webpackerにはassets:precompile
時にwebpackビルドも一緒にやってくれる仕組みがあるのですが、そのあたりは今回は諦めます。
下準備
置き換える対象のアプリケーション
シンプルな構成での実験としたいので、--webpack=react
オプションで作成できるRails&Webpacker&Reactのテンプレートアプリケーションを置き換え対象にします。
つくる
まずは対象となるWebpackerが使えるRailsアプリケーションを作ります
rails new parcel_app --webpack=react
hello_react.jsx
というファイルが生成されますが、デフォルトだとどこからも参照されていないので、確認用に簡単なページを追加します。
class SampleController < ApplicationController
def index; end
end
<!DOCTYPE html>
<html>
<head>
<title>ParcelApp</title>
<%= csrf_meta_tags %>
<%= javascript_pack_tag 'application' %>
</head>
<body>
<%= yield %>
</body>
</html>
this is sample
root to: 'sample#index'
import './hello_react';
実験的に動かして、以下の画面が出れば実験対象となるアプリケーションの準備完了です。
parcel化する
では、ここから実際にparcelを利用した形に置き換えていきます。
package.jsonから不要なモジュールを削除
まずは、不要となるモジュール(@rails/webpacker
など)をpackage.jsonから削除してキレイな形にします。React関係のものはそのまま使わせてもらいましょう。
{
"name": "parcel_app",
"private": true,
"dependencies": {
"babel-preset-react": "^6.24.1",
"prop-types": "^15.6.0",
"react": "^16.2.0",
"react-dom": "^16.2.0"
}
}
yarn install --force
Webpacker gem自体は残っていますが、そのあたりは一旦は無視します。
parcelを追加
なにはともあれparcelです。
ついでに必要となるbabelのpresetやpluginも追加しましょう。
yarn add parcel-bundler
yarn add babel-preset-env babel-plugin-syntax-dynamic-import babel-plugin-transform-object-rest-spread babel-plugin-transform-class-properties
試しに application.js
をビルドしてみましょう。
yarn run parcel build ./app/javascript/packs/application.js --no-minify
もう通ってしまいました。
ほぼ無設定でいきなりビルドが通ってしまうあたり、parcelのスゴさを感じますね。
fingerprintの付いたファイルを出力する
Railsではproduction用にアセットをビルドすると、fingerprintが付いたファイルが出力されます。
application-4af55faf4d5ce3c921b1.js
みたいなファイルですね。
Webpackerを利用している場合は、webpack-manifest-pluginと組み合わせて実現されており、Webpackerを利用しない場合にはSprocketsがそのあたりをいい感じにしてくれています。
parcelでもなんとかする必要がありますが、デフォルトではサポートされていません。
どうするか?
作った
無いなら自分で作りましょうということで、プラグインを作りました。
https://github.com/mugi-uno/parcel-plugin-bundle-manifest
これを突っ込みます。
yarn add parcel-plugin-bundle-manifest
これでもう一度ビルドしてみると、それっぽい出力となります。
{"application.js":"application-fb0fd6b9e53deb12504c514378e4d058a4d9d7aeff03f792384d5f0744f00e09.js"}
fingerプリント付きファイルをviewでロードする
viewからは元のファイル名を指定して、実際にはfingerprint付きファイルをロードするような仕組みが必要となります。
Webpackerがやっているのと同じように、manifestファイルを介してロードするようなヘルパーを作ります。
manifestファイルのキャッシュはどうするの?など色々と突っ込みどころはありますが、動かすことだけを目指すのでそのあたりは省略します。
public/parcel
配下にビルド結果を配備することを前提とします。
module ApplicationHelper
def javascript_parcel_tag(name, **options)
javascript_include_tag('/parcel/' + Manifest.instance.lookup(name), **options)
end
end
require 'singleton'
class Manifest
include Singleton
def lookup(name)
json[name.to_s]
end
private
def json
# load & cache
@data ||= JSON.parse(Rails.root.join('public', 'parcel', 'parcel-manifest.json').read)
end
end
利用するヘルパーを切り替えます
~省略~
<head>
<title>ParcelApp</title>
<%= csrf_meta_tags %>
<%= javascript_parcel_tag 'application.js' %>
</head>
~省略~
最後に、public/parcel
配下にビルド結果を出力します。
yarn run parcel build ./app/javascript/packs/application.js -d public/parcel
動作を確認してみる
rails serverを起動します。
Hello React!
の表示から、jsxが正常にビルドできていることがわかります。そして、期待通りfingerprint付きのjsファイルがロードされているようです。
変更時に自動ビルドする
開発時には変更を検知しての自動ビルドをしたくなります。
そのあたりも特に工夫は不要で、
yarn run parcel watch ./app/javascript/packs/application.js -d public/parcel
だけでビルド自体は実現できます。
ただ、この場合上記に記述したヘルパーから利用するManifest.rb
のコードでは、ロードしたjsonファイルをキャッシュするようにしているので、そこだけ書き換えないとリロードしても反映されないので注意が必要です。
(実際にはproduction時のみキャッシュされるようにしたほうが良いですね。)
複数エントリーポイントへの対応について
今回はapplication.js
のみを対象にしていますが、実際にはエントリーポイントが複数存在するケースも考えられるかと思います。
その場合は、ビルド用のスクリプトを別途作成する必要が出てきます。
あるいは、CodeSplittingを利用して動的インポートとすることでも対応可能かもしれません。
参考: Qiita - parcelのCode Splittingは何ができるのか
以上で、簡易的ですがWebpackerがやっていたビルドを置き換えることができました。
ちなみに実際には
- Webpacker gemの削除
- Webpackerが追加した設定の削除
も必要ですが、このエントリーでは省略します。
parcelを使うことでフロントエンドに関する設定ファイルがかなりコンパクトになるので、わけのわからない部分が減るかな〜という印象を受けました。
すぐにparcel_rails
みたいなgemが登場しそうな雰囲気を感じますが、待ちきれないor自分で作りたい場合はぜひやってみてください。楽しいですよ!