Broccli開発者によるリリースエントリーの適当日本語訳。
http://www.solitr.com/blog/2014/02/broccoli-first-release/
途中で飽きた。やっぱgulpがいいわ。
ブロッコリーは、新規のビルドツール。Node上で動作し、バックエンドに依存せず、Railsのアセットパイプラインに相当するもの。
長い0.0.xのアルファリリースを経て、最初のベータ版Broccoli0.1.0をプッシュした。
ここで、構文を説明するためにコメントなしで提示されたサンプルのビルド定義ファイル(Brocfile.js)は、次のとおり。
module.exports = function (broccoli) {
var filterCoffeeScript = require('broccoli-coffee');
var compileES6 = require('broccoli-es6-concatenator');
var sourceTree = broccoli.makeTree('lib');
sourceTree = filterCoffeeScript(sourceTree);
var appJs = compileES6(sourceTree, {
...
outputFile: '/assets/app.js'
});
var publicFiles = broccoli.makeTree('public');
return [appJs, publicFiles];
};
broccoli serve
を実行すると、ソースファイルを監視して、ローカルホスト上で継続的にビルド出力する。Broccoliは、可能な限り高速でbroccoli serve
するよう最適化されているので、リビルド時の一時停止を経験することはもうない。
一度broccoli build dist
を実行すると、dist
ディレクトリにビルドの出力を配置する。
より長い例については、broccoli-sample-appを見て。
Broccoliの設計にもっとも重要なことは、高速な増加分のリビルドを可能にしていること。理由はこう:
例えば、CoffeeScript、Sass、さらにいくつかのようなコンパイラで書かれたアプリケーションをビルドするためにGruntを使うとする。
開発者は、ファイルを編集すれば毎回手動でリビルドしなくてもブラウザーがリロードするようにしたいはず。だからgrunt watch
を使って自動でリビルドする。しかし、アプリケーションの成長に合わせて、ビルドが遅くなる。数ヶ月の開発期間のうちに、編集→リロードのサイクルが、編集→10秒待って→リロードというサイクルになる。
だからビルドを高速化するには、変更されたファイルのみをリビルドしてみる。だけど、時に1つの出力ファイルは、複数の入力ファイルに依存するので、これは困難である。ファイルが変更された時に依存しているファイルを正しくリビルドするために、いくつかの依存関係のルールを手動で設定する。しかし、Gruntはよくこれを行うように設計されていなかったし、カスタムルールセットでは、確実に正しいファイルをリビルドしない。Sometimes it rebuilds files when it doesn’t have to (making your build slow). Worse, sometimes it doesn’t rebuild files when it should (making your build unreliable).
Broccoliでは、一度broccoli serve
を起動すると、ファイルを監視してリビルドが必要なものだけをリビルドする。
In effect, this means that rebuilds tend to be O(1) constant-time with the number of files in your application, as you generally only rebuild one file. I’m aiming for under 200 ms per rebuild with a typical build stack, since that type of delay feels near-instantaneous to the human brain, though anything up to half a second is acceptable in my book.
もう一つの懸念は、プラグインで解決できる。BroccoliでCoffeeScriptをコンパイルして、出力を縮小化することがいかに簡単であるかをお見せする。
var tree = broccoli.makeTree('lib')
tree = compileCoffeeScript(tree)
tree = uglifyJS(tree)
return tree
Gruntでは 、出力先ディレクトリだけでなく、CoffeeScriptの出力を格納するための一時ディレクトリを作る必要がある。それらをすべて記述した結果、Gruntfileは長くなっていく。Broccoliでは、これらはすべて自動的に処理される。
好奇心の強い人のために、Broccoliのアーキテクチャについてお話しする。
Broccoliにおける記述ソースファイルの抽出とプロダクトのビルドの単位は、ファイルではなくむしろツリー、すなわちファイルとサブディレクトリを持ったディレクトリーです。だから、ファイルがファイルに出て行くではなく、ツリーがツリーに出て行く。
我々は個々のファイルの周りにBroccoliを設計した場合、我々は(それが1つの出力ファイルに1つの入力ファイルをコンパイルするように)うまくCoffeeScriptのをコンパイルすることができると思うが、APIは、(@import
文で複数のファイルを読み込んでいて、1つの出力ファイルにn個の入力ファイルをコンパイルする必要がある)Sassのようなコンパイラのために不自然なものになるだろう。
一方、Broccoliのデザインは、ツリーに基づいて、Sassのようなn:1のコンパイラは問題なく、CoffeeScriptのような1:1のコンパイラはサブケースとして簡単に表現可能である。実際には、我々は実装がそれらを非常に容易にするために、このような1:1のコンパイラ用Filter
ベースクラスを持っている。
この仕組みは、少しばかり捉えにくいものである。最初は2つの原始的な、ファイルを含むディレクトリを表した「ツリー」、と入力ツリーを受け取って新たなコンパイル済みのツリーを返すチェーンナブルな「transform」、でBroccoliをデザインしていた。
これはマップのツリーを1:1に変換することを意味する。驚くべきことに、これは、すべてのコンパイラにとって良い抽象化ではありません。例えば、Sassコンパイラには、@import
構文を検出したときに検索する「load paths」の概念がある。同様に、r.jsのようなJavaScriptのconcatenatorsは、インポートされたモジュールを検索するための「paths」を選択できる。これらのロードパスは、理想的には「ツリー」オブジェクトの集合として表される。
ご覧のように、多くの実世界のコンパイラは、実際には1つツリーにn個のツリーをマップする。これをサポートする最も簡単な方法は、プラグインによって、0、1、またはn個の入力ツリーを取ることができ、それらの入力ツリー自身で対処できるようにすることである。
しかし、今、我々はプラグインがそれらの入力ツリーを処理させることを、私たちはもうBroccoliの世界でファーストクラスのオブジェクトとしてコンパイラについて知っている必要はない。プラグインは、単純にゼロ以上の入力ツリー(そしておそらくいくつかのオプション)を取る関数をエクスポートし、新しいツリーを表すオブジェクトを返す。例えば:
broccoli.makeTree('lib') // => a tree
compileCoffeeScript(tree) // => a tree
compileSass(tree, {
loadPaths: [moreTrees, ...]
}) // => a tree