顧客が本当に求めていたsprockets、sprockets-commonerの紹介

## はじめに

みなさんにdisられて久しいsprockets氏ですが、メリットはそのままこれまでの問題を解決してくれる[sprockets-commoner](https://github.com/Shopify/sprockets-commoner)という素晴らしいgemを見つけたので紹介します。

## sprockets-commonerとは

[sprockets-commoner](https://github.com/Shopify/sprockets-commoner)とは、Railsコミッターも在籍する[Shopify](https://github.com/Shopify)で作られたsprocketsの拡張gemです。

このgemの機能の中でも特に嬉しいのは以下の2つの機能です。

### 1. node.js/ESnextでフロントエンドを書けるようになる

sprockets-commonerを入れると、sprockets管理下のJSファイルをbabelでトランスパイルしてくれるようになります。
そのため、node.js/ESnextでフロントエンドを書けるようになります。

一応、sprockets次バージョンの4でもESnextは書けるようになりますが、依存している[babel-transpiler gem](https://github.com/babel/ruby-babel-transpiler)の都合で[babel5までしか使えない問題がある](https://github.com/babel/ruby-babel-transpiler/pull/282)のに対し、sprockets-commonerならnpmパッケージの[babel-core](https://github.com/babel/babel/tree/master/packages/babel-core)をそのまま使うので、最新のbabelを利用できます。

また、node.jsなので当然ですが、ライブラリの導入にnpm/yarnを使うことが出来るようになります。
そのため、gemでラップされたJSを使う必要はありません。

### 2. ファイルごとにスコープが区切られる

sprocketsはプリコンパイルによって管理下のファイルを全コードがグローバルな一つのファイルにまとめるので、各ファイル・コードの依存関係がわかりにくくなったり、変更を加えた際に思わぬところに影響を及ぼすなど様々な問題を引き起こしていました。
sprockets-commonerを入れるとファイルごとにスコープが区切られるようになるので、このグローバル問題を解決してくれます。（[生成されるコード例](https://github.com/Shopify/sprockets-commoner#bundling)）

また、スコープが区切られる対象は「自分が書いたコード」及び「CommonJS/ES modulesで読み込まれたライブラリ」の2つで、「これまでのsprocketsの構文（`//= require jquery`ってやつ）」で読み込まれたライブラリはこれまで通りグローバルになります。
これにより、これまでの動作はなるべく壊さずnode.jsの世界に移行できます。


以上2つが特に重要な機能になりますが、その他にも以下のような既存コードに気を使った便利機能もあります（使わないで済むならその方がいいですが、現実問題あると嬉しい機能）。

### 3. 管理対象ファイルが選択可能

[2](#2-ファイルごとにスコープが区切られる)で説明した通り、sprockets-commonerを入れるとファイルごとにスコープが区切られるので、既存コードにグローバルであることを期待したコードがある場合はそこが死にます。
ですが、sprockets-commonerは管理対象を自由に設定できるので、sprocketsのディレクトリ構成（`app/assets/javascripts/`）を崩すことなく特定のファイルのみをnode.js化することができます。

```rb:config/initializers/sprockets_commoner.rb
Rails.application.config.assets.configure do |env|
  Sprockets::Commoner::Processor.configure(env,
    # 管理対象に含めるファイル。デフォルトは`[env.root]`
    include: ['app/assets/javascripts/subdirectory'],

    # 除外するファイル。デフォルトは['vendor/bundle'].
    exclude: ['vendor/bundle', /ignored/],

    # CommonJSの解決はするけど、babelには通さないファイル。デフォルトは[/node_modules/]
    babel_exclude: [/node_modules/]
  )
end
```

https://github.com/Shopify/sprockets-commoner#fine-tuned-selection-of-files-to-process

### 4. ESnextとCoffeeScriptでモジュールをやりとりできる機能がある

これは少々強引な機能ではあるのですが、CoffeeScriptからESnextへの移行を順次推し進めるために、本来ESnext/CoffeeScript間ではモジュールのやりとりができないところを、`window`にオブジェクトを詰め込むことによってモジュールの受け渡しを実現する機能があります。
あくまでESnextへの移行時のための機能にはなりますが、着実にESnext化していく上で非常に便利な機能だと思います。

#### CoffeeScriptからESnextに渡す場合

https://github.com/Shopify/sprockets-commoner#importing-coffeescript-files

```coffeescript:file.coffee
# windowにImportantClassを詰め込む
class window.ImportantClass
```

```js:main.js
// file.coffee読み込み 
var klass = require('./file');

// CoffeeScriptの中で定義したImportantClassをESnext内で使える
new klass();
```

#### ESnextからCoffeeScriptに渡す場合

https://github.com/Shopify/sprockets-commoner#expose

```js
// window.MyClassにexportしたものが詰め込まれる
'expose window.MyClass';

export default class MyClass {}
```

```js
// window.Constantsにexportしたものが詰め込まれる
'expose window.Constants';

export const A = 1;
export const B = 2;
export const C = 3;

// CoffeeScript側で`window.Constants`と呼ぶことで`{A: 1, B: 2, C: 3}`を取得できる
```

この2つの機能のおかげで、node.js環境に移行する際に既存コードを一気に書き換えたり、もしくはsprockets管理外にディレクトリを切ってそこで書き換えていって最後にsprockets管理下に戻すなど、そういう面倒なことをする必要がありません。sprocketsのディレクトリ構成はそのままで順次移行していくことができます。
また、ビルドはsprockets-commonerがいい感じにやってくれるので、browserifyやwebpackなどのフロントエンドビルドツールの使い方を学ぶ必要は（ほぼ）ありません。（`.babelrc`の書き方だけは学ぶことになります。）

その他、詳細な機能は公式READMEを参照してください。
https://github.com/Shopify/sprockets-commoner#features

## 信頼できるプロダクトなの？

sprocketsの拡張ということで、「それ、本当に信頼して大丈夫なの？」と心配になる方もいるかと思います。
ただ、これに関しては僕は大丈夫だと思っています。

というのも、開発元がShopifyで開発者がRails/Sprocketsの開発にも携わっている[boukさん](https://github.com/bouk)、さらに（コードを書いてるわけではなさそうですが）Railsコミッターの[rafaelfrancaさん](https://github.com/rafaelfranca)（二人ともShopifyの人）がレビューしていますし、必要とあれば他のRails/Sprocketsコミッターも呼んでレビューしているので、このgemがRails/Sprockets wayに則っていること、ちゃんと開発がされ続けること、それなりに大きなサービスで実際に使われていることが期待できます。

## インストール手順

さて、話が長くなりましたが実際に導入してみましょう。
https://github.com/Shopify/sprockets-commoner#10-second-simple-set-up

1. gemを入れる

    ```rb:Gemfile
    gem 'sprockets-commoner'
    ```

    ```bash:console
    $ bundle install
    ```

2. babel-coreを入れる

    ```bash:console
    $ yarn add babel-core
    # npm派の方は`npm i -S babel-core`ですが、Rails5.1からyarn推奨なのでyarnに慣れた方がいいでしょう
    ```

以上、これだけでsprockets管理下がnode.jsで書けるようになります。

また、管理対象ファイルを制限したい場合は、[上で説明したように](#3-管理対象ファイルが選択可能)設定を追加してください。
babelの設定をする必要がある方は`.babelrc`を書きましょう。
https://github.com/Shopify/sprockets-commoner#enabling-babel-transforms
https://babeljs.io/docs/usage/babelrc/

## PostCSS(cssnext)は使えるの？

残念ながら出来ません。諦めてwebpackなどに移るか、sprockets-commonerにコミットしましょう。

ちなみにですが、sprockets-commonerは内部で[schmooze](https://github.com/Shopify/schmooze)（これもShopify製）という軽量ExecJSを使ってRubyからbabel-coreを叩いています。（[コード](https://github.com/Shopify/sprockets-commoner/blob/7180a07f1c3ac0d7d9abf59bd3b270a87f535bf5/lib/sprockets/commoner/processor.rb)）
ここにpostcssを足せば、使えるようになると思います。

## [webpacker](https://github.com/rails/webpacker)使えばいいんじゃないの？

先日、DHHがRails5.1にwebpack拡張を入れる意向を示し、[webpacker](https://github.com/rails/webpacker)というgemを公開しました。
これを使うと確かにRailsからwebpackを使えるようになるのですが、このgemはsprocketsの置き換えは目的としておらず、別々の役割を持って同居する形となります。
この同居を実現するため、webpackerは`app/javascript/`というディレクトリを新しく作り、その配下を管理対象とします。

対して、sprockets-commonerは既存のsprockets管理対象（`app/assets/javascripts/`）をnode.js化するため、ファイル配置場所はそのままで大丈夫で導入・移行コストが最小限に抑えられますし、既存コードのnode.js/ESnextへの移行時に使える便利機能もあるので、もし最終的にwebpackerに完全移行するとしても移行期間に使うのは非常に有用だと思います。

## まとめ

sprockets-commonerを使えば、「これまでの環境はなるべく壊さず」「かかるコストは最小限」で「node.js/ESnextでフロントエンドを書ける」ようになります。
また、既存コードを順次node.js/ESnext環境に置き換えていくための機能も備えてますので、最終的にはwebpackなどを使うとしても移行時に非常に便利ですし、それ以外の人にとってもフロントエンドビルドツールを一から学ばずとも導入できる気軽さがあるのでオススメです。

「もうこれがsprockets4でいいんじゃないかな？」ってくらいの完成度である[sprockets-commoner](https://github.com/Shopify/sprockets-commoner)、みなさんも使ってみてはいかがでしょうか？