MinifyしてからConcat

配布されているライブラリーを最小ツールに通すと、ライセンスあたりの扱いで面倒なことになる。またCSSの場合は壊れる可能性を否定できないことは意識しなければならない。ということで重い腰を上げて、最小化してから連結するような工夫をソース・マップを維持することを前提にこのウェブサイトで実験し始めた。

JavaScriptファイルのビルドをGruntでやるとして、最小化についてはソース・マップのサポートは問題ないので、いつも通りgrunt-contrib-copygrunt-contrib-uglifyを使うことにする。最後に連結する時にソース・マップを維持できるのかというのが最大の問題だったが、7月にソース・マップのサポートがgrunt-contrib-copyへ入っていたため、結果的にはこれを使うだけで良かった。

タスクの手順的には以下のようになる。

  1. 一時ディレクトリーを掃除
  2. 一時ディレクトリーへソースとなるファイルのコピー
  3. 非ライブラリーのファイルのみをソース・マップ付きで最小化
  4. プロダクションのファイルを連結して作成
  5. 公開ディレクトリーへソース・マップ・ファイルを含めてコピー

これを踏まえて、Bowerでインストールしたlib-a.min.jslib-b.min.js、そして自分で書いたfoo.jsbar.jsを最小化してから連結し、最終的にmain.min.jsを作るとする。タスクの設定は以下のようになることだろう。

module.exports = function (grunt) {
  grunt.initConfig({
    clean: {
      // `tmp/`ディレクトリーを掃除するタスク
      main: {
        src: ['tmp/**/*']
      }
    },

    concat: {
      // 最小化済みのファイルをソース・マップ付きで連結するタスク
      js: {
        options: {
          seperator: ';',
          sourceMap: true,
          sourceMapStyle: 'link'
        },

        files: {
          'tmp/main.min.js': [
            'tmp/lib-a.min.js',
            'tmp/lib-b.min.js',
            'tmp/foo.min.js',
            'tmp/bar.min.js'
          ]
        }
      }
    },

    copy: {
      // プロダクション向けに公開ディレクトリーへコピーするタスク
      js: {
        cwd: 'tmp/',
        dest: 'build/js/',
        expand: true,
        src: [
          '**/*.js',
          '**/*.js.map'
        ]
      },

      // 一時ディレクトリーへ必要なファイルをコピーするタスク
      // 最小化済みのライブラリーは元ファイルとソース・マップ・ファイルも
      prejs: {
        files: {
          'tmp/bar.js': 'src/js/bar.js',
          'tmp/foo.js': 'src/js/foo.js',
          'tmp/lib-a.js': 'bower_component/lib-a/dist/lib-a.js',
          'tmp/lib-a.min.js': 'bower_component/lib-a/dist/lib-a.min.js',
          'tmp/lib-a.min.js.map': 'bower_component/lib-a/dist/lib-a.min.js.map',
          'tmp/lib-b.js': 'bower_component/lib-a/dist/lib-b.js',
          'tmp/lib-b.min.js': 'bower_component/lib-b/dist/lib-b.min.js',
          'tmp/lib-b.min.js.map': 'bower_component/lib-b/dist/lib-b.min.js.map'
        }
      }
    },

    uglify: {
      options: {
        preserveComments: 'some',
        sourceMap: true
      },

      // まだ最小化されていないファイルをソース・マップ付きで最小化するタスク
      main: {
        cwd: 'tmp/',
        dest: 'tmp/',
        expand: true,
        ext: '.min.js',
        src: [
          '**/*.js',
          '!**/*.min.js'
        ]
      }
    }
  });

  grunt.loadNpmTasks('grunt-contrib-clean');
  grunt.loadNpmTasks('grunt-contrib-copy');
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-uglify');

  grunt.registerTask('build:js', [
    'clean',
    'copy:prejs',
    'uglify',
    'concat:js',
    'copy:js'
  ]);
};

concat:jscopy:prejs以外はほぼポータブルなタスク設定になっている。これでgrunt build:jsと実行すると、最終的にbuild/js/ディレクトリーへ以下のようにファイルが作成される。

中間のソース・マップ・ファイルはちゃんとまとめられているので、実際にはマークされているファイルだけあれば良い。しかし設定が汎用性のない複雑なものになってしまうので、全部コピーしておく方が面倒がないだろう。そうでなければソース・マップ・ファイルへsourcesContentで元ソースを含めてしまうのが良い。この辺りはもうちょっと運用しないとどうするのが最良なのかは見えてこなさそうだ。


欠点はあまりない。最小化後に連結するという観点の考察がウェブにはまだないので手探りでやることになるのが一番の壁だろう。取るに足りないとは思うが、最終ファイルが数バイトから数十バイト増えることは挙げておく。

利点としては、ライブラリーのライセンスや著作権者情報など消すべきでないものが確実に残ることと、ライブラリーが動作が確認されている状態のままで連結されることが挙げられる。消すべきでないものが残るということは、つまりビルド・タスク側でいわゆるバナーなどと呼ばれている先頭のコメントのことを考えなくて良いということでもある。

欠点と比較する限り、得られるものは大きいと言える。