Rails
webpack
webpacker
parcel

Rails5.1でWebpackerの代わりにparcelを使う

More than 1 year has passed since last update.

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 というファイルが生成されますが、デフォルトだとどこからも参照されていないので、確認用に簡単なページを追加します。


app/controllers/sample_controller.rb

class SampleController < ApplicationController

def index; end
end


app/views/layouts/application.html.erb

<!DOCTYPE html>

<html>
<head>
<title>ParcelApp</title>
<%= csrf_meta_tags %>
<%= javascript_pack_tag 'application' %>
</head>
<body>
<%= yield %>
</body>
</html>


app/views/sample/index.html.erb

this is sample



routes.rb

root to: 'sample#index'



app/javascript/packs/application.js

import './hello_react';


実験的に動かして、以下の画面が出れば実験対象となるアプリケーションの準備完了です。

image.png


parcel化する

では、ここから実際にparcelを利用した形に置き換えていきます。


package.jsonから不要なモジュールを削除

まずは、不要となるモジュール(@rails/webpackerなど)をpackage.jsonから削除してキレイな形にします。React関係のものはそのまま使わせてもらいましょう。


package.json

{

"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

image.png

もう通ってしまいました。

ほぼ無設定でいきなりビルドが通ってしまうあたり、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

これでもう一度ビルドしてみると、それっぽい出力となります。

image.png


parcel-manifest.json

{"application.js":"application-fb0fd6b9e53deb12504c514378e4d058a4d9d7aeff03f792384d5f0744f00e09.js"}



fingerプリント付きファイルをviewでロードする

viewからは元のファイル名を指定して、実際にはfingerprint付きファイルをロードするような仕組みが必要となります。

Webpackerがやっているのと同じように、manifestファイルを介してロードするようなヘルパーを作ります。

manifestファイルのキャッシュはどうするの?など色々と突っ込みどころはありますが、動かすことだけを目指すのでそのあたりは省略します。

public/parcel配下にビルド結果を配備することを前提とします。


app/helpers/application_helper.rb

module ApplicationHelper

def javascript_parcel_tag(name, **options)
javascript_include_tag('/parcel/' + Manifest.instance.lookup(name), **options)
end
end


app/helpers/manifest.rb

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


利用するヘルパーを切り替えます


app/views/layouts/application.html.erb

~省略~

<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

image.png


動作を確認してみる

rails serverを起動します。

image.png

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自分で作りたい場合はぜひやってみてください。楽しいですよ!