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では、require
とprovide
という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
に含まれるパスをシェルのグロブルールで展開するかどうかをtrue
かfalse
で指定する:load_path => ["vendor/sprockets/*/src"]
などが展開されるかどうか- デフォルトは
true
まとめ
Sprockets自体はprototype.jsなどのパッケージングでも利用されており、RubyやRailsとは無関係のプロジェクトでも重宝しそう。Rails 3.1での利用が前提ならば、実行環境はおいといてとりあえずディレクティブの働きだけを理解すればよさそう。