ES6 で Angular アプリを書くのに使っている方法を書きます。(だいぶ前に書いて公開したつもりが、公開できてなかった・・・。)
コンパイル
ES6 で書いて ES5 にコンパイルします。ES6 module も使いたいので、コンパイルには browserify と 6to5ify を使っています。Traceur でなく 6to5 を使うのは、基本的には比較的自然なコードが出力されるのと、(ある程度までは)ランタイムなしで動くためです。
6to5 は Traceur と違ってコメントをいい感じに保持してくれるので ng-annotate と一緒に使えます(後で詳述)。
// Gulp
var gulp = require('gulp');
var source = require('vinyl-source-stream');
var buffer = require('vinyl-buffer');
// Browserify
var browserify = require('browserify');
var to5ify = require('6to5ify');
var ngAnnotate = require('broserify-ngannotate');
gulp.task('js:app', function() {
return browserify({ entries: 'app/app.js', debug: true })
.transform(to5ify)
.transform(ngAnnotate)
.bundle()
.pipe(source('app.js'))
.pipe(buffer())
.pipe(gulp.dest('public/js'));
});
ファイル構成
以下のような感じで、各ファイルで export default し、ディレクトリごとに module を作って集約し、それをさらに親の module で集約しています。
import foo from './foo/module';
import bar from './bar/module';
// `foo`, `bar` は `module` そのもの。`name` で名前がとれる。
angular.module('app', [
foo.name,
bar.name
]);
import AwesomeController from './awesome.controller';
import awesomeComponent from './awesome-component.directive';
export default angular.module('app.foo', [])
.controller('AwesomeController', AwesomeController)
.directive('awesomeComponent', awesomeComponent);
// @ngInject
export default class AwesomeController {
constructor(niceService, sweetService) {
this.niceService = niceService;
this.sweetService = sweetService;
}
doSomething() {
return this.niceService.doNiceThing()
.then((niceResult) => {
return this.sweetService.fetch(niceResult);
});
}
}
johnpapa/angularjs-styleguide と Ben Clinkinbeard による ng-conf 2014 での講演を参考にしています。
Angular 本体などのライブラリについては特に browserify を使う必要もないので、bower で管理して concat し、アプリのコードとは別ファイルにしています。
ng-annotate
DI のためのアノテーションをつけるのに ng-annotate を使います。
ng-annotate には現在 ES6 サポートがないため、ES5 にコンパイルした後に適用します。Traceur だとコメントを消してしまうけど、6to5 ならいい感じに残してくれるのでうまくいきます。
クラス
クラスの上にコメントを書くのがポイント。
// @ngInject
class AwesomeController {
constructor(niceService, sweetService) {
}
doSomething() {
}
}
以下のようにコンパイルされます。
// @ngInject
var AwesomeController = function AwesomeController(niceService, sweetService) {
};
AwesomeController.$inject = ['niceService', 'sweetService'];
AwesomeController.prototype.doSomething = function() {
};
コンストラクタの上に書くのは、うまくいきません。
class AwesomeController {
// @ngInject
constructor(niceService, sweetService) {
}
doSomething() {
}
}
以下のようにコンパイルされてしまいます。コンストラクタの prototype にメソッドが追加できていません。
var AwesomeController =
// @ngInject
['niceService', 'sweetService', function AwesomeController(niceService, sweetService) {
}];
AwesomeController.prototype.doSomething = function() {
};
Factory 関数
Factory, filter, directive などの factory 関数 はちょっと妙な書き方をする必要があります。
export default /* @ngInject */ function AwesomeFilter(Awesomizer) {
return function(str) {
return Awesomizer(str);
};
}
これはクラスと同様に上に書くと以下のようにコンパイルされてしまうためです。
// @ngInject
module.exports = AwesomeFilter;
function AwesomeFilter(Awesomizer) {
// ...
}
function の前に書けば以下のようになります。
module.exports = AwesomeFilter;
/* @ngInject */ function AwesomeFilter(Awesomizer) {
// ...
}
6to5 は良く出来てますね!