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

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

```ruby:app/controllers/sample_controller.rb
class SampleController < ApplicationController
  def index; end
end
```

```erb: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>
```

```erb:app/views/sample/index.html.erb
this is sample
```

```ruby:routes.rb
root to: 'sample#index'
```

```js:app/javascript/packs/application.js
import './hello_react';
```

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

![image.png](https://qiita-image-store.s3.amazonaws.com/0/55375/3136ba5d-b230-eb7b-88fc-d916cd8c93e5.png)


parcel化する
============

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

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

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

```json: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](https://qiita-image-store.s3.amazonaws.com/0/55375/4e830d3a-f98f-b812-e59d-7d51a8342cbe.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](https://qiita-image-store.s3.amazonaws.com/0/55375/81a0da5e-48ec-1cba-f450-7f0564304013.png)

```json:parcel-manifest.json
{"application.js":"application-fb0fd6b9e53deb12504c514378e4d058a4d9d7aeff03f792384d5f0744f00e09.js"}
```

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

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

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

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

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

```ruby:app/helpers/application_helper.rb
module ApplicationHelper
  def javascript_parcel_tag(name, **options)
    javascript_include_tag('/parcel/' + Manifest.instance.lookup(name), **options)
  end
end
```

```ruby: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
```

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

```erb: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](https://qiita-image-store.s3.amazonaws.com/0/55375/20f1af17-d2fd-7328-8a1b-f1310652de4b.png)

動作を確認してみる
--------------

rails serverを起動します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/55375/68fa0c7f-cabe-6ee1-1a05-3a048de15ab1.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は何ができるのか](https://qiita.com/mugi_uno/items/0a383ccc5fb9b433a8d4)

----

以上で、簡易的ですがWebpackerがやっていたビルドを置き換えることができました。

ちなみに実際には

- Webpacker gemの削除
- Webpackerが追加した設定の削除

も必要ですが、このエントリーでは省略します。

parcelを使うことでフロントエンドに関する設定ファイルがかなりコンパクトになるので、わけのわからない部分が減るかな〜という印象を受けました。

すぐに`parcel_rails`みたいなgemが登場しそうな雰囲気を感じますが、待ちきれないor自分で作りたい場合はぜひやってみてください。楽しいですよ！