gulp-tscとgulp-typescriptの利用方法の違いについて

gulpでTypeScriptをコンパイルするプラグインはいくつか種類があるようですが、今のところ自分は[gulp-tsc](https://www.npmjs.org/package/gulp-tsc)と[gulp-typescript](https://www.npmjs.org/package/gulp-typescript)の2つを試しながら使っています。

両方使ってみると分かるのですが、**利用方法にかなり違いがある**のでお好みに合わせて選ぶと良いのではと思っています。
今回はそんな2つのプラグインの利用方法の違いについて説明します。

<br>
と、その前に。

書いている途中で気づいたのですが、今現在のgulp-tscは最新のTypeScriptを利用すると上手く動作しない場合があるようです。
GitHubの[Issues](https://github.com/kotas/gulp-tsc/issues)でも報告されていて[Pull Requests](https://github.com/kotas/gulp-tsc/pulls)にPRがたまっているので、これらを参考にしながら自分で修正すれば直るかもしれませんがまだ試せていません。

自分の環境だと`/// <reference path="...">`の指定がコンパイラに正しく解釈されずにコンパイルエラーが発生してしまう場合がありました。
TypeScript 1.0だと問題なく、1.1以降では駄目みたいですね。

gulp-tscはしばらく更新されてないのですが、将来的に修正される可能性もあるかもしれないので（作者が日本の方なので贔屓にしたいという思いもありつつ）、今のところ利用は続けています。
gulp-tscが上手く動作しないという方はgulp-typescriptを利用するか、TypeScriptのバージョンを1.0にして使ってもらえればと思います。

<br>

[2014/12/22 追記]
gulp-tscがgulpのブラックリスト入りしてる件に編集リクエスト頂いたので記事中に反映しています。
すっかりその存在を忘れていましたが、、、[ガイドライン](https://github.com/gulpjs/gulp/blob/master/docs/writing-a-plugin/guidelines.md)([日本語翻訳](http://qiita.com/morou/items/abc05013053f6110bcb1))の基準を満たしていないと**非推奨**となり[gulpプラグイン一覧](http://gulpjs.com/plugins/)から弾かれたりするみたいです。
なので、利用に関しては上記を理解した上で自己判断頂ければと思います。
<br>


## ざっくり比較

### gulp-tsc

* [2014/12/22 追記] [ブラックリストに登録されている](https://github.com/gulpjs/plugins/pull/125)
* シンプルで使いやすい
* `--out`オプションに対応
* 型定義ファイル(.d.ts)やソースマップファイル(.js.map)はそのままストリームに含まれて返ってくる
* 作者が日本人の方

### gulp-typescript

* 使い方は少し複雑
* 差分（変更のあったTSファイルのみ）コンパイルに対応しているので状況しだいで処理が高速に
* `--out`オプションはあえて（？）非対応、代替手段あり
* JSファイルと型定義ファイルのストリームが分かれる
* ソースマップはgulp-sourcemapsやgulp-concat-sourcemapと連携して別で生成する事を想定

他にも試していない機能はありますが、知ってる範囲でざっくりと比較するとこんな感じです。
感触としては、**初心者向けのgulp-tsc、上級者向けのgulp-typescript**といった所でしょうか。

ボリュームと時間の都合上、型定義ファイルやソースマップについて触れるのは断念しましたが、
今回はそれぞれのプラグインを使って、TSファイルからJSファイルを生成する基本的な実装パターンを説明していきたいと思います。

<br>

## プロジェクトのフォルダ構成

まずはプロジェクトのフォルダ構成を先に示しておきます。

```
├─ gulp/    - gulpの作業フォルダ
|  ├ node_modules/
|  ├ gulpfile.js
|  └ package.json
|
├─ src/     - ソースフォルダ
|  └─ ts/     - 編集用のTSファイルが入る
|
└─ release/ - 出力フォルダ(Webサーバのドキュメントルートを想定)
   └─ js/     - JSファイルを出力
```

ソースの入っている`src`フォルダと、コンパイル後のファイルを出力する`release`フォルダを完全に切り分けています。
また、`release`フォルダはローカルで立てるWebサーバのドキュメントルートにもなる事を想定しています。

<br>

## gulp-tscの実装パターン

### 複数のTSファイルをひとつのJSファイルにまとめてコンパイル

```js:gulpfile.js
var tsc = require('gulp-tsc');

gulp.task('typescript-compile', function(){
  // 基点となるファイルのみ指定
  gulp.src(['../src/ts/Main.ts'])
    // --outオプションでひとまとめにコンパイル
    .pipe(tsc({ target: "ES5", removeComments: true, out: "main.js" }))
    .pipe(gulp.dest('../release/js/'));
});
```

これは一番よく使うパターンですが、クラスごとにTSファイルを分け、コンパイル時にひとつのJSファイルにまとめるという事をしています。

gulp-tscは`--out`オプションに対応しているため、`Main.ts`ファイルで`/// <reference path="..." />`の形式で他のファイルを参照しておけば再帰的に参照を辿っていって、**必要なTSファイルを自動でコンパイルに含んでくれます。**
この方法であれば、**フォルダ内に使わなくなったTSファイルがあったとしても参照を切っておけばコンパイル対象に含まれなくなる**ので便利だと思います。

ちなみに、gulp-tscに渡すオプションで`removeComments: true`を指定するとコメントを取り除いてくれます。
また、[gulp-uglify](https://www.npmjs.org/package/gulp-uglify)を使えばコメント除去も含めてJSファイルを圧縮してくれるのでオススメです。


### TSファイルとJSファイルを1対1でコンパイル

```js:gulpfile.js
var tsc = require('gulp-tsc');

gulp.task('typescript-compile', function(){
  // 対象となるファイルを全部指定
  gulp.src(['../src/ts/**/**.ts'])
    // 1対1でコンパイル
    .pipe(tsc({ target: "ES5", removeComments: false }))
    .pipe(gulp.dest('../release/js/'));
});
```

フォルダ内のTSファイルをそれぞれJSファイルに1対1でコンパイルする場合は、上記のような方法になるかと思います。

`gulp.src()`に渡したワイルドカードの部分は出力先でも維持されるので、**階層構造を保ったまま`src/ts/`の中のTSファイルを`release/js/`にJSファイルとして出力**することができます。

このパターンは[yuidoc](http://yui.github.io/yuidoc/)でドキュメントを生成したい場合に使っています。
コメントを残したままJSファイルに1対1でコンパイルし、yuidocを使ってドキュメント化に利用するようにしています。
yuidocはTypeScriptファイルから直接ドキュメント化できないようなので、この方法がお気に入りです。

<br>

gulp-tscの実装パターンは以上です。
とてもシンプルな使い方になっていると思います。

続いて、同じパターンをgulp-typescriptで実装してみます。

<br>

## gulp-typescriptの実装パターン

### 複数のTSファイルをひとつのJSファイルにまとめてコンパイル
```js:gulpfile.js
var typescript = require('gulp-typescript');
var concat = require('gulp-concat');

gulp.task('typescript-compile', function(){
  // 対象となるファイルを全部指定
  gulp.src(['../src/ts/**/*.ts'])
    .pipe(typescript({ target: "ES5", removeComments: true, sortOutput: true }))
    // jsプロパティを参照
    .js 
    // ファイルをひとまとめに
    .pipe(concat("main.js"))
    .pipe(gulp.dest('../release/js/'));
});
```
gulp-typescriptは`--out`オプションに対応していないため、`gulp.src()`では**globを使って必要なファイルを全部読み込む**ようにします。

併せて、gulp-typescriptに渡すオプションで`sortOutput: true`を指定すると、**ファイル同士の参照関係を元にコンパイル後のファイルの順番をソート**してくれます。
(ただ、自分が検証した限りでは指定が無くてもソートしている気がしています…。)

コンパイルが終わったら後続の処理に移りますが、ここで1つ注意点があります。
**コンパイル後のJSファイルのストリームは`js`プロパティに格納される**ので、そのプロパティに対して`pipe()`を実行しないといけません。
ちなみに、型定義ファイルを生成した場合は`dts`プロパティにストリームが格納されるようになっています。

最後に、JSファイルが分かれたままになっているので**[gulp-concat](https://www.npmjs.org/package/gulp-concat)を使ってファイルを結合**してから、`gulp.dest()`でファイルを書き出します。

<br>

ここで1つ問題なのが、gulp-tscの場合と違ってこのままでは**実際には使ってないTSファイルもコンパイルの対象になってしまう**事です。
不必要なファイルをglobの指定で逐一外す事も出来なくないですが、それだとかなり面倒になってしまいます。

この問題の解決方法は後で説明したいと思います。



### TSファイルとJSファイルを1対1でコンパイル
```js:gulpfile.js
var typescript = require('gulp-typescript');

gulp.task('typescript-compile', function(){
  // 対象となるファイルを全部指定
  gulp.src(['../src/ts/**/*.ts'])
    // 1対1でコンパイル
    .pipe(typescript({ target: "ES5", removeComments: true, noExternalResolve: true }))
    // jsプロパティを参照
    .js
    .pipe(gulp.dest('../release/js/'));
});
```

このパターンはgulp-tscとあまり変わらないですね。

ちなみに、オプションで`noExternalResolve: true`を指定すると、渡したファイル同士の参照関係を無視するのでコンパイルが早くなるそうです。
1対1のコンパイルなので、各TSファイルをそのままJSファイルに変換したい場合はこのオプションを使っても良いかもしれません。
ただし、足りないファイルがあってもコンパイルエラーにならないので注意が必要です。



### 差分コンパイル

ここからはgulp-typescript独自の機能について説明します。
以下は、最初の「複数のTSファイルをひとつのJSファイルにまとめてコンパイル」のパターンを差分コンパイルで実装しています。

```js:gulpfile.js
var typescript = require('gulp-typescript');
var concat = require('gulp-concat');

// オプションを渡して事前にプロジェクトを作成
var typescriptProject = typescript.createProject({
  target: "ES5", 
  removeComments: true, 
  sortOutput: true
});

gulp.task('typescript-compile', function(){
  // 対象となるファイルを全部指定
  gulp.src(['../src/ts/**/*.ts'])
    // プロジェクトを渡す事で差分コンパイル
    .pipe(typescript(typescriptProject))
    // jsプロパティを利用
    .js
    // ファイルをひとまとめに
    .pipe(concat("main.js"))
    .pipe(gulp.dest('../release/js/'));
});
```

差分コンパイルを行う場合は、`typescript.createProject()`を事前に呼び出してプロジェクトと呼ばれるオブジェクトを生成しておきます。

そして、このプロジェクトをコンパイル時に渡す事で、**コンパイル済みのファイルがプロジェクトにキャッシュされて、更新のあったファイルのみがコンパイルされる仕組み**になるようです。

速度面の比較などはまだ出来てないのですが、ファイル数が膨大になってくるほど恩恵が大きくなると思っています。



### 差分コンパイル＋必要なファイルのみを抽出

```js:gulpfile.js
var typescript = require('gulp-typescript');
var concat = require('gulp-concat');

// オプションを渡して事前にプロジェクトを作成
var typescriptProject = typescript.createProject({
  target: "ES5", 
  removeComments: true, 
  sortOutput: true
});

gulp.task('typescript-compile', function(){
  // 対象となるファイルを全部指定
  gulp.src(['../src/ts/**/*.ts'])
    // プロジェクトと基点となるファイル名を渡して差分コンパイル
    .pipe(typescript(typescriptProject, {referencedFrom: ['Main.ts']}))
    // jsプロパティを利用
    .js
    // ファイルをひとまとめに
    .pipe(concat("main.js"))
    .pipe(gulp.dest('../release/js/'));
});
```

先ほど、実際には使ってないTSファイルまでがコンパイルの対象になってしまうと説明しました。
しかし、プロジェクトを使用するとこの問題を解決できます。

コンパイル時の2つ目の引数で`{referencedFrom: ['Main.ts']}`というオブジェクトを渡す事で、**指定したTSファイルから再帰的に参照を辿って見つかったファイルのみが出力**されるようになります。
（配列なので、複数指定することもできます。）

ここで注意が必要なのは**ファイルパスではなくファイル名となっている**点です。
ここでは`Main.ts`を指定していますが、一致するファイルは`../src/ts/Main.ts`を想定しています。
もし仮に`Main.ts`という名前のファイルが他のフォルダにもあるとそっちが対象となってしまう可能性があります。
（おそらく最初に見つかったファイルが1つ対象になると思われます。）

若干癖はありますが、結果的に`--out`オプションを使った場合と同じような結果を得ることができました。

<br>

## まとめ
2つのプラグインの利用方法を説明しましたが、今の所はどちらを使ってもやりたい事は実現できています。

ここからさらに型定義ファイルやソースマップを出力する場合にも様々な違いが出てきますが、それについてはまた機会があれば説明したいと思います。

ではでは。