YeomanとBrunchをさわさわした

YeomanとBrunchを並べて試してメモしてみたよ


Yeoman and Brunch

Yeoman and Brunch


今回はニュートラルな気持ちで、並べて触ってみるのが目的なので、どっちが良い・悪い、という評価的な主張は重きを置いてない。(感想としては混じってるけど)淡々と記録しているだけなので、流し読んでいただければ幸い。

  1. CompareTable
  2. Install
  3. Skeletons/BoilerPlate
  4. Scaffolding (generate/destroy)
  5. Build
  6. Server & Watch
  7. Test
  8. PackageManager
  9. Conclusion

Compare Table

とりあえず比較表。オリジナルから行列加えてる。

Original: Compare Table - Brunch | HTML5 application assembler

<tr>
  <td valign="middle" class="td1">
    <p class="p2"><b>Feature</b></p>
  </td>
  <td valign="middle" class="td2">
    <p class="p3"><a href="http://yeoman.io/"><b>Yeoman</b></a></p>
  </td>
  <td valign="middle" class="td3">
    <p class="p3"><a href="http://brunch.io/"><b>Brunch</b></a></p>
  </td>
  <td valign="middle" class="td4">
    <p class="p3"><a href="http://gruntjs.com/"><b>Grunt</b></a></p>
  </td>
  <td valign="middle" class="td5">
    <p class="p3"><a href="http://livereload.com/"><b>LiveReload</b></a></p>
  </td>
  <td valign="middle" class="td6">
    <p class="p3"><a href="http://incident57.com/codekit/"><b>CodeKit</b></a></p>
  </td>
</tr>
<tr>
  <td valign="middle" class="td1">
    <p class="p2"><b>Builder, linter, concatenator, minifier</b></p>
  </td>
  <td valign="middle" class="td2">
    <p class="p4">✓</p>
  </td>
  <td valign="middle" class="td3">
    <p class="p4">✓</p>
  </td>
  <td valign="middle" class="td4">
    <p class="p4">✓</p>
  </td>
  <td valign="middle" class="td5">
    <p class="p4">✓</p>
  </td>
  <td valign="middle" class="td6">
    <p class="p4">✓</p>
  </td>
</tr>
<tr>
  <td valign="middle" class="td1">
    <p class="p2"><b>File watcher</b></p>
  </td>
  <td valign="middle" class="td2">
    <p class="p4">✓</p>
  </td>
  <td valign="middle" class="td3">
    <p class="p4">✓</p>
  </td>
  <td valign="middle" class="td4">
    <p class="p4">✓</p>
  </td>
  <td valign="middle" class="td5">
    <p class="p4">✓</p>
  </td>
  <td valign="middle" class="td6">
    <p class="p4">✓</p>
  </td>
</tr>
<tr>
  <td valign="middle" class="td1">
    <p class="p2"><b>Automatic browser reloading</b></p>
  </td>
  <td valign="middle" class="td2">
    <p class="p4">✓</p>
  </td>
  <td valign="middle" class="td3">
    <p class="p4">✓</p>
  </td>
  <td valign="middle" class="td4">
    <p class="p4">✓</p>
  </td>
  <td valign="middle" class="td5">
    <p class="p4">✓</p>
  </td>
  <td valign="middle" class="td6">
    <p class="p4">✓</p>
  </td>
</tr>
<tr>
  <td valign="middle" class="td1">
    <p class="p2"><b>Blazing fast third-party API</b></p>
  </td>
  <td valign="middle" class="td2">
    <p class="p5">✗</p>
  </td>
  <td valign="middle" class="td3">
    <p class="p4">✓</p>
  </td>
  <td valign="middle" class="td4">
    <p class="p5">✗</p>
  </td>
  <td valign="middle" class="td5">
    <p class="p5">✗</p>
  </td>
  <td valign="middle" class="td6">
    <p class="p5">✗</p>
  </td>
</tr>
<tr>
  <td valign="middle" class="td1">
    <p class="p2"><b>Automatic wrapping of scripts in modules</b></p>
  </td>
  <td valign="middle" class="td2">
    <p class="p4">✓</p>
  </td>
  <td valign="middle" class="td3">
    <p class="p4">✓</p>
  </td>
  <td valign="middle" class="td4">
    <p class="p4">✓</p>
  </td>
  <td valign="middle" class="td5">
    <p class="p5">✗</p>
  </td>
  <td valign="middle" class="td6">
    <p class="p5">✗</p>
  </td>
