Middleman + sprockets でも React を production に無理矢理する。

たぶん、誰も必要としない技術…
そして、空いていたからと Advent Calendar に後から参加するという暴挙…

## トラブル編

オワコン(死語)なので、仕方が無い。

### sprockets はまだオワコンでは無い！(オレの中では)

Middleman は v4 から sprockets を切り離しました。代わりに外部パイプラインを使って、JavaScript のエコシステムを使うのがナウい(死語)と言うことらしいです。

が、sprockets は滅んではいません。[middleman-sprockets](https://github.com/middleman/middleman-sprockets) を使えば今でも sprockets は十分使えます。そんな sprockets 使い達への文章です。**ぶっちゃけ、需要は無いと思っています。**

sprockets の強い味方が [Rails Asstes](https://rails-assets.org/) です。Rails Assets は [Bower](https://bower.io/) にあるパッケージを機械的に問答無用で取ってきているので、Bower にあるものはほぼ全て利用できます(たぶん)。あの JavaScript ライブラリが無い！なんてことは(ほぼ)ありません。つまりはですね、群雄割拠の JavaScript エコシステムの世界に入らなくてもパッケージは手に入りますし、昔ながらの sprockets で一つにまとめるのも簡単にできると言うことです。欠点は CommonJS 表記ができない[^1]、Sass が遅い、時代遅れを感じると言った所だけです。

[^1]: [sprockets-commonjs](https://github.com/maccman/sprockets-commonjs) というのを使えばできないことは無いんだけど、最近保守されてないっぽい。

### React のモードが変更できない？

さて、これで万事解決と思いきや、一つ問題が出てきました。[rails-assets-react](https://rails-assets.org/#/components/react) を使って、React も万全だーと思いきや、コンソールログに開発(development)モードを使ってますよ、本番環境は製品(production)モードを使いましょうねメッセージが・・・なん、だ、と？

ちょっとここで解説。React には二つのモードがあります。開発モードと製品モードです。開発モードではエラー時に詳細なログが出るのですが、重めです。製品モードでは簡易ログしか出ない代わりに軽めです。開発者はこの二つモードを切り替えて、開発時は開発モードで詳しく調べながら、いざ、製品としてリリースするときは製品モードでなるべく軽快になるようにします。これを切り替える方法は二つです。

* アーカイブや Bower で入手している場合は、react.js(開発モード) と react.min.js(製品モード) に分かれているため、読み込むファイルを変える。
* webpack 等で自分で React を一つにまとめる場合は、NODE_ENV 環境変数で切り替わるようにする(webpack.config.js 等でそうなるように設定する)。

では、Rails Assets で入手した場合はどうなのかというと…なんと、製品モード版、つまり、react.min.js がパッケージに含まれていません。どうあがいても開発モード版の react.js しか読み込めないのです。なんてこった、詰んだ。外部パイプラインで webpack 使えって事かよ。[^2]

[^2]: なお、Ralis 専用の [react-rails](https://github.com/reactjs/react-rails) は Rails の設定で切り替えられるようになっていますが、使えませんでした。他に Middleman 用の [middleman-react](https://github.com/plasticine/middleman-react) と言うのもあるのですが、これまた保守されておらず…。

完

## 解決編

まだだ！まだ終わらんよ！

### 圧縮前に変更せよ！

Ruby に不可能はありません。いや、一杯ありそうな気がしますけど、何でもできるのが Ruby のはずです。ということで、config.rb の `configure :build ...` 付近をちょこちょこっとこのようにいじります。

```config.rb
require 'uglifier'
# Custom Ugilifer
# "development" => "production"
class CustomUglifier < Uglifier
  def compress(content)
    super(content.gsub('"development"', '"production"'))
  end
end

# Build-specific configuration
configure :build do
  # Minify CSS on build
  activate :minify_css

  # Minify Javascript on build
  activate :minify_javascript,
           inline: true,
           compressor: proc {
             CustomUglifier.new
           }
end
```

細かいところはお好みで変えてください。さて、Middleman では JavaScript の圧縮に uglifier を使っています。具体的には、`Ugilifier` オブジェクトを生成して、`compress` メソッド[^3]を呼び出しています。なら、この呼び出しの前で`"development"`を`"production"`に変えちゃえば良いんだ！という発想です。

[^3]: `Uilifier#compress` は `Uilifier#compile` のエイリアスです。

react.js ファイルでは、(たぶん) webpack により `process.env.NODE_ENV` が `"development"` に置き換わっています。ソース側では、`process.env.NODE_ENV !== 'production'` というコードになっているのですが、react.js になると `"development" !== 'production'` というコードになっているという仕組みです。そう、全部これを置き換えちゃえば、production、つまり製品モードになるということです。

一応、手元ではこれでうまくいきました。その後の Uglifier の元々のメソッドにより、綺麗さっぱり開発モードのコードは消えていました。`configure :build ...` の中だけなので、build 時しか反映されないというのもなかなか良いアイデアだと思っています。

### これって大丈夫？

わかりません。React のみならず、全てのコードについて同じように書き換えてしまいます。でも、同じ仕組みを使っているところは、逆にうまくいきます。

### 他の方法は？

Rails Assets ではなく Bower で取ってくると言う方法があります。sprockets が bower_components 配下も見に行くように設定は必要ですが、概ねうまくいきます。

正当派は、外部パイプラインで webpack 等を使う方が良いのでしょう。

## 所感

sprockets はオワコンと言われる昨今。さりとて、群雄割拠の JavaScript エコシステムにあまり近づきたくないと勝手に思っています。トップレベルに設定ファイルがどんどん増えていくのにうんざりです。依存パッケージも管理が複数に分かれるのも避けたいと思っています。となると、Ruby だけで完結できる sprockets はまだまだ頑張れるのでは無いかと勝手に思っています。

Ruby がオワコンだろって？何言っているんだ、まだ始まってすらいないよ！