アカベコマイリ

HEAR NOTHING SEE NOTHING SAY NOTHING

grunt-usemin

仕事の Web フロントエンド開発でファイル構成を以下のように管理したくなった。

  • jQuery や normalize.css などのライブラリと自作部を分ける
  • 開発用 HTML では素の JavaScript、CSS を参照
  • リリース用 HTML では複数の JavaScript と CSS を結合 & Minify したものを参照
  • リリース用のファイル構成には必要最小なものだけが含まれるようにする

この要望を満たすのに grunt-usemin がよい感じだったので使い方をメモしておく。

あと別件で Yeoman を試してみようとしたが構成要素である GruntBower の理解が浅くファイル構成で混乱したので、今回の記事ではこれらに慣れるため素で使ってみる。

開発用ファイル構成

開発用のファイル構成はルート部分に HTML を配置。そこから参照されるものがサブ フォルダになるという一般的な形である。

/
├ index.html
├ page-1.html
├ page-2.html
├ css
│  ├ normalize.css
│  └ style.css
├ js
│  ├ jquery-2.1.1.js
│  └ main.js
└ img
   ├ logo.png
   └ background.png 

この中から normalize.css と jQuery を切り離して Bower で管理したい。ビルド作業で Grunt を使用することも考えると、これらの設定ファイルとソースが混じって見通しが悪くなるため開発用ファイルは src フォルダへ置くことにする。

/
└ src
  └ 開発用ファイル

ついでに直置きしていた normalize.css と jQuery を削除。

Bower

すでに Node.js がインストールされ npm が利用できることを前提とする。はじめに Bower を npm からインストールしておく。-g オプションでグローバルにしているので一度だけ実行すればよい。

$ npm install -g bower

これで bower コマンドが利用できるようになった。プロジェクトのルートへ移動して bower init コマンドにより設定ファイルを作成する。ウィザード形式だが質問への回答は適当でもよい。面倒ならすべて Enter で。

$ bower init

ウィザードが終了するとプロジェクトのルートに bower.json が生成されている。もし回答でミスがあったらこのファイルを修正すればよい。次にプロジェクトで使用するライブラリをインストール。今回は jQuery と nomalize.css。

$ bower install --save jquery
$ bower install --save normalize.css

bower install する際に --save オプションをつけるとインストールしたライブラリの依存情報を bower.json に追記してくれる。この情報があると bower.json の置かれた場所で

$ bower install

を実行するだけでライブラリを一括インストールできる。よって Git リポジトリには bower.json だけコミットしておき、Bower 用の bower_components フォルダは .gitignore で除外しておく。これでプロジェクトのファイル構成は以下のように変化した。

/
├ src
│  └ 開発用ファイル
└ bower_components
  ├ normalize.css
  └ jquery

そのため HTML からのライブラリ参照パスも変更が必要になる。

<link rel="stylesheet" href="../bower_components/normalize.css/normalize.css">
<link rel="stylesheet" href="css/style.css">
<script src="../bower_components/jquery/dist/jquery.js"></script>
<script src="js/main.js"></script>

このままだとプロジェクトをリリースする先でも bower_components が必要になりイマイチだ。また開発中は素の CSS と JavaScript を参照するけどリリース用にファイル結合 & Minify したくなるだろう。これらに対応する場合、開発とリリースで HTML の参照パスを切り替えることになる。ファイルを二重管理するとか参照部分を動的に更新するような仕組みを構築するのも面倒だ。

grunt-usemin

というわけで grunt-usemin の出番がきた。これは Grunt のプラグインでファイル結合、Minify、HTML 内の CSS や JavaScript の参照パス切り替えを実行してくれる優れもの。利用するために Grunt と各種プラグインをプロジェクトにインストールする。

はじめに Grunt CLI (Command Line Interface) をグローバルにインストール。

$ npm install -g grunt-cli

これで grunt コマンドが利用できるようになった。Grunt とプラグインは npm なので Bower と同じく設定ファイルで依存を管理したい。というわけでプロジェクトのルートに移動してから npm init を実行。ウィザードが開始されるので適当に回答してゆく。

$ npm init

するとプロジェクトのルートに package.json が生成される。この状態になったら続けて Grunt 関連をインストールする。以下の例ではわかりやすくするためコマンドを分けているけど面倒ならパッケージ名をスペース区切りで列挙して一括インストールも可能。

$ npm install --save-dev grunt
$ npm install --save-dev grunt-contrib-clean
$ npm install --save-dev grunt-contrib-concat
$ npm install --save-dev grunt-contrib-copy
$ npm install --save-dev grunt-contrib-cssmin
$ npm install --save-dev grunt-contrib-uglify
$ npm install --save-dev grunt-contrib-watch
$ npm install --save-dev grunt-filerev
$ npm install --save-dev grunt-usemin

これでプロジェクトのファイル構成は以下のように変化した。