</tr>
<tr>
  <td valign="middle" class="td1">
    <p class="p2"><b>OS X Notification Center / Growl / Inotify output for errors</b></p>
  </td>
  <td valign="middle" class="td2">
    <p class="p5">✗</p>
  </td>
  <td valign="middle" class="td3">
    <p class="p4">✓</p>
  </td>
  <td valign="middle" class="td4">
    <p class="p5">✗</p>
  </td>
  <td valign="middle" class="td5">
    <p class="p5">✗</p>
  </td>
  <td valign="middle" class="td6">
    <p class="p4">✓</p>
  </td>
</tr>
<tr>
  <td valign="middle" class="td1">
    <p class="p2"><b>Built-in webserver</b></p>
  </td>
  <td valign="middle" class="td2">
    <p class="p4">✓</p>
  </td>
  <td valign="middle" class="td3">
    <p class="p4">✓</p>
  </td>
  <td valign="middle" class="td4">
    <p class="p4">✓</p>
  </td>
  <td valign="middle" class="td5">
    <p class="p5">✗</p>
  </td>
  <td valign="middle" class="td6">
    <p class="p5">✗</p>
  </td>
</tr>
<tr>
  <td valign="middle" class="td1">
    <p class="p2"><b>Skeletons (boilerplates)</b></p>
  </td>
  <td valign="middle" class="td2">
    <p class="p4">✓</p>
  </td>
  <td valign="middle" class="td3">
    <p class="p4">✓</p>
  </td>
  <td valign="middle" class="td4">
    <p class="p4">✓</p>
  </td>
  <td valign="middle" class="td5">
    <p class="p5">✗</p>
  </td>
  <td valign="middle" class="td6">
    <p class="p5">✗</p>
  </td>
</tr>
<tr>
  <td valign="middle" class="td1">
    <p class="p2"><b>Scaffolding (generate / destroy)</b></p>
  </td>
  <td valign="middle" class="td2">
    <p class="p6">▲</p>
    <p class="p7">※<span class="s1">1</span></p>
  </td>
  <td valign="middle" class="td3">
    <p class="p4">✓</p>
  </td>
  <td valign="middle" class="td4">
    <p class="p5">✗</p>
  </td>
  <td valign="middle" class="td5">
    <p class="p5">✗</p>
  </td>
  <td valign="middle" class="td6">
    <p class="p5">✗</p>
  </td>
</tr>
<tr>
  <td valign="middle" class="td1">
    <p class="p2"><b>Headless testing without a browser</b></p>
  </td>
  <td valign="middle" class="td2">
    <p class="p4">✓</p>
  </td>
  <td valign="middle" class="td3">
    <p class="p4">✓</p>
  </td>
  <td valign="middle" class="td4">
    <p class="p4">✓</p>
  </td>
  <td valign="middle" class="td5">
    <p class="p5">✗</p>
  </td>
  <td valign="middle" class="td6">
    <p class="p5">✗</p>
  </td>
</tr>
<tr>
  <td valign="middle" class="td1">
    <p class="p2"><b>Cross-platform</b></p>
  </td>
  <td valign="middle" class="td2">
    <p class="p5">✗</p>
  </td>
  <td valign="middle" class="td3">
    <p class="p4">✓</p>
  </td>
  <td valign="middle" class="td4">
    <p class="p4">✓</p>
  </td>
  <td valign="middle" class="td5">
    <p class="p5">✗</p>
  </td>
  <td valign="middle" class="td6">
    <p class="p5">✗</p>
  </td>
</tr>
<tr>
  <td valign="middle" class="td1">
    <p class="p2"><b>Free</b></p>
  </td>
  <td valign="middle" class="td2">
    <p class="p4">✓</p>
  </td>
  <td valign="middle" class="td3">
    <p class="p4">✓</p>
  </td>
  <td valign="middle" class="td4">
    <p class="p4">✓</p>
  </td>
  <td valign="middle" class="td5">
    <p class="p5">✗</p>
  </td>
  <td valign="middle" class="td6">
    <p class="p5">✗</p>
  </td>
