web開発にはフロントエンドのビルドツールが必要。
coffeeスクリプトや、scssなどのメタ言語を使いたいならもちろんビルドをしてjsやcssに変換しないといけないし、もし仮にそれを使わなかったとしても、jsやcssの圧縮をするべきなので、ビルドをするソリューションあるべき。
(どっちみちビルドという工程をやることになるなら、メタ言語というものは使った方が良い。とおもう)
grunt、mincer、brunch を使ってみて、結局brunchにしたので比較したことをメモしておく。
Grunt
http://gruntjs.com/
gruntは、けっこう名前をよく聞くビルドツール。
処理そのものはプラグインとして組み込んでおき、色々な処理たちをパイプライン的に順番に並べる設定ファイルを書く。
ファイル変更の監視や、開発webサーバをgruntタスクとして立ち上げることもできるので、rubyで言うところの rake + guard + webrick がこれひとつで大丈夫
Gruntの気にいらなかったとこ
- watchが素朴な感じで、ファイルの変更を検知 → 設定しておいたタスク実行。これだけなので、よっぽどの工夫をしない限り、coffeeスクリプトをファイル一個保存しただけなのに、プロジェクト内の全部のcoffeeがコンパイルされるという動作をする。
- 僕は暇さえあればファイルを保存しているので、コンパイル走りすぎてうざいから使うのやめた。
- これを回避する良いやりかたがあれば教えてほしい。
mincer
https://github.com/nodeca/mincer
Gruntとはアプローチが違い、あらかじめビルドしておくのではなく、jsやcssへリクエストが飛んできたタイミングで、ビルドしたものを返却する仕組み。
mincerをサーバとして実行して待機させておくか、node.js製のサーバに組み込んで使う。
RailsのAssets pipelineとまったく同じアプローチ。本家にも、"port of sprockets" と書いてある。
mincerの気にいらなかったとこ
- サーバサイドのcoffeeを、同じ枠組みでコンパイルする手段がない。
- 開発環境では、たくさんのjavascript(css)が圧縮されずに1つずつリクエストされるので、リロードしたとき遅い。(一方、Railsは、turbolinks使えば快適だと思われる)
Brunch
Gruntと似たような機能を持っているビルドツール。
Brunch is an ultra-fast HTML5 build tool
と書いてある。
なにがウルトラfastかというと、brunch w
でファイルを監視し、ファイルの変更を検知されたとき、変更されたファイルのみをビルドしてくれるという、私のような暇さえあればファイルを保存している病気がある人にとっては嬉しい挙動をしてくれるところ。
基本的には、HTML側では、開発環境/本番環境できりかえずに、<script src="app.js></script>
みたいな1つのファイルを読みこむようにしておく。
$ brunch w -s
`17 Nov 23:07:38 - info: compiled circle.coffee and 9 cached files into app.js in 136ms`
みたいに、ウォッチしていると、メッセージがコンソールに出て、ちゃんと余計なものはコンパイルしてない賢いことをした結果、app.jsをつくったよ。と教えてくれる。
また、Gruntより良いなと思ったところがいくつか
- bowerとの連携が良い感じ。勝手にbower.json を読んでくれる。
- bowerでいれたスクリプトに、
main
キーが設定されていない場合は、自分のプロジェクト内のbower.jsonでoverrideしておく。 - クライアントサイドの外部スクリプトに関する設定が、ほぼほぼbower.jsonだけで済む。
- bowerでいれたスクリプトに、
- 設定ファイルがもっと短く書ける
- liverelod的なことがBrunchだけでできる。(アドオンとかいらない)
- Yoemenのような雛形作成機能がある。これはGruntにはない。
サーバサイドのcoffeeスクリプトをBrunchで監視
Brunchの設定ファイルde、server
というキーに色々と設定することで、サーバサイドのcoffeeスクリプトも監視してコンパイルしてくれる。
変更されたらサーバを再起動。。みたいな動作は、残念ながら自分で色々設定しないといけないようだったが、
brunch new gh:jerfowler/ExpressBrunchJade
とかして雛形つくると、express + 変更あったら再起動 の設定が同梱されている。
自分は上記をベースにして、いらないところ色々抜いて使ってる。
exports.config =
paths:
public: 'public'
watched: ['src']
modules:
wrapper: 'amd'
# See docs at http://brunch.readthedocs.org/en/latest/config.html.
files:
javascripts:
defaultExtension: 'coffee'
joinTo:
'js/app.js': /^src\/client/
'js/vendor.js': /^bower_components/
templates:
defaultExtension: 'hbs'
joinTo: 'js/app.js'
server:
path: 'brunch_server'
port: 3000
base: '/'
app: 'src/server/app'
persistent: true
interval: 100
watched: ['src/server']
ignore: /(^[.#]|(?:~)$)/
source: /.*\.coffee$/
tester:
enabled: false
plugins:
autoReload:
enabled:
css: on
js: on
assets: off
port: [1234, 2345, 3456]
{exists} = require('fs')
{EventEmitter} = require('events')
{resolve, join} = require('path')
{filter} = require('async')
http = require('http')
cordell = require('cordell')
{config} = require('./config')
class BrunchServer extends EventEmitter
constructor: (config) ->
@config = {}
@config[k] = v for k, v of config.server
throw 'Must specify an app location under config.server.app' unless @config.app?
@app = require(resolve(@config.app))
@server = http.createServer(@app)
# @_debug = if @config.debug? require('debug')("#{@config.debug}") else ->
start: (port, callback) ->
@server.listen port, callback
if @config.watched?
filter @config.watched, exists, (files) =>
@ranger = cordell.ranger files, @config
@ranger.on 'end', (files, stats) =>
setTimeout =>
@ranger.on 'add', =>
@reload()
@ranger.on 'rem', =>
@reload()
@ranger.on 'change', =>
@reload()
, 1000
@emit 'start', @
stop: (fn) ->
console.log 'Stopping...'
@server.close fn
@watcher.close()
@emit 'stop', @
reload: ->
console.log 'Reloading...'
@app = require(resolve(@config.app))
@emit 'reload', @
close: (fn) ->
@stop fn
module.exports = exports = BrunchServer
module.exports.startServer = (port, path, callback) ->
server = new BrunchServer(config)
# io = require('socket.io').listen server.server, logger: server.logger
# io.set 'log level', 2
# sockets = require('./express/sockets')(io)
# server.on 'reload', ->
# sockets.emit '/brunch/reload', 1000
# sockets.destroy()
# sockets = require('./express/sockets')(io)
server.start(port, callback)
server
cofig.coffeeは、Gruntfileよりだいぶ短くなったんだよね