/
├ src
│  └ 開発用ファイル
├ bower_components
│  └ ライブラリ
└ node_modules
   └ node.js パッケージ

nom install のオプションに --save をつけると依存関係が package.json に記録される。その際の JSON プロパティ名は dependencies になる。

実際にプロジェクトから参照するものとビルドなど開発作業だけに利用するものを分けて定義したほうが管理しやすいのでオプションを --save-dev として JSON プロパティ名が devDependencies となるようにしている...と理解したけどあっているだろうか?

ちなみに dependenciesdevDependencies のどちらでも定義さえあれば package.json の置かれた場所で以下のコマンドを実行すると内容に応じてパッケージをインストールしてくれる。

$ npm install

この機能があるため node_modules フォルダも .gitignore で Git リポジトリ管理から除外しておこう。

パッケージが揃ったので Grunt を利用するためのスクリプト Gruntfile.js を作成する。これは JavaScript または CoffeeScript で記述されたビルド処理ファイルとなる。私は CoffeeScript を使ったことがないので JavaScript にしておく。

module.exports = function( grunt ) {
    var pkg = grunt.file.readJSON( 'package.json' );

    grunt.initConfig( {
        pkg: pkg,
        /**
         * ファイル、ディレクトリを削除します。
         * @type {Object}
         */
        clean: {
            build: [ '.tmp', 'dist' ],
            release: [ '.tmp' ]
        },
        /**
         * ファイル、ディレクトリをコピーします。
         * @type {Object}
         */
        copy: {
            html: {
                files: [ {
                    expand: true,
                    cwd: 'src',
                    src: [ '*.html', 'img/*.*' ],
                    dest: 'dist'
                } ]
            }
        },
        /**
         * usemin の grunt タスクを作成します。
         * @type {Object}
         */
        useminPrepare: {
            html: 'src/*.html',
            options: {
                dest: 'dist'
             }
        },
        /**
         * CSS、JavaScript を結合・圧縮し、HTML の参照パスを更新します。
         * @type {Object}
         */
        usemin: {
            html: 'dist/*.html',
            options: {
                dest: 'dist'
             }
        },

        /**
         * フォルダの変更を監視する設定。
         * $ grunt watch を実行している間、変更がある度に指定されたタスクを実行する。
         * @type {Object}
         */
        watch: {
            scripts: {
                files: [ 'src/css/*.css', 'src/js/*.js' ],
                tasks: [ 'build' ]
            }
        }
    } );

    Object.keys( pkg.devDependencies ).forEach( function( devDependency ) {
        if( devDependency.match( /^grunt\-/ ) ) {
            grunt.loadNpmTasks( devDependency );
        }
    } );

    grunt.registerTask( 'build', [ 'clean:build', 'copy', 'useminPrepare', 'concat', 'uglify', 'cssmin', 'usemin', 'clean:release' ] );
    grunt.registerTask( 'default', [ 'watch' ] );
};

環境面の準備が整ったので HTML 側を修正。CSS と JavaScript の参照部分に grunt-usemin 用のコメントを追記する。

<!-- build:css css/app.css -->
<link rel="stylesheet" href="../bower_components/normalize.css/normalize.css">
<link rel="stylesheet" href="css/style.css">
<!-- endbuild -->

<!-- build:js js/app.js -->
<script src="../bower_components/jquery/dist/jquery.js"></script>
<script src="js/main.js"></script>
<!-- endbuild -->

このようにすることで grunt-usemin を実行したときコメントの buildendbuild に囲まれた部分の結合と Minify が処理される。プロジェクトのルートに移動してビルド実行してみる。

$ grunt build

すると Gruntfile.js に記述した処理の実行と結果出力がおこなわれ、成功すればプロジェクト直下に dist というサブフォルダが追加される。内容は以下のようになっているはず。

dist
├ index.html
├ page-1.html
├ page-2.html
├ css
│  └ app.css
├ js
│  └ app.js
└ img
   ├ logo.png
   └ background.png 

HTML のライブラリ参照部分を見ると、結合 & Minify されて dist 側に出力されたものに更新されている。

<link rel="stylesheet" href="css/app.css">
<script src="js/app.js"></script>

複数の HTML で同じファイル名を指定した結合 & Minify を定義した場合、buildendbuild の内容が異なればビルド時にエラーとなる。これはおそらく同名ならファイル生成の重複を避けて一度だけ実行する処理となっており、構成の異なるものがあるとそれが阻害されるためと思われる。

構成が異なるなら build コメントのファイル名を変更して別にしたほうがよい。その場合でも共通部分は同じ buildendbuild にしておいて差分だけ別にすると管理しやすそうだ。

一度でも環境を作ってしまえばライブラリが増減しても bower.json と src 側の HTML 修正だけで済む。なんとも楽ちん。

サンプル

今回の記事で作成したプロジェクトをサンプルとして GitHub に公開してみた。

clone して README.md の Installation に書かれた手順を実行すると grunt-usemin によるビルドを試せる。