</tr>
<tr>
  <td valign="middle" class="td1">
    <p class="p2"><b>Image Optimizer</b></p>
  </td>
  <td valign="middle" class="td2">
    <p class="p4">✓</p>
  </td>
  <td valign="middle" class="td3">
    <p class="p5">✗</p>
  </td>
  <td valign="middle" class="td4">
    <p class="p6">▲</p>
    <p class="p7">※<span class="s1">2</span></p>
  </td>
  <td valign="middle" class="td5">
    <p class="p5">✗</p>
  </td>
  <td valign="middle" class="td6">
    <p class="p4">✓</p>
  </td>
</tr>
<tr>
  <td valign="middle" class="td1">
    <p class="p2"><b>Package Manager</b></p>
  </td>
  <td valign="middle" class="td2">
    <p class="p4">✓</p>
  </td>
  <td valign="middle" class="td3">
    <p class="p5">✗</p>
  </td>
  <td valign="middle" class="td4">
    <p class="p5">✗</p>
  </td>
  <td valign="middle" class="td5">
    <p class="p5">✗</p>
  </td>
  <td valign="middle" class="td6">
    <p class="p5">✗</p>
  </td>
</tr>

※1 generateのみがあって、destroyはない

※2 grunt-imgなどのtaskを入れればできる(のは果たして△と言って良いのか?)

Install

Yeoman

Yeomanはチェックスクリプトが用意されていて、手動で必要な依存ライブラリを揃えていく感じ。yeoman本体はnpmの管理下にある。

% curl -L get.yeoman.io | bash
% npm install -g yeoman

前はAutomated Instellerだった覚えがあるけど、問題があったとかで手動インストール&チェックスクリプトの体裁に変更されたみたい。

Brunch

Brunchはすごくシンプルに、npm installのみで入る。

% npm install -g brunch

Skeletons/BoilerPlate

Yeoman

yeoman initで、プロジェクトをgeneratorで初期化する。grunt initのラッパかな。generatorを指定しない場合のデフォルトは、対話式にrequire.jsやbootstrapの利用について確認される。

% yeoman init
Running "init:yeoman" (init) task
Please answer the following:
[?] Would you like to include Twitter Bootstrap for Compass instead of CSS? (Y/n) n
[?] Would you like to include the Twitter Bootstrap JS plugins? (Y/n) n
[?] Would you like to include RequireJS (for AMD support)? (Y/n) Y
[?] Would you like to support writing ECMAScript 6 modules? (Y/n) n
[?] Do you need to make any changes to the above before continuing? (y/N) N

# yeoman init 
# e.g, ...
% yeoman init backbone   # backbone
% yeoman init bbb            # backbone boilerplate
% yeoman init chromeapp # chrome application

組み込みのgeneratorは、yeoman init --helpで確認できて、yeoman/generatorsで管理されてる。後述のScaffoldingにも関わるが、大体そんな感じ。yeoman init chromeapp試したら色々聞かれて、おもしろかった。

generators | Yeomanに、generatorの自作方法が書いてあるが、Brunchと比べてめんどくさそうな印象。

Brunch

brunch newまたはbrunch nで、プロジェクトのスケルトンを作成する。

% brunch new sample-project

31 Oct 07:45:56 - info: Created brunch directory layout
31 Oct 07:45:56 - info: Installing packages…

# brunch new  --skeleton 
# e.g, ... % brunch new sample-project --skeleton https://github.com/icholy/ember-brunch % brunch new sample-project --skeleton https://github.com/paulmillr/brunch-with-chaplin % brunch new sample-project --skeleton https://github.com/pkmishra/Dhancha

デフォルトのスケルトンは、paulmillr/brunch-with-chaplin · GitHubのようで、利用できるスケルトンの一覧はSkeletons · brunch/brunch Wikiにある。addressはgitかgithubのURLを指定できる。

デフォルトのスケルトンが、Backbone, CoffeeScript, Stylus, Handlebarsと盛ってる印象。クセが強いようにも思うが、気に入ったモノがなければ自分用のskeltonを用意して、githubで管理すれば良いだけなので手軽かもしれない。

Scaffolding ( generate / destroy )

Yeoman

前述のスケルトンと同じくしてgrunt initを利用して作成する。init generator:allが実質のスケルトン作成で、generatorを確認して、それ以外に生えてるものでbackbone:modelangular:viewなどがあればScaffoldingも利用できるような感じ。destroy相当のコマンドはたぶんない。

