エンジニアをリングする

プログラをミングしたり。

my web site twitter

AngularJS開発に便利なgulpプラグインと設定

この記事はAngularJS Advent Calendar 2014の11日目の記事です。

私がAngularJSでアプリ開発した時にすごく便利だったgulpタスクの組み合わせを紹介しようと思います!

  • gulp-concat
  • gulp-tap
  • gulp-ng-annotate

gulp-concat

AngularJSアプリは1モジュール1ファイルにする場合も多いかと思いますが、そうなるとどんどんファイル数が増えて読み込みがとにかく大変です。
ディレクティブが展開されない!と思ったらそのディレクティブを定義したjsファイル読み込み忘れてたとかね・・・
なのでjsファイルはgulpタスクでひとつにまとめてからそれだけ読み込むと、ファイルが増えてもHTMLでの読み込みを増やさなくていいのでスムーズです!
ファイルをひとつにまとめてくれるのがgulp-concatです!

var concat = require('gulp-concat');

gulp.task('js', function() {
  return gulp.src('src/js/**/*.js')
    .pipe(concat('build.js'))
    .pipe(gulp.dest('www/js/'));
});

これでsrc/js/以下のjsファイルがwww/js/build.jsにまとまって出力されるので、HTMLからはそのbuild.jsだけ読み込んでおけば、ControllerもServiceもDirectiveもファイルを作った時点で自動で読み込んでくれてすぐ使えます!
これでファイルを増やすときの手間がだいぶはぶけます。
(Browserifyとかも使ってみたいけど使ったことない)

gulp-tap

複数のスタイルガイドにあるように、私はモジュール定義のときに直接関数リテラルを書くのは避けるようにしています。

// 非推奨
angular
  .module('app')
  .controller('HogeController', function() { /* 中身 */ });

// 推奨
angular
  .module('app')
  .controller('HogeController', HogeController);

var HogeController = function() { /* 中身 */ }

こっちのほうがネストも少なく良いのですが、問題は下部で定義した関数(変数)がグローバルスコープを汚染してしまうことです。
これは即時関数で囲んで一時的なスコープに閉じ込めてあげれば解決できます。

// HogeControllerを即時関数のスコープに閉じ込める
(function() {
  angular
    .module('app')
    .controller('HogeController', HogeController);

  var HogeController = function() { /* 中身 */ }
}());

どうせ全ファイルをこれで囲むなら、gulpタスクでやってしまいましょう!
そこでgulp-tapの出番です。

var tap = require('gulp-tap');

gulp.task('js', function() {
  return gulp.src('src/js/**/*.js')
    .pipe(tap(function(file) {
        file.contents = Buffer.concat([
            new Buffer("(function(){\n"),
            new Buffer("'use strict';\n"),
            file.contents,
            new Buffer("}());")
        ]);
    }))
    .pipe(concat('build.js'))
    .pipe(gulp.dest('www/js/'));
});

これで実際のファイルには中身の定義だけ書いておいても、ビルド時に即時関数で囲んでから結合してくれるので、実際にはひとつひとつのモジュール定義が即時関数に囲まれた形で実行されます!
あと即時関数のついでにstrictモードの記述も挟んでいます。
strictも関数で囲めばその関数の中でだけ効いてくれるので、即時関数で囲んであげると安全です!

編集するファイルの内容と実際に実行されるコードが違うのは好みがあるかと思いますが、個人的にはファイルに必要なことだけ記述できるようになって見通しがよくなったのでオススメです。インデントもひとつ減らせるし。

gulp-ng-annotate

さてさてこちらはAngularならではのgulpプラグインです。

AngularJSのminify対策、やってますか?
モジュールを定義するときに引数に他のモジュールの名前を書くことでそのモジュールを注入できるDIがAngularJSの特徴のひとつですが、内部的には関数を文字列としてパースして処理しているので、minifyなどで引数の名前が変わってしまうとDIがうまく働かなくなります。
その回避策として以下のように配列の中に文字列でモジュール名を書いておくことで引数側がminifyされても引数の内容と順番を守ることができます。

angular.module('myApp')
  .controller('HogeController', ['$scope', 'FugaService', function($scope, FugaService) {
  //略
}]);

これはやっておいたほうがいいのですが、いいのですが・・・

超だるい。

新しいモジュールを注入したいときに2箇所も変更しないといけないのです。
しかもその2箇所がちゃんと同期していないと知らないうちに引数の中身が食い違っていたりして大変です。

そこでgulp-ng-annotateです!!
これを使えばgulpファイルの中でDIの文字列定義を自動で行ってくれるのです!!

var ngAnnotate = require('gulp-ng-annotate');

gulp.task('js', function() {
  return gulp.src('src/js/**/*.js')
    .pipe(ngAnnotate()) // ここを追加
    .pipe(tap(function(file) {
        file.contents = Buffer.concat([
            new Buffer("(function(){\n"),
            new Buffer("'use strict';\n"),
            file.contents,
            new Buffer("})();")
        ]);
    }))
    .pipe(concat('build.js'))
    .pipe(gulp.dest('www/js/'));
});

これでjsファイルの方は引数にだけDIを書くだけで大丈夫になります!
AngularJSとgulpを使用するなら是非使いたいですね。

最終形態

  • DIのminify対策して、
  • 即時関数で囲ってuse strictつけて、
  • 1ファイルにまとめてそれだけ読み込む
gulp.task('watch', function() {
  gulp.watch('src/js/**/*.js', ['js']);
});

gulp.task('js', function() {
  return gulp.src('src/js/**/*.js')
    .pipe(ngAnnotate())
    .pipe(tap(function(file) {
        file.contents = Buffer.concat([
            new Buffer("(function(){\n"),
            new Buffer("'use strict';\n"),
            file.contents,
            new Buffer("})();")
        ]);
    }))
    .pipe(concat('build.js'))
    .pipe(gulp.dest('www/js/'));
});

いろいろな手間がはぶけました!