Jam で Rails のクライアントサイド JS を管理する


Jam (http://jamjs.org/) は クライアントサイド JavaScript のパッケージマネージャ。Ruby でいう gem / bundler のようなことができる。

これを使って Rails のクライアントサイド JS を管理する方法を検討した。

# インストール

要 npm。

```bash
npm install --global jamjs
```

# 概要

`jam install` を実行すると...

```bash
jam install backbone
```

カレントディレクトリに jam ディレクトリができる。

```bash
$ tree jam
jam
├── backbone
│   ├── backbone.js
│   └── package.json
├── jquery
│   ├── dist
│   │   └── jquery.js
│   ├── package.json
│   ├── src
│   │   └── sizzle
│   │       ├── package.json
│   │       └── speed
│   │           └── benchmark.js
│   │               └── package.json
│   └── test
│       └── qunit
│           └── package.json
├── require.config.js
├── require.js
└── underscore
    ├── package.json
    └── underscore.js
```

Backbone が依存している jQuery と Underscore のファイルがあるのがわかる (正確には jQuery 依存してないけど)。

`package.json` は gemspec みたいなもので、バージョンや依存するパッケージが記述されている。

`require.js` は RequireJS ( http://requirejs.org/ )。

`require.config.js` は RequireJS の設定ファイル。


# Asset Pipeline を使わない場合

`cd public` してから `jam install`。

```bash
rails new jam_test
cd jam_test
cd public
jam install backbone
```

HTML では `/jam/require.js` だけ読み込み、`require([パッケージ名], ロード後に実行する関数)` と書けば、指定したライブラリと依存するライブラリをロードして、第 2 引数の関数を実行してくれる。

```html
<script src="/jam/require.js"></script>
<script>
  require(["backbone"], function(b){ console.log(Backbone.VERSION); }); // 0.9.9
</script>
```

# Asset Pipeline を使う場合 (1)

`Rails.root` で `jam install`。

```bash
rails new jam_test
cd jam_test
jam install backbone
```

assets path に `jam` ディレクトリを追加して...

```ruby
# config/application.rb
config.assets.paths << Rails.root.join("jam")
```

`application.js` などで自前でロードする。ロード順については `jam/require.config.js` を参考にする。

```javascript
// app/assets/javascripts/application.js
//= require jquery
//= require jquery_ujs
//= require underscore/underscore
//= require backbone/backbone
//= require_tree .
```

application.js に書かなきゃいけないのは面倒だけど、jam 使わないのに比べるとだいぶ楽。

# Asset Pipeline を使う場合 (2)

Sprockets に手を入れて `jam/require.config.js` をロードする。

```ruby
# config/initializers/sprockets.rb
require "sprockets/directive_processor"
module Sprockets
  class DirectiveProcessor
    def process_require_jam_directive
      # read jam/require.config.js
      config = "#{Rails.root}/jam/require.config.js"
      jam = ExecJS.compile(File.read(config)).eval("jam")

      packages = jam["packages"]
      shim = jam["shim"]

      # Resolving dependencies
      resolved = []
      unresolved = packages.dup

      until unresolved.empty?
        resolved_names = resolved.map{|package| package["name"]}

        just_resolved = unresolved.select{|package|
          deps = (shim[package["name"]] && shim[package["name"]]["deps"]) || []
          (deps - resolved_names).empty?
        }

        resolved += just_resolved
        unresolved -= just_resolved
        if just_resolved.empty? && !unresolved.empty?
          raise "Cannot resolve #{unresolved.map{|package| package["name"]}}"
        end
      end

      # Load libraries
      resolved.each do |package|
        path = File.join(package["location"].gsub(%r{^jam/}, ""), package["main"])
        context.require_asset(path)
      end
    end
  end
end
```

asset path を追加して...

```ruby
# config/application.rb
config.assets.paths << Rails.root.join("jam")
```

application.js に `require_jam` と書く。

```js
// app/assets/javascripts/appplication.js
//= require_jam
//= require jquery_ujs
//= require_tree .
```

これで、依存関係を気にせず、jam install でインストールしたライブラリをすべてロードできる。

なお、この方法では、新たに `jam install` しても application.js 自体に変化がないので、cache が効いてしまう模様。`rm -rf tmp/cache/assets` か `rake tmp:cache:clear` でとりあえず cache 削除できる。