All Articles

gulp+TypeScript+Browserify+mochaの環境を作る

gulp+TypeScript+browserify+mochaの開発環境を作る

ここ最近はずっと静的型付言語ばかり触っていて、ちょっとJavaScriptを書こうと思った時にもやっぱり型システムほしいなーってことで、TypeScriptを始めたメモです。最近のフロントエンド関連のはやりはあんまりよくわかってないですはい。

インストール

npmから入る。

$ npm instal -g typescript
$ tsc -v
>> message TS6029: Version 1.4.1.0

tscコマンドが有効になる。

複数ファイル・モジュールとimport関連でハマる

ちゃんと理解してないまま使ったのもいけないけど、モジュールとimport周りで相当ハマりました。

これさえやれば大丈夫! TypeScriptのImportが取っ付きにくい人向け -Qiita TypeScriptのimport指定方法 -Qiita TypeScriptでState Monadを実装する -Qiita

だいたいこの辺見てました。なお、今回はとある機能ライブラリを作ろうとしてて、

  • 実装コードはTypeScript、CommonJSスタイルでビルドする
  • テストはmocha(node)でやりたい
  • 提供コードはブラウザ向け
  • ↑はgulpでタスク化する

という感じです。まずTypeScriptのモジュールは内部モジュールと外部モジュールがあるようで、これらになるにはいくつかの条件があるようです。 これを理解するのに時間がかかってしまったんですが、内部モジュールはいわゆるで記載されるようなもので、exportとかしないと内部モジュールとして扱われるような感じです。 逆にexportしてrequireできるようにするのは外部モジュールということになるみたい。この辺りは以下のエントリが詳しい。

TypeScriptで複数ファイル構成する2つの方法

例えば、「モジュールSomeModuleのクラスAが同じモジュール内のクラスBに依存している」ケースがあったとして、AからBをrefenreceで参照するんですが、この時Bは内部モジュールとして扱のが良さそうです。具体的にはこんなコード:

// A.ts
/// <reference path="B.ts" />

module SomeModule {
    export class A {
        b: B;
        constructor() {
            this.b = new B("bar");
        }
    }
}
// B.ts
module SomeModule {
    export class B {
        constructor(private foo: string) {
        }
    }
}

これはtsc A.ts B.ts --out main.jsでコンパイルが通ります。

// main.js
var SomeModule;
(function (SomeModule) {
    var B = (function () {
        function B(foo) {
            this.foo = foo;
        }
        return B;
    })();
    SomeModule.B = B;
})(SomeModule || (SomeModule = {}));
/// <reference path="B.ts" />
var SomeModule;
(function (SomeModule) {
    var A = (function () {
        function A() {
            this.b = new SomeModule.B("bar");
        }
        return A;
    })();
    SomeModule.A = A;
})(SomeModule || (SomeModule = {}));

ですが、このクラスBに対してnodeからテストが書けないんですよね…。exportsすると外部モジュールになってしまいます。まぁSomeModuleに対して単体テストを書けばいい話だとは思うのですが、Bの機能だけをテストしたいときに困りました。そもそもこういう設計はどうなんだろう。

外部モジュール化、CommonJSスタイルにして解決

nodeからrequireでロードしてテストできるようにするため外部モジュールにしてしまいました。

// A.ts
import modB = require("./B");

module SomeModule {
    export class A {
        b: modB.B;
        constructor() {
            this.b = new modB.B("bar");
        }
    }
}
// B.ts
module modB {
    export class B {
        constructor(private foo: string) {
        }
    }
}
// module.exports = modBと等価
export = modB;

これはモジュールに分割されるので、それぞれのファイルにコンパイルされます。privateクラスなんてなかった。

$ tsc A.ts --module commonjs

var modB = require("./B");
var SomeModule;
(function (SomeModule) {
    var A = (function () {
        function A() {
            this.b = new modB.B("bar");
        }
        return A;
    })();
    SomeModule.A = A;
})(SomeModule || (SomeModule = {}));
$ tsv B.ts --module commonjs

var modB;
(function (modB) {
    var B = (function () {
        function B(foo) {
            this.foo = foo;
        }
        return B;
    })();
    modB.B = B;
})(modB || (modB = {}));
module.exports = modB;

これで別々のモジュールになったので、それぞれにテストが書けるようになりました。が、これだと結合させないといけないですね。

結合とブラウザ向けビルド

テストが書ければあとは結合してブラウザ向けに提供すればOKです。ブラウザ向けにはbrowserifyを使えばいけそうです。この辺りはgulpで一括処理するように。

var gulp = require("gulp");
var typescript = require("gulp-typescript");
var uglify = require("gulp-uglify");
var browserify = require("browserify");
var source = require("vinyl-source-stream");
var buffer = require("vinyl-buffer");

// TypeScript compile
gulp.task("typescript", function() {
    gulp.src(["./src/*.ts"])
        .pipe(typescript({target:"ES5", sortOutoput:true, module:"commonjs"}))
        .pipe(gulp.dest("./build/scripts"));
});

// Browserify concat
gulp.task("browserify", function() {
    browserify({
        entries: ["./build/scripts/a.js"]
    })
    .bundle()
    .pipe(source("lib.js"))
    .pipe(gulp.dest("./build/"));
});

// Browserify with minify
gulp.task("browserify-minify", function() {
    browserify({
        entries: ["./build/scripts/a.js"]
    })
    .bundle()
    .pipe(source("lib.min.js"))
    .pipe(buffer())
    .pipe(uglify())
    .pipe(gulp.dest("./build/"));
});

gulp.task("dev", ["typescript", "browserify"]);
gulp.task("build", ["typescript", "browserify-minify"]);
gulp.task("default", ["dev"]);

依存npmはファイル先頭の通りです。これでsrc/以下の.tsファイルがそれぞれコンパイルされてbuild/scrpts/に生成されるので、これらに対してテストを書けばいいですし、browserifyでbuild/に成果物が生成されます。ひとまずこんな環境で落ちついた感じです。

まとめ

色々勝手がわからなくて手間取ってしまったので、一冊書籍か購入しようと思います…。が、enumとか色々機能があるのがやっぱり嬉しいです。素のJSも書けますしね。

なお、mochaのテスト自体もTypeScriptで書くTipsなんかもありましたが、実行されるのはコンパイル後のJSなので、テスト自体はTypeScriptで書かなくていいんじゃないかなぁって思ってます(mocha.d.tsを参照したり色々面倒ですし)。 まだまだ使いこなせてない感があるけどメモ書き程度に。参考になれば幸いです。ツッコミあれば是非お願いしたいです。