# yeoman init:   [options]
% yeoman init backbone:model Hogehoge
Running "init:yeoman" (init) task

"yeoman" template notes:
   invoke  backbone:model
   create    app/scripts/models/Hogehoge-model.js

Generators Lookup | Yeomanを見る限りでは、lib/generatorsがプロジェクトのカレント、またはnode_modules以下のyeoman-*なパッケージ内にあれば、カスタム品を配置できるのかしら。(未検証)scaffoldingの選択肢の見通しは、grunt init --helpの一覧を参考にできる。

Brunch

Scaffoldingについて、brunch generateまたはbrunch gで作成し、brunch destroyまたはbrunch dで削除する、こうしてみるとbrunchのほうがよりRailsライクかな。反面、yeomanはgrunt+npmな印象(そのまんまだ)。

# brunch generate   [options]
% brunch g collection hoge
31 Oct 07:55:36 - info: create app/models/hoges.coffee
31 Oct 07:55:36 - info: create test/models/hoges_test.coffee

# brunch destroy   [options]
% brunch d collection hoge
31 Oct 07:56:42 - info: destroy test/models/hoges_test.coffee
31 Oct 07:56:42 - info: destroy app/models/hoges.coffee

Brunchの場合は、brunch newで展開したスケルトンのgenerators/を勝手に参照する。そこにそれらがあれば、Scaffoldできる。スケルトンを作成することによって、同時にscaffoldingの選択肢の見通しを確保している。

brunch-with-chaplin/generators/model at master · paulmillr/brunch-with-chaplin · GitHub を見ると、generator.jsonhuge.coffee.hbsのような設定ファイルとテンプレートのペアで生成することができる。

Build

Yeoman

詳しくはBuild | Yeomanを参照すると、Sub Taskがどーとか、r.jsのconfigurationがこーとか、書いてある。

% yeoman build

おもむろにyeoman buildすると、コンパイル系タスクやconcat、min、ファイル名にrevの挿入、Cache Manifestの生成などなどが行われて、app/を元にビルド済みファイル群がdist/に配備される。細かい設定はGruntfile.js依存と思われる。

Brunch

% brunch build

スケルトンのもつconfig.coffeeに書いてある内容を元に、これまたコンパイルやconcat, minなどを行う様子。ファイル名へのrev挿入やCache Manifestなどは行われなかったが、そのあたりは設定ファイル次第というところではないだろうか。

Server & Watch

Yeoman

yeoman watchおよびyeoman serverでok。手元が静的ファイル(サーバサイドはAPI叩くだけとか)で簡潔できるようであれば、yeoman serverで使えば快適そう。

