- 開発中とリリースビルドをわけて考える。
- 開発中はとにかく速度重視。テスト重視。
- リリースビルドのほうに、自動化できる便利なタスクを出来るだけ突っ込んでおく。
- 開発中に使用するソースファイルから、リリースビルドに余計なファイルを混ぜない。
こんな考え方でGruntfileを書いてみました。
プロジェクトごとに最適化したGruntfileを作るということも魅力的な挑戦なのですが、私は開発者一人で短納期な案件をいくつもこなさないといけないので、そのために導入したタスクランナーのはずなのにGruntfileの開発やメンテに時間を取られるのは本末転倒になってしまうので、出来る限り汎用的に使えるように気をつけて書きました。
さらに何か特定の開発スタイルやフレームワークに依存しないように、特殊なディレクトリ構成などを必要としない点も気を付けました。
普段は開発は私一人ですが、デザイナーやコーダーや開発者などスキルの異なる複数人でコーディングを同時進行する場合も多々あるからです。
Gruntを導入することによって自分や誰かのスキルや開発スタイルに依存したり変更することがないよう、こころがけています。
CoffeeScript、Sass対応。
Lessは対応しているはずだけど自分はまだ使ってません。
次の段階でTypeScriptにも対応させるつもり。
Grunt0.4.1対応。
プラグインは管理が面倒臭いので、grunt-contribを全部入りで入れています。
#除外ファイル
exclude = [
'!**/.DS_Store'
'!**/Thumbs.db'
'!**/*.coffee'
'!**/*.map'
'!**/*.scss'
'!**/*.less'
'!**/*.s.css'
'!**/*.l.css'
'!**/<%= dir.cssCompile %>/'
'!**/coffee/'
'!**/sass/'
'!**/less/'
'!**/_notes/'
'!**/.idea/'
'!**/.gitignore'
'!**/*.mno'
'!**/Templates/'
'!**/Library'
'!**/*.dwt'
'!**/*.lbi'
'!**/*.fla'
]
module.exports = (grunt) ->
pkg = grunt.file.readJSON 'package.json'
grunt.initConfig
#ディレクトリ設定
dir :
src : 'src'
dist : 'dist'
test : '<%= dir.src %>/test'
doc : 'docs'
js : 'js'
css : 'css'
cssCompile : '<%= dir.src %>/<%= dir.css %>.compile'
#package.jsonの読み込み
pkg : pkg
#クリーン
clean:
js:
src : '<%= dir.src %>/<%= dir.js %>/*'
css:
src : '<%= dir.src %>/<%= dir.css %>/*'
build :
src : ['<%= dir.dist %>/**', '<%= dir.doc %>/**']
#CoffeeScriptコンパイル
coffee:
options:
sourceMap : true
#通常はルートディレクトリのcoffeeディレクトリに置かれたスクリプトをメインとする
main :
src : '<%= dir.src %>/coffee/*.coffee'
dest : '<%= dir.src %>/<%= dir.js %>/<%= pkg.name %>.js'
#サブディレクトリのCoffeeScriptもコンパイル
all :
expand : true
ext : '.js'
src : ['<%= dir.src %>/**/*.coffee', '!<%= coffee.main.src %>']
#Sassコンパイル
sass:
#通常はルートディレクトリのsassディレクトリに置かれたCSSをメインとする
main :
src : '<%= dir.src %>/sass/*.scss'
dest : '<%= dir.cssCompile %>/<%= pkg.name %>.s.css'
#サブディレクトリのSassもコンパイル
all :
expand : true
ext : '.css'
src : ['<%= dir.src %>/**/*.scss', '!<%= sass.main.src %>', '!<%= dir.src %>/<%= dir.css %>/<%= pkg.name %>.css', '<%= concat.css.dest %>']
#Lessコンパイル
less:
#通常はルートディレクトリのlessディレクトリに置かれたCSSをメインとする
main :
src : '<%= dir.src %>/less/*.less'
dest : '<%= dir.cssCompile %>/<%= pkg.name %>.l.css'
#サブディレクトリのlassもコンパイル
all :
expand : true
ext : '.css'
src : ['<%= dir.src %>/**/*.less', '!<%= less.main.src %>', '!<%= dir.src %>/<%= dir.css %>/<%= pkg.name %>.css', '<%= concat.css.dest %>']
#結合
concat:
#コンパイルされたCSSを結合
css :
src : '<%= dir.cssCompile %>/*.*.css'
dest : '<%= dir.src %>/<%= dir.css %>/<%= pkg.name %>.css'
#画像最適化
imagemin:
dev :
optimizationLevel: 3
files : [
expand: true
src: '<%= dir.src %>/**/*.{png,jpg,jpeg}'
]
dist :
optimizationLevel: 3
files : [
expand: true
src: '<%= dir.dist %>/**/*.{png,jpg,jpeg}'
]
#監視ファイル
watch:
coffee:
files : '<%= coffee.main.src %>'
tasks : 'coffee:main'
coffeeAll:
files : '<%= coffee.all.src %>'
tasks : 'coffee:all'
sass:
files : '<%= sass.main.src %>'
tasks : 'sass:main'
sassAll:
files : '<%= sass.all.src %>'
tasks : 'sass:all'
less:
files : '<%= less.main.src %>'
tasks : 'less:main'
lessAll:
files : '<%= less.all.src %>'
tasks : 'less:all'
css:
files : '<%= concat.css.src %>'
tasks : 'concat:css'
#コピー
copy:
build :
expand : true
filter: 'isFile'
cwd : '<%= dir.src %>/'
src : ['**'].concat exclude
dest : '<%= dir.dist %>/'
#htmlのminify
htmlmin:
all :
options :
removeComments : true
removeCommentsFromCDATA : true
removeCDATASectionsFromCDATA : true
collapseWhitespace : true
removeRedundantAttributes : true
removeOptionalTags : true
expand : true
src : '<%= dir.dist %>/**/*.html'
#JSのminify
uglify:
options :
banner: '/*! <%= pkg.name %> <%= grunt.template.today("dd-mm-yyyy") %> */\n'
main :
expand : true
src : '<%= dir.dist %>/<%= dir.js %>/<%= pkg.name %>.js'
all :
expand : true
src : ['<%= dir.dist %>/**/*.js', '!<%= uglify.main.src %>']
#CSSのminify
cssmin:
main :
expand : true
src : '<$= dir.dist %>/<%= dir.css %>/<%= pkg.name %>.css'
all :
expand : true
src : ['<%= dir.dist %>/**/*.css', '!<%= cssmin.main.src %>']
#yuidoc
yuidoc:
dist:
name : '<%= pkg.name %>'
description : '<%= pkg.description %>'
version : '<%= pkg.version %>'
options :
paths : '<%= dir.src %>/'
outdir : '<%= dir.doc %>'
syntaxtype : 'coffee'
extension : '.coffee'
for taskName of pkg.devDependencies when taskName.substring(0, 6) is 'grunt-'
grunt.loadNpmTasks taskName
#どういった用途で使用するか明示的にするためにも、冗長なエイリアスも指定しておく
grunt.registerTask 'default', 'watch'
grunt.registerTask 'w', 'watch'
grunt.registerTask 'c', ['clean:js', 'clean:css']
grunt.registerTask 'main', ['coffee:main', 'sass:main', 'less:main']
grunt.registerTask 'compile', ['coffee:all', 'sass:all', 'less:all']
grunt.registerTask 'img', 'imagemin:dev'
grunt.registerTask 'build', ['clean:build', 'copy', 'imagemin:dist', 'htmlmin', 'uglify', 'cssmin', 'yuidoc']
プロジェクトディレクトリは次のようになります。
-
src
開発用ディレクトリ。ここでごりごり開発していきます。
-
dist
リリースビルド用ディレクトリ。ビルドすると自動的に作られます。
-
doc
ドキュメントが入るディレクトリ。これもビルドすると自動的に作られます。
開発用ディレクトリとリリースビルドの他にもうひとつ、開発中ビルドのディレクトリ「dev」も欲しかったのですが、LiveReloadなどリロード系監視システムでdevフォルダを監視してしまうと、開発ビルドが行われるたびに何十回、何百回とリロードしまくってしまうので、srcをソースディレクトリ兼開発用ディレクトリとして、LiveReloadなどの監視対象としています。
さらにデザイナー系コーダーの方と同時開発する場合でも、srcさえ共有しておけば相手はGruntを使っていなくてもLiveReloadやCodeKitなどで同じようにSassやCompass、LessやCoffeeScriptなども導入できるという利点もあります。
使い方
開発中のときとリリースビルドするときで動作が違います。
順番に説明していきます。
開発中
プロジェクトディレクトリで次のようにgrunt
してsrcディレクトリを監視開始します。
$ grunt
srcディレクトリを監視して、ファイルの更新があった場合次のような動作をします。
/src/coffee/*.coffee
に変更があった場合
/src/coffee/ 内の *.coffee ファイルを全て結合して
/src/js/<%= pkg.name %>.js
として保存します。
(<%= pkg.name %>はパッケージ名)
なのでHTML側では
<script src="/js/< パッケージ名 >.js"></script>
このようにパッケージ名.jsファイルを読み込むようにしておけばいいことになります。
パッケージ名は、プロジェクト毎にGruntをインストールするときに使う、package.json
の
{
"name": "パッケージ名"
};
この部分です。
/src/coffee
はリリースビルドの時にはビルドされません。
リリースビルドの動作は後でも詳細に書きます。
その他 *.coffee
に変更があった場合
自動でCoffeeを結合してくれるのもいいんですが、場合によっては単体のCoffeeScriptを単体のままJavaScriptとして出したいときもあります。
そこで、 /src/coffee
以外のどこに書いた *.coffee
でも、同一ディレクトリに < 同名 >.js
としてコンパイルする機能も備えています。
つまり
/src/hoge/fuga.coffee
は
/src/hoge/fuga.js
としてコンパイルされます。
fuga.coffeeのほうは、リリースビルドのときにはビルドされないので安心です。
SassやLessも基本的には同じ
ただしちょっとだけ複雑なので順番に説明します。
/src/sass/*.scss
に変更があった場合
/src/sass
内の*.scss
ファイルに変更があった場合、一旦
/src/css.compile/
というディレクトリに
<%= pkg.name %>.s.css
というファイル名でコンパイルします。
つまりHTML側では /css.compile/< パッケージ名 >.s.css をリンクすればいいのだなと言うのはちょっと待ってください。
/src/less/*.less
ファイルに変更があった場合
sassのときと似たように、
/src/css.compile/
というディレクトリに
<%= pkg.name %>.l.css
というファイル名でコンパイルします。
つまり
/src/css.compile/ディレクトリには
Sassで作った
< パッケージ名 >.s.css
と、Lessで作った
< パッケージ名 >.l.css
の二つのファイルが入る可能性があるということになります。
このディレクトリ内ファイルをHTML側からリンクするのはちょっと待ってください。
/src/css.compile/はリリースビルドに含まれません。 もう一段階Gruntが自動化処理します。
/src/css.compile/
内ファイルに変更があった場合
つまりSassなりLessなりがコンパイルされた場合ですが、Gruntにさらにそれをwatchさせていて、
/src/css/<%= pkg.name %>
として結合します。
なのでHTML側では
<link rel="stylesheet" href="/css/< パッケージ名 >.css">
としておけばいいということになります。
その他*.scss
や*.less
に変更があった場合
CoffeeScriptのときと事情が一緒ですが、全部をまとめてひとつのファイルに結合したくないとき、つまり個別にSassを書いたら個別のcssファイルとしてコンパイルしたい場合もあります。
/src/sass/
やsrc/less/
以外の場所にある*.scss
や*.less
ファイルはそのまま単体のcssファイルとしてコンパイルさせる機能も持っています。
つまり
/src/hoge/fuga.scss
は
/src/hoge/fuga.css
としてコンパイルされます。
もちろんfuga.scssのほうはリリースビルドには含まれないので安心です。
画像最適化を手動で行う
リリースビルドの時に画像最適化は自動で行いますので、ふだん開発中は気にしなくてもいいのですが、コマンドラインで
$ grunt img
と打つことで開発中の/src/ディレクトリ内画像を最適化処理することもできます。
開発中に画像のファイルサイズなどが気になった場合に使えます。
基本的な開発中の機能は以上です。
次はリリースビルド時にどういうことをするかの説明に入ります。
リリースビルド
リリースビルドをするには、コマンドラインで
$ grunt build
と入力します。
プロジェクトディレクトリに、
/dist/
というディレクトリを自動的に作り、その中にリリースビルドが書き出されます。
除外ファイル
リリースビルドに含ませたくないソースファイルや無駄なファイルは、
Gruntfile.coffeeの一番最初の
#除外ファイル
exclude = [
'!**/.DS_Store'
'!**/Thumbs.db'
'!**/*.coffee'
'!**/*.map'
'!**/*.scss'
'!**/*.less'
'!**/*.s.css'
'!**/*.l.css'
'!**/<%= dir.cssCompile %>/'
'!**/coffee/'
'!**/sass/'
'!**/less/'
'!**/_notes/'
'!**/.idea/'
'!**/.gitignore'
'!**/*.mno'
'!**/Templates/'
'!**/Library'
'!**/*.dwt'
'!**/*.lbi'
'!**/*.fla'
]
この部分で指定できます。
.DS_StoreやThumbs.db、CoffeescriptやSassのソースファイルやソースディレクトリ、その他flaファイルやpsd素材ファイル、Dreamweaverのテンプレートやデザインノートファイルなど、リリースに含ませたくないファイルは最初から除外しています。
他に除外したいファイルがあれば追加してください。
minify
HTML、Javascript、CSSファイルをminifyします。
さらに画像はプロジェクト内にあるpngとjpegをimageminで最適化を行っています。
ドキュメント
さらに
/doc/ディレクトリにYUIDocのドキュメント化も行っています。
まだちょっと自分でも使っていませんが、いずれ便利に使える日が来ることを信じて。
さいごに
既にリリースビルドが終わっていて、更新作業のときにgrunt build
し忘れて古いファイルをアップしてしまったという、Gruntを導入したことによって増えたヒューマンエラーがありましたが、リリースビルドを習慣づけるという人的対応、あと将来的にgrunt watch
のタイミングでリリースビルドを削除してしまうと言うシステム対応も考えていて、そのへんでどうにか切り抜けようと思います。
いちおう3案件ほど実戦投入してきて、だいぶこなれてきたので思い切って晒してみました。
最後に思ったんだけど、これアップグレードしていくんでGitHubに置いたほうがよかった。