sprockets4にnpmを利用するための機能が追加された

こんにちは

みんなにdisられて久しいsprockets氏ですが、面白そうなコミットが入っていたので紹介します
https://github.com/rails/sprockets/commit/6eac004a1567f70270606170f8a4d51d4e2fb3e8

## どんなコミット？

sprocketsからnpmパッケージを利用するための機能が追加されました。
これにより、例えばこれまでjquery-railsなどjsをラップしたgemを用いていたものを、npmで管理できるようになります。

## この変更は何が嬉しいの？

この変更により、npmの世界でjsライブラリを管理する**道が整備されました**。

なぜ**道が整備された**ことを強調するかと言いますと、別にこれまでもbrowserify-rails/webpack-railsなどを利用すれば同じことが出来たからです。
ただ、「Railsが公式にnpmでのパッケージ管理を行う道を整備した」ことは、以下のような状況と比較して大いに価値があることだと思っています。

- エッジなjsを使いたい人は、独自に頑張らなければならなかった
    - 導入/維持コストが馬鹿にならない
    - 車輪の再発明
- 頑張れない状況の人はxxjs-rails gemなどを利用しなければならなかった
    - JSをRubyの世界で管理しなければならない
    - バージョンアップデートつらい
    - JSの進化についていけない

## 今すぐ使いたい方々のための導入ガイド

### 1. sprockets4をinstall

Gemfileに以下の行を追加して、`bundle install`します。

```rb:Gemfile
gem 'sprockets', git: 'git@github.com:rails/sprockets.git', branch: 'master'
```

ちなみに、現在のSprocketsの最新は4.0.0.beta4で、これにはまだnpmの機能は入っていません。
beta5になれば入ると思いますので、beta5以降は以下のように定義すれば大丈夫だと思います。

```rb:Gemfile
gem 'sprockets', '4.0.0.beta5'
```

### 2. npmパッケージをinstall

ここでは一例としてjqueryとreactをinstallします。（「その２つを同時に使うな」という話は別でやりましょう）

注： 個人的趣味によりnpmでなく[yarn](https://yarnpkg.com/)を使います

```bash:console
$ yarn add jquery react react-dom
```

このコマンドを打つと以下のファイルができます。

```json:package.json
{
  "dependencies": {
    "jquery": "^3.1.1",
    "react": "^15.3.2",
    "react-dom": "^15.3.2"
  }
}
```

jqueryはnpmで管理することにしたので、Gemfileの`gem 'jquery-rails'`は消してしまいましょう。

### 3. asset管理対象にnode_modulesを入れる

[Rails Guides: アセットパイプライン](http://railsguides.jp/asset_pipeline.html#%E3%82%A2%E3%82%BB%E3%83%83%E3%83%88%E3%81%AE%E7%B7%A8%E6%88%90)

```rb:config/application.rb
module SampleApp
  class Application < Rails::Application
    config.assets.paths << Rails.root.join('node_modules')
  end
end
```

### 4. npmパッケージを読み込む

```js:app/assets/javascripts/application.js
//= require jquery
//= require react/dist/react
//= require react-dom/dist/react-dom
```

ここでちょっと面倒な話なのですが、コード例のようにjqueryは`//= require jquery`でrequireできますが、
reactやreact-domでは`//= require react`ではなく`//= require react/dist/react`と書く必要があります。

これはなぜかと言いますと、各パッケージのpackage.jsonの`"main"`に書かれたjsファイルが「ブラウザで読めるjs」か「node.js(CommonJS)のjs」かの2通りあるためです。

これについて、もう少し詳しく見ていきます。

まず、sprokects/npmの実装は以下の流れになっています。（[コード](https://github.com/rails/sprockets/commit/6eac004a1567f70270606170f8a4d51d4e2fb3e8#diff-9316dbf7ddc668b0c4df82b179005ab1R39)）

1. 各npmパッケージのpackage.jsonを取得し、
2. "main"に書かれたファイル、ないし"main"未定義の場合はindex.jsを読み込む

それを理解した上でjqueryを見てみますと、下記の画像のように`"main"`で指定されたファイルは"dist/jquery.js"で、これはブラウザで読めるjsになっております。

![1.png](https://qiita-image-store.s3.amazonaws.com/0/15402/1556c484-5e22-f28a-afa9-653b62ab9209.png)

対してreactはと言いますと、`"main"`に書かれたファイルはnode.jsファイルのため、ブラウザで読むことができません。

![2.png](https://qiita-image-store.s3.amazonaws.com/0/15402/46e410d7-6585-8562-4b8d-d4de3d35a0c4.png)

ブラウザで読めるコンパイル済みファイルは`dist/`配下にあります。なので、直接これを`//= require react/dist/react`と指定してあげる必要があります。

![3.png](https://qiita-image-store.s3.amazonaws.com/0/15402/bd578122-72cb-2289-8539-0572b9df9ca4.png)

`require`してみてうまくいかなかった時に`node_modules`を確認しに行けばいいだけとはいえ、これが少々面倒で何かうまい感じにならないかなー、と思っています。
が、それを置いておけばこれだけでnpmパッケージをsprocketsで利用できます！

実際に動くサンプルアプリも作りましたので、動かしてみたい方はどうぞ
https://github.com/itkrt2y/sprockets4-sample

## 所感

### Pros

- 手軽にnpmを利用できるようになる
    - Ruby層でのJSパッケージ管理をやめて、JS層に持ってこれる
- Railsでのnpmの使い方を公式が提供してくれたことにより、車輪の再発明が減る

### Cons

sprocketsを使う以上、前からずっとある問題ですが......

- Railsを起動しない、JSのみの単体テストをすることができない
- やっぱりCommonJS欲しい
    - globalにライブラリが読み込まれてしまう......
- JSガチ勢の利用には耐えなさそう......

まとめると、sprockets故の苦しみは解消されていないのですが、現状のJS界を鑑みれば落とし所としては悪くないのでは？という印象でした

## 追記

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">rails newする際にnpm用の設定を生成する提案も行われていて、色々と動いてはいる / Add npm support in new apps using --npm option  <a href="https://t.co/rvuzrgsJjU">https://t.co/rvuzrgsJjU</a></p>&mdash; 神社 (@y_yagi) <a href="https://twitter.com/y_yagi/status/791098058486317056">2016年10月26日</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>