% yeoman watch
FYI: Yeoman`s watch task is integrated within yeoman server to combine the dev server, re-compilation and live reloading of changed assets.

Continuing anyway...


Running "watch" task
Waiting...OK
> > File "app/scripts/main.js" changed.


% yeoman server
Running "server" task

Starting static web server on port 3501
  - /Users/hoge/path/to/yeoman-sandbox/app
I'll also watch your files for changes, recompile if neccessary and live reload the page.
Hit Ctrl+C to quit.

Running "clean" task

Running "coffee:compile" (coffee) task
Unable to compile; no valid source files were found.

Running "compass:dist" (compass) task
Nothing to compile. If you're trying to start a new project, you have left off the directory argument.
Run "compass -h" to get help.

Running "open-browser" task

Running "watch" task
Waiting...OK
> > File "app/scripts/main.js" changed.

地味にopen-browserタスクいいなぁ。watchタスクがLiveReloadも含んでいるため、Chrome ExtensionのLiveReloadを入れておけば使える。

Brunch

brunch watchのオプションとして、brunch watch --serverで、ローカルサーバーが立ち上がる。brunch wのエイリアスもある。

% brunch watch
31 Oct 07:59:31 - info: compiled in 1355ms

% brunch watch --server
31 Oct 08:06:34 - info: application started on http://localhost:3333/
31 Oct 08:06:35 - info: compiled in 925ms

こちらはLiveReloadを利用するには、brunch/auto-reload-brunchが必要なようで、使い方はリンク先を参照のこと。

Test

Yeoman

yeoman testでheadless testが実行される。まんまgrunt-mochaが走っていると思われる。

% yeoman test
Running "mocha:all" (mocha) task
Testing index.html.OK
> > 1 assertions passed (0s)

Done, without errors.

デフォルト構成を見る限りmochachaiphantomjsな構成だが、中身はgruntドリヴンなので、好きな構成を引っ張ってこればよさそう。gruntのビルトインでqunitはあるし、yeoman自身がgrunt-jasmine-taskも抱えているので、メジャーな構成はすぐに使えそう。

Brunch

おもむろにbrunch testすると、jsdomのインストールを求められた。phantomjsとか依存してないのかな〜、と思ったらそういうことらしい。

% brunch test
In order to run tests in a CLI/jsdom environment, you have to install jsdom.

a) Install jsdom for all system packages (recommended):
  * Execute `npm install -g jsdom`
  * Add the parent dir of jsdom to NODE_PATH environment var,
  like `export NODE_PATH=/usr/local/lib/node_modules`
b) Install jsdom locally for this project:
  * Execute `npm install jsdom`

% npm install -g jsdom

npm install -g jsdomないし、locallyにnpm install jsdomとしてjsdomをいれる。

% brunch test
31 Oct 08:14:39 - info: compiled in 1087ms

  Header
    ✓ should contain 4 items

  HeaderView
    ✓ should display 4 links
    ✓ should re-render on login event

  HomePageView
    ✓ should auto-render

  ✔ 4 tests complete (34 ms)

思ったよりキュートな表示がされる。こちらもmochachaiのようで、海外のフロントエンドはもかちゃいがデフォルトだというの、、、。

Package Manager

Yeoman

Yeomanのpackage managerは、実質BOWERまんまである。

% yeoman install underscore
Running "bower:install:underscore" (bower) task
GET https://bower.herokuapp.com/packages/underscore
bower cloning git://github.com/documentcloud/underscore.git
bower caching git://github.com/documentcloud/underscore.git
bower fetching underscore
bower checking out underscore#1.4.2
bower copying /path/to/.bower/underscore

Done, without errors.

./app/componentの下にinstallしたpackageを入れてくれる。ただ、ちょっと難があるところもあって、基本はcomponent.jsonをアテにするのだが、fallback?的にpackage.jsonも参照する。すると、dependenciesにbowerが管理していないpackageが含まれるとインストールに失敗する。

% yeoman install lodash
Running "bower:install:lodash" (bower) task
GET https://bower.herokuapp.com/packages/lodash
bower cloning git://github.com/bestiejs/lodash
bower cached git://github.com/bestiejs/lodash
bower fetching lodash
bower checking out lodash#v0.9.1
bower copying /path/to/.bower/lodash
GET https://bower.herokuapp.com/packages/tar
 tar not found 

lodashは悪くないと思うのだが、bowerがlodashがdependenciesに加えているtarを探しにいってしまってnot foundの憂いをみている。微妙な解決法としては、~/.bowerの下にキャッシュされているリポジトリのpackage.jsonを編集してdependenciesを消してしまえばよかったりする。

Brunch

残念ながらPackage Managerは無い。

Pros of yeoman:

ただし、1.5で乗せる予定らしいので、そのあたりは待ってれば解決が望めるみたい。

Conclusion

Brunch is far more mature because it existed longer. I predict yeoman will fight some of the issues brunch already solves. andriijas commented Issue #408 · brunch/brunch

ちょっと触った感じだと、たしかにBrunchのほうが洗練されている印象かな。現在のYeoman周りの勢いがあれば、解決されるのは時間の問題だろうし、Gruntとのポジショニングの兼ね合いさえクリアできれば、Gruntと親和性の高いYeomanのほうを推したいかも。全体的に様子見。

ちなみに、YeomanサイドがBrunchをどう見てるかの情報は見つけてないけど、少なくともBrunchサイドはYeomanを意識しているのか下記のようなissueでcommentが残されていた。(上の引用も当該のissueから)

フロントエンドのワークフロー&コンポーネント管理は、今後ともヲチを続けていきたい所存。


Author

ahomuAyumu Sato

overflow, Inc.VPoE

東京資本で生きる名古屋の鳥類

Web 技術、組織開発、趣味など雑多なブログ。技術の話題は zenn にも分散して投稿しています。

Bio: aho.mu
X: @ahomu
Zenn: ahomu
GitHub: ahomu

Related

Latest

Archives

Tags

Search