Skip to content

Instantly share code, notes, and snippets.

@japboy
Created July 19, 2012 17:12
Show Gist options
  • Save japboy/3145402 to your computer and use it in GitHub Desktop.
Save japboy/3145402 to your computer and use it in GitHub Desktop.
My Cakefile

オレオレ Cakefile

セットアップ

npm install -g coffee-script
npm install -g clean-css growl jade moment nib stylus uglify-js

coffee-script 以外は -g 付けずに Cakefile と同じディレクトリにインストールしても OK

Growl の通知を有効にするには growl のインストールと環境ごとに対応した追加ライブラリのインストールが必要。詳しくは Growl for nodejs を参照。

使い方

cake [build|minify|watch]
  • build で CoffeeScript, Jade, Stylus から JavaScript, HTML, CSS へ変換
  • minifybuild して .js, .css をミニファイして .min.js, .min.css として保存
  • watch でファイル更新を検知して自動で buildminify を実行

例えば;

example/
├── css/
│   ├── _mixins.styl
│   └── style.styl
├── js/
│   └── script.coffee
├── lib/
│   ├── jquery.js
│   └── normalize.css
├── _layout.jade
├── Cakefile
└── index.jade

上記のようなファイル構成で Cakefile のあるディレクトリ上で cake minify コマンドを打つと、下記のような感じでファイルが生成されます。

example/
├── css/
│   ├── _mixins.styl
│   ├── style.css
│   ├── style.min.css
│   └── style.styl
├── js/
│   ├── script.coffee
│   ├── script.js
│   └── script.min.js
├── lib/
│   ├── jquery.js
│   ├── jquery.min.js
│   ├── normalize.css
│   └── normalize.min.css
├── _layout.jade
├── Cakefile
├── index.html
└── index.jade

補足

  • _ で始まるファイルは変換対象外 (Jade や Stylus の include 用ファイル想定)
  • Cakefiletargets 配列に対象ディレクトリを指定 (再帰検索はしない)
  • ビルドとミニファイはファイルの内容に変化があった場合のみの実行
