22
22

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Typescriptで書いたAngularJSのControllerをgulpで自動登録する

Posted at

Typescriptを使うとAngularJSのControllerをクラスとして開発することができる。クラスとしてきれいに開発できるのでTypescriptは気に入っている。

navbar.controller.ts
module b2 {

  class NavbarController {
    constructor ($scope: ng.IScope) {
      $scope.date = new Date();
    }
  }

  angular.module('b2')
  .controller('NavbarController',['$scope',NavbarController]);

}

でも、AngularJSへの登録コードは残ってしまう

AngularJSにクラスをコントローラーとして登録するコードを毎回書かなければならない。

  angular.module('b2')
  .controller('NavbarController',['$scope',NavbarController]);

せっかく、TypeScriptでクラスとして開発しているのに残念だ。

gulpのスクリプトでAngularJSへの登録コードを自動生成する

gulpでTypescriptをビルドするタイミングで、JavaScritpのASTを解析して、50行くらいのコードで実現できた。gulpはデータの流れが分かりやすいので、こういう処理を追加しやすい。

環境セットアップ

yo gulp-angular を実行しTypescriptを選び、gulp & AngularJS & Typescriptのプロジェクトを作る。

npm install --save-dev through2 falafel を実行し、JavaScriptnのASTを解析するライブラリーをインストールする。

gulp スクリプトの開発

コンパイル用のgulpのスクリプトファイルを編集していく。

gulp/scripts.js
'use strict';

var gulp = require('gulp');
var paths = gulp.paths;

var $ = require('gulp-load-plugins')();

gulp.task('scripts', function () {
  return gulp.src(paths.src + '/{app,components}/**/*.ts')
    .pipe($.typescript())
    .on('error', function handleError(err) {
      console.error(err.toString());
      this.emit('end');
    })
    .pipe(gulp.dest(paths.tmp + '/serve/'))
    .pipe($.size())
});

npmで取得したライブラリーを読み込む

through2とfalafelを読み込む。

'use strict';

var gulp = require('gulp');
// 追加したライブラリーを読み込む
var through = require('through2');
var falafel = require('falafel');
var paths = gulp.paths;

typescriptのコンパイル後に処理を挟み込む

angularifyメソッドをpipeで追加する。

gulp.task('scripts', function () {
  return gulp.src(paths.src + '/{app,components}/**/*.ts')
    .pipe($.typescript())
    .on('error', function handleError(err) {
      console.error(err.toString());
      this.emit('end');
    })
    .pipe(angularify({  /* 追加 */
      moduleName:'b2'   
    }))                 
    .pipe(gulp.dest(paths.tmp + '/serve/'))
    .pipe($.size())
});

JavaScriptにコンパイルされた後のファイルごとに処理をする

angularifyメソッドのなかでthroughを使うことでJavaScriptにコンパイルされた後のコードをファイルごとに操作することができる。

function angularify(opts) {

  var stream = through.obj(function(file, enc, cb) {
    if (file.isNull()) {
      // do nothing
    }

    if (file.isBuffer()) {
      // ファイルごとに中身を見て、変換することができる
      var contents = file.contents.toString();
      contents = transform(contents,opts.moduleName);
      file.contents = new Buffer(contents.toString());
    }

    this.push(file);

    return cb();
  });

  // returning the file stream
  return stream;
};

JavaScriptを解析し、AngularJSへの登録処理を追加する

falafelを利用しASTに変換されたJavaScriptを解析しソースコードを変換する。

function transform(contents,moduleName){
   // JavaScriptをASTに変換する
   return falafel(contents, {tolerant: true}, function (node) {
     registerControllerToModule(node,moduleName);
   }).toString();
}

function registerControllerToModule(node,moduleName){
 var decls, decl;
 // Typescriptのクラスは一定のルールでJavaScriptに変換されるので、
 // クラス宣言のパターンにマッチングして、コントローラーのクラス宣言に
 // ひっかける
 if (node.type === 'VariableDeclaration' &&
     (decls = node.declarations) && decls.length === 1 &&
     (decl = decls[0]) && decl.id.name.match(/.*Controller/)) {
     if(decl.init.type === 'CallExpression'){
       // コンストラクタのFunction
       var constructor = decl.init.callee.body.body[0];
       // コンストラクタの引数名
       var constructorParams = constructor.params.map(function(param){
         return '\''+param.name + '\'';
       });
    // コントローラー名
       var controllerName = decl.id.name;
       // コンストラクタの引数の後にFunctionを追加する
       constructorParams.push(controllerName);
		
       // AngularJSへの登録コードを組み立てる
       var source = '\n    ';
       source += 'angular.module(\''+ moduleName +'\')';
       source += '.controller(\''+controllerName+'\',['+constructorParams.join('\,')+']);';

       // クラス宣言の後にAngularJSへの登録コードを追加する
       node.update(node.source()+source);
     }
   return true;
 }
 return false;
}

gulp スクリプトの動作確認

AngularJSへの登録コードを削除したTypeScriptのクラスを用意する。

'use strict';

module b2 {

  interface INavbarScope extends ng.IScope {
    date: Date
  }

  class NavbarController {
    constructor ($scope: INavbarScope) {
      $scope.date = new Date();
    }
  }

}

gulp の実行後に生成されるファイルには、AngularJSへの登録コードが追加されている。

'use strict';
var b2;
(function (b2) {
    var NavbarController = (function () {
        function NavbarController($scope) {
            $scope.date = new Date();
        }
        return NavbarController;
    })();
    angular.module('b2').controller('NavbarController',['$scope',NavbarController]);
})(b2 || (b2 = {}));

作った gulp スクリプト

gulp/scripts.js
'use strict';

var gulp = require('gulp');
var through = require('through2');
var falafel = require('falafel');
var paths = gulp.paths;

var $ = require('gulp-load-plugins')();

gulp.task('scripts', function () {
  return gulp.src(paths.src + '/{app,components}/**/*.ts')
    .pipe($.typescript())
    .on('error', function handleError(err) {
      console.error(err.toString());
      this.emit('end');
    })
    .pipe(angularify({
      moduleName:'b2'
    }))
    .pipe(gulp.dest(paths.tmp + '/serve/'))
    .pipe($.size())
});

function angularify(opts) {

  // creating a stream through which each file will pass
  var stream = through.obj(function(file, enc, cb) {
    if (file.isNull()) {
      // do nothing
    }

    if (file.isBuffer()) {
      var contents = file.contents.toString();
      contents = transform(contents,opts.moduleName);
      // console.log(contents);
      file.contents = new Buffer(contents.toString());
    }

    this.push(file);

    return cb();
  });

  // returning the file stream
  return stream;
};

function transform(contents,moduleName){
   return falafel(contents, {tolerant: true}, function (node) {
     registerControllerToModule(node,moduleName);
   }).toString();
}

function registerControllerToModule(node,moduleName){
 var decls, decl;
 if (node.type === 'VariableDeclaration' &&
     (decls = node.declarations) && decls.length === 1 &&
     (decl = decls[0]) && decl.id.name.match(/.*Controller/)) {
     if(decl.init.type === 'CallExpression'){
       var constructor = decl.init.callee.body.body[0];
       var constructorParams = constructor.params.map(function(param){
         return '\''+param.name + '\'';
       });
       var controllerName = decl.id.name;
       constructorParams.push(controllerName);
       var source = '\n    ';
       source += 'angular.module(\''+ moduleName +'\')';
       source += '.controller(\''+controllerName+'\',['+constructorParams.join('\,')+']);';
       node.update(node.source()+source);
     }
   return true;
 }
 return false;
}
22
22
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
22

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?