Sooey

2011-08-01 12:10:27 +0900

Asset Pipelineを備えたRails 3.1のリリースに向けて、その根幹を担うSprocketsをちょっと触っておくことにした。

SprocketsはRubyで書かれたJavaScriptプリプロセッサで、複数のJavaScriptソースファイルを1つにまとめるのが主な機能。それによって以下のようなメリットがある。

  • 複数のサイトやアプリケーションで共通するコードを再利用可能な形で切り出した構成にできる
    • JavaScriptプラグインという単位でCSSや画像もまとめることができる
  • JavaScriptファイルを1つにまとめてブラウザに読ませることによる高速化
    • HTTPリクエストの回数を減らすことができる
  • JavaScriptをコメント付きの複数のファイルおよびディレクトリとして整理できる
    • ソースコードをサイトやアプリケーションのルートディレクトリ外で管理することもできる
    • コメントは最終的に除去されてブラウザに渡されるため、沢山のコメントを書いても問題にならない

インストール

今回はRails 3.1には触れないので、Sprocketsを普通にgemとしてインストールする。

$ gem install sprockets
Fetching: sprockets-1.0.2.gem (100%)
Successfully installed sprockets-1.0.2
1 gem installed

インストールが完了するとsprocketizeコマンドが利用できるようになる。

$ sprocketize
Usage: sprocketize [options] filename [filename ...]
    -C, --directory=DIRECTORY    Change to DIRECTORY before doing anything
    -I, --include-dir=DIRECTORY  Adds the directory to the Sprockets load path
    -a, --asset-root=DIRECTORY   Copy provided assets into DIRECTORY
    -h, --help                   Shows this help message

Sprockets向けの記法

Sprocketsで処理されるJavaScriptでは、requireprovideという2つのディレクティブを使用することができるほか、YAMLファイルで定義した定数値を埋め込むことができる。

コメント

Sprocketsではソースファイル内のコメントを以下のように扱う。

  • //=で始まるコメントはSprocketsのディレクティブとして扱われる
  • 結合後のファイルには含まれないコメント
    • ソースファイル中の//コメント
    • /** ... **/のようなPDocコメント(PDocはprototype.jsなどで使用されているAPIドキュメント用の記法&ツール)
  • 結合後のファイルにも含まれるコメント
    • ソースファイル中の/* ... */コメント

requireディレクティブ

requireディレクティブを使うと、指定したファイルがその位置に展開された状態で結合後のファイルへの書き出しが行われる。指定したファイルが既にrequiredで処理されていた場合は何もしない。

// ロードパス内にあるjquery.jsを読み込む
//= require <jquery>

ファイル名を<...>で指定した場合は、ロードパス内を順にたどってjquery.jsを探し、最初に見つかったファイルが処理対象となる。ファイルが見つからない場合はエラーが発生する。

// 同じディレクトリにあるjquery.jsを読み込む
//= require "jquery"

ファイル名を"..."で指定した場合は、ロードパスの探索は行われず、処理中のファイルと同じディレクトリにあるjquery.jsが探される。

外部のパッケージやライブラリ、プラグインなどを読む場合は<...>を使い、プロジェクト内のソースファイルを読む場合は"..."を使うのが一般的な使い方のようだ。

サブディレクトリ内のソースファイルを読む場合は以下のように指定する。

//= require <foolib/foo>

この場合は、ロードパスの中でfoolibというディレクトリ内にあるfoo.jsというファイルが探される。

provideディレクティブ

CSSや画像、HTMLなどのアセットファイルをJavaScriptプラグインと関連付けるためにはprovideディレクティブを使用する。

JavaScriptファイル内に記述したprovideディレクティブは、現在のJavaScriptファイルがアセットファイルに依存することを示し、JavaScriptファイルに関連するファイル群を一括してドキュメントルートにコピーする機能が利用できるようになる。

例えば、以下のようなディレクトリ構造で、4つの画像ファイル、1つのCSSファイル、2つのJavaScriptファイルを含むcolor_pickerプラグインの場合、

plugins/color_picker/assets/images/color_picker/arrow.png
plugins/color_picker/assets/images/color_picker/circle.png
plugins/color_picker/assets/images/color_picker/hue.png
plugins/color_picker/assets/images/color_picker/saturation_and_brightness.png
plugins/color_picker/assets/stylesheets/color_picker.css
plugins/color_picker/src/color.js
plugins/color_picker/src/color_picker.js

Sprocketsのロードパスにはplugins/color_picker/src/が含まれるようになるという前提で考え、plugins/color_picker/src/color_picker.jsファイルには以下のようなディレクティブを記述する。

//= require "color"
//= provide "../assets"

こうすると、あるアプリケーション内のJavaScriptファイルにrequire <color_picker>という記述があった場合に、Sprocketsによるプラグインの参照が行われ、provideディレクティブによって明示されたアセットファイルがそのアプリケーションのドキュメントルートにコピーされる(ドキュメントルートのimagesおよびstylesheets以下にコピーされる)。

定数の展開

constants.ymlというYAMLファイルで定義した定数を、<%= ... %>という記法で展開することができる。

例えばcolor_pickerプラグインの場合、plugins/color_picker/src/constants.ymlというファイルに以下のような定数の定義を書き、

COLOR_PICKER_VERSION: 1.0.0
COLOR_PICKER_AUTHOR: Sam Stephenson <sam@example.org>

JavaScriptからは<%= ... %>で参照する。

/* Color Picker plugin, version <%= COLOR_PICKER_VERSION %>
 * (c) 2009 <%= COLOR_PICKER_AUTHOR %>
 * Distributed under the terms of an MIT-style license */

var ColorPicker = {
  VERSION: '<%= COLOR_PICKER_VERSION %>',
  ...
};

定数名はグローバルに扱われ、ロードパス内で参照できるので注意。指定された定数が見つからない場合は、エラーが発生してプリプロセス処理は中断する。

つかいかた

Sprockets本体はRubyライブラリになっていて、それを利用した以下のようなツールが同梱されている。

  • コマンドラインツール(sprocketize
  • Railsプラグイン(sprockets-rails
  • その他の環境用のCGIスクリプト

今回は、コマンドラインツールとして呼び出すパターンと、ライブラリとしてRubyのコードから呼び出すパターンを試してみる。

コマンドラインから使う

sprocketizeコマンドは引数にソースファイルとオプションを指定すると、ソースファイルを結合したものを出力するので、それをファイルにリダイレクトして保存します。

コマンドラインオプション。

-C, --directory=DIRECTORY    処理前にディレクトリを移動する
-I, --include-dir=DIRECTORY  ロードパスにディレクトリを追加する
-a, --asset-root=DIRECTORY   アセットをディレクトリにコピーする
-h, --help                   ヘルプメッセージを表示する
-v, --version                バージョンを表示する

使用例。

$ sprocketize -I app/javascripts \
              -I vendor/sprockets/prototype/src \
              -I vendor/sprockets/color_picker/src \
              --asset-root=public \
              app/javascripts/*.js > public/sprockets.js

上記のコマンドでは、app/javascripts/*.jsが1つにまとめてpublic/sprockets.jsとして保存される。その際、

  • app/javascripts
  • vendor/sprockets/prototype/src
  • vendor/sprockets/color_picker/src

の3つのディレクトリがロードパスに追加され、requireディレクティブで参照されるファイルの探索が行われる。また、requireで読み込んだファイル中にprovideディレクティブが記述されていた場合は、その値に応じたアセットファイルがpublic以下にコピーされる。

ライブラリとして使う

RubyのコードからSprocketsを使用する場合は、以下のようにSprockets::SecretaryクラスとSprockets::Concatenationクラスを利用する。

# オプションを指定してSprockets::Secretaryオブジェクトを生成する
secretary = Sprockets::Secretary.new(
  :asset_root   => "public",
  :load_path    => ["vendor/sprockets/*/src", "vendor/plugins/*/javascripts"],
  :source_files => ["app/javascripts/application.js", "app/javascripts/**/*.js"]
)

# ソースファイルの結合を行うSprockets::Concatenationオブジェクトを生成する
concatenation = secretary.concatenation

# 結合結果をファイルに保存する
concatenation.save_to("public/sprockets.js")

# アセットファイルをコピーする
secretary.install_assets

Sprockets::Secretary.newには以下のようなオプションが指定できる。

  • :root
    • ロードパスやソースファイルとして指定されるディレクトリの起点となる場所
    • デフォルトはカレントディレクトリ(.
  • :asset_root
    • アプリケーションのドキュメントルートまたはWebサーバーがファイルを配信するディレクトリ
  • :load_path
    • JavaScriptソースファイルを探す際に対象となるディレクトリ名の配列
    • 絶対パスまたは相対パス(相対パスの場合は:rootからの相対指定となる)
    • 配列に格納された順に探索が行われる
  • :source_files
    • 処理対象のJavaScriptソースファイルの配列
    • 絶対パスまたは相対パス(相対パスの場合は:rootからの相対指定となる)
    • 配列に格納された順に1つずつ処理が行われる
  • :expand_paths
    • load_pathおよびsource_filesに含まれるパスをシェルのグロブルールで展開するかどうかをtruefalseで指定する
    • :load_path => ["vendor/sprockets/*/src"]などが展開されるかどうか
    • デフォルトはtrue

まとめ

Sprockets自体はprototype.jsなどのパッケージングでも利用されており、RubyやRailsとは無関係のプロジェクトでも重宝しそう。Rails 3.1での利用が前提ならば、実行環境はおいといてとりあえずディレクティブの働きだけを理解すればよさそう。