#
# My Cakefile
# written by [Yu Inao](http://twitter.com/japboy)
# updated on 2013-01-25
# TODO: Add YUIDoc for documentation
http = require 'http'
fs = require 'fs'
path = require 'path'
url = require 'url'
util = require 'util'
{spawn} = require 'child_process'
csso = require 'csso'
coffee = require 'coffee-script'
growl = require 'growl'
jade = require 'jade'
moment = require 'moment'
nib = require 'nib'
stylus = require 'stylus'
uglifyjs = require 'uglify-js'
# Specify target directories
targets = [
'.'
'./css'
'./js'
'./lib'
]
# Specify header for converted JavaScript/CSS files
header =
msg: """
/**
* File built with Cakefile at #{moment().format()}
*/
""" + '\n'
token: /^.*\n.*\n.*\n(.*)/
# Get a list of specified files
getFiles = (dirpath, token) ->
contents = fs.readdirSync dirpath
files = []
for content in contents when content.match(token)
cpath = "#{dirpath}/#{content}"
files.push cpath if fs.statSync(cpath).isFile()
files
# Convert from CoffeeScript to JavaScript
convertCoffee = (fpath) ->
conv_fpath = fpath.replace /\.coffee$/, '.js'
_str = fs.readFileSync fpath, 'utf8'
conv_str = coffee.compile _str
if not fs.existsSync conv_fpath
fs.writeFileSync conv_fpath, header.msg + conv_str, 'utf8'
util.log "File built: #{conv_fpath}"
growl "File built: #{conv_fpath}"
else
_str = fs.readFileSync conv_fpath, 'utf8'
orig_str = _str.replace header.token, '$1'
if conv_str != orig_str
fs.writeFileSync conv_fpath, header.msg + conv_str, 'utf8'
util.log "File built: #{conv_fpath}"
growl "File built: #{conv_fpath}"
true
# Convert from Jade to HTML
# https://github.com/visionmedia/jade#a5
convertJade = (fpath) ->
conv_fpath = fpath.replace /\.jade$/, '.html'
_str = fs.readFileSync fpath, 'utf8'
html = jade.compile _str, { filename: fpath, pretty: true }
conv_str = html()
if not fs.existsSync conv_fpath
fs.writeFileSync conv_fpath, conv_str, 'utf8'
util.log "File built: #{conv_fpath}"
growl "File built: #{conv_fpath}"
else
_str = fs.readFileSync conv_fpath, 'utf8'
orig_str = _str
if conv_str != orig_str
fs.writeFileSync conv_fpath, conv_str, 'utf8'
util.log "File built: #{conv_fpath}"
growl "File built: #{conv_fpath}"
true
# Convert from Stylus to CSS
# http://learnboost.github.com/stylus/docs/js.html
# FIXME: This must be SYNC
convertStyl = (fpath) ->
conv_fpath = fpath.replace /\.styl$/, '.css'
_str = fs.readFileSync fpath, 'utf8'
stylus(_str)
.set('filename', fpath)
.set('compress', false)
.use(nib())
.render (err, str) ->
util.error(err) if err
conv_str = str
if not fs.existsSync conv_fpath
fs.writeFileSync conv_fpath, header.msg + conv_str, 'utf8'
util.log "File built: #{conv_fpath}"
growl "File built: #{conv_fpath}"
else
_str = fs.readFileSync conv_fpath, 'utf8'
orig_str = _str.replace header.token, '$1'
if conv_str != orig_str
fs.writeFileSync conv_fpath, header.msg + conv_str, 'utf8'
util.log "File built: #{conv_fpath}"
growl "File built: #{conv_fpath}"
true
# Compress CSS to be minified
# https://github.com/css/csso
compressCss = (fpath) ->
conv_fpath = fpath.replace /\.css$/, '.min.css'
_str = fs.readFileSync fpath, 'utf8'
conv_str = csso.justDoIt _str
if not fs.existsSync conv_fpath
fs.writeFileSync conv_fpath, header.msg + conv_str, 'utf8'
util.log "File minified: #{conv_fpath}"
growl "File minified: #{conv_fpath}"
else
_str = fs.readFileSync conv_fpath, 'utf8'
orig_str = _str.replace header.token, '$1'
if conv_str != orig_str
fs.writeFileSync conv_fpath, header.msg + conv_str, 'utf8'
util.log "File minified: #{conv_fpath}"
growl "File minified: #{conv_fpath}"
true
# Compress JavaScript to be minified
compressJs = (fpath) ->
conv_fpath = fpath.replace /\.js$/, '.min.js'
map_fpath = fpath.replace /\.js$/, '.min.js.map'
_str = fs.readFileSync fpath, 'utf8'
toplevel = uglifyjs.parse _str,
filename: fpath
toplevel: toplevel
toplevel.figure_out_scope()
compressor = uglifyjs.Compressor()
compressed_ast = toplevel.transform compressor
compressed_ast.figure_out_scope()
compressed_ast.compute_char_frequency()
compressed_ast.mangle_names()
source_map = uglifyjs.SourceMap()
stream = uglifyjs.OutputStream { source_map: source_map }
compressed_ast.print stream
conv_str = stream.toString()
map_str = source_map.toString()
if not fs.existsSync conv_fpath
fs.writeFileSync conv_fpath, header.msg + conv_str, 'utf8'
util.log "File minified: #{conv_fpath}"
growl "File minified: #{conv_fpath}"
else
_str = fs.readFileSync conv_fpath, 'utf8'
orig_str = _str.replace header.token, '$1'
if conv_str != orig_str
fs.writeFileSync conv_fpath, header.msg + conv_str, 'utf8'
util.log "File minified: #{conv_fpath}"
growl "File minified: #{conv_fpath}"
if not fs.existsSync map_fpath
fs.writeFileSync map_fpath, map_str, 'utf8'
util.log "Source map generated: #{map_fpath}"
growl "Source map generated: #{map_fpath}"
else
_str = fs.readFileSync map_fpath, 'utf8'
if map_str != _str
fs.writeFileSync map_fpath, map_str, 'utf8'
util.log "Source map generated: #{map_fpath}"
growl "Source map generated: #{map_fpath}"
true
optipng = (callback) ->
for file in (getFiles './img', /\.png$/)
exec = spawn 'optipng'
, ['-backup', '-clobber', '-preserve', '-dir', './img', file]
exec.stderr.on 'data', (data) ->
util.error data.toString()
exec.stdout.on 'data', (data) ->
util.log data.toString()
exec.on 'exit', (code) ->
callback?() if code is 0
true
# Launch local static server
# https://gist.github.com/701407
serve = (port) ->
http.createServer (req, res) ->
uri = url.parse(req.url).pathname
filename = path.join process.cwd(), uri
resError = (code, err) ->
res.writeHead code, { 'Content-type': 'text/plain' }
res.write "#{err}\n"
res.end()
path.exists filename, (exists) ->
return resError 404, 'Not found\n' if not exists
fs.stat filename, (err, stats) ->
return resError 500, err if err
filename = "#{filename}/index.html" if stats.isDirectory()
fs.readFile filename, 'binary', (err, file) ->
return resError 500, err if err
res.writeHead 200
res.write file, 'binary'
res.end()
.listen port
util.log "Static server running at: http://localhost:#{port}"
growl "Static server running at: http://localhost:#{port}"
task 'build', 'Build from CoffeeScript, Jade, & Stylus files', ->
for target in targets
convertCoffee file for file in (getFiles target, /^\.?\/?[^_\.]+\.coffee$/)
convertJade file for file in (getFiles target, /^\.?\/?[^_\.]+\.jade$/)
convertStyl file for file in (getFiles target, /^\.?\/?[^_\.]+\.styl$/)
true
task 'minify', 'Minify CSS, & JavaScript files', ->
invoke 'build'
for target in targets
compressCss file for file in (getFiles target, /^\.?\/?[^_\.]+\.css$/)
compressJs file for file in (getFiles target, /^\.?\/?[^_\.]+\.js$/)
true
task 'watch', 'Watch file changes and build them automatically', ->
files = []
for target in targets
[].push.apply files, (getFiles target, /\.coffee$/)
[].push.apply files, (getFiles target, /^[^_].*\.jade$/)
[].push.apply files, (getFiles target, /^[^_].*\.styl$/)
[].push.apply files, (getFiles target, /[^min]\.css$/)
[].push.apply files, (getFiles target, /[^min]\.js$/)
for file in files
do (file) ->
# TODO: Use this if stable enough in OS X
#fs.watch file, (ev, f) ->
# if 'change' == ev
fs.watchFile file, (curr, prev) ->
if +curr.mtime != +prev.mtime
util.log "Changes detected: #{file}"
growl "Changes detected: #{file}"
convertCoffee file if file.match(/\.coffee$/)
convertJade file if file.match(/\.jade$/)
convertStyl file if file.match(/\.styl$/)
compressCss file if file.match(/[^min]\.css$/)
compressJs file if file.match(/[^min]\.js$/)
true
true
true
task 'open', 'Open index page with default UA', ->
spawn 'open', ['./index.html']
invoke 'watch'
task 'optipng', 'Optimize PNG files', ->
optipng()
task 'serve', 'Simple static server for testing', ->
port = 49513
serve port
spawn 'open', ["http://localhost:#{port}/"]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment