Gulp で TypeScript をコンパイル & tsify で連結 - Gulp で作る Web フロントエンド開発環境 #4

TypeScript とは

TypeScript はマイクロソフトによって開発されたフリーでオープンソースのプログラミング言語である。TypeScript は JavaScript に使用するかどうかが任意の静的型付けとクラスベースオブジェクト指向を加えたスーパーセットとなっている。C# のリードアーキテクトであるアンダース・ヘルスバーグが TypeScript の開発に関わっている。

TypeScript - Wikipedia

logo-typescript

Alt JS として何かと CoffeeScript と比較されることの多い言語です。これまでは Rails 開発が主だったことからデフォルトで対応している CoffeeScript を使うことが多かったのですが、最近アサインされた案件は Rails ではなく Scala & Play による開発なので、同じ型推論を持つ TypeScript を使ってみようぜということになりました1)特に深い意味はありません。

サンプルコードはこちらからどうぞ。

TypeScript を Gulp 上でコンパイルしてみる

まずは TypeScript コンパイラをインストールします。

$ npm install -g typescript
$ tsc -v
message TS6029: Version 1.5.0-alpha

gulp-typescript をインストールします。

$ npm i -D gulp-typescript

適当に TypeScript ファイルを作成します。公式サイトにあるチュートリアルのコードをそのまま持って来ました。

class Student {
    fullname : string;
    constructor(public firstname, public middleinitial, public lastname) {
        this.fullname = `${firstname} ${middleinitial} ${lastname}`;
    }
}
interface Person {
    firstname: string;
    lastname: string;
}
function greeter(person : Person) {
    return `Hello, ${person.firstname} $person.lastname}`;
}
var user = new Student('Jane', 'M.', 'User');
document.body.innerHTML = greeter(user);

タスクを定義します。

var typescript = require('gulp-typescript');
gulp.task('ts', () => {
    return gulp.src('./src/typescripts/**/*.ts')
        .pipe(typescript({
            target: 'ES5',
            removeComments: true
        }))
        .js.pipe(gulp.dest('./dest/assets/js'));
});

いくつかのコンパイルオプションを指定してみました。

  • target
    • ECMAScript 構文を指定。デフォルトはES3で、ES5, ES6が選択可能
  • removeComments
    • コメントを削除するかどうかを指定します。デフォルトはfalse

こちらのサイトにて全てのオプションを確認することが出来ます。

実行してみます。

$ gulp ts
[11:37:09] Using gulpfile ~/Documents/sandbox/try_gulp/try_typescript/gulpfile.js
[11:37:09] Starting 'ts'...
[11:37:10] Finished 'ts' after 1.23 s

生成された JavaScript がこちら。

var Student = (function () {
    function Student(firstname, middleinitial, lastname) {
        this.firstname = firstname;
        this.middleinitial = middleinitial;
        this.lastname = lastname;
        this.fullname = firstname + " " + middleinitial + " " + lastname;
    }
    return Student;
})();
function greeter(person) {
    return "Hello, " + person.firstname + " " + person.lastname;
}
var user = new Student("Jane", "M.", "User");
document.body.innerHTML = greeter(user);

さすが TypeScript 。きれいなコードにコンパイルされました。

モジュール単位で分割したファイルを読み込む

TypeScript を取り入れる主な動機に、肥大化するコードをモジュール単位で分割して管理したいというのがあります。モジュールの定義方法はいくつかありますが、ここでは内部モジュールと外部モジュールの二種類について学ぶとします。

内部モジュール (Internal Module)

早い話が、名前空間を定義するというものです。これによってオブジェクトやクラスに境界線や階層を作ることができ、名前の衝突を防いだり構造が管理しやすくなります。

簡単なモジュールを作ってみます。

module app.util.strings {
  export function trimLeft(str: String): string {
    return str.replace(/^s+/, '');
  }
  export function trimRight(str: String): string {
    return str.replace(/s+$/, '');
  }
}

appという変数がグローバルスコープに追加されます。モジュールの内容は全てスコープ内に収まっており、デフォルトでは privateとなっているので通常は外部から参照出来ませんが、exportというキーワードをメンバの先頭につけることでpublicとなり、参照できるようになります。

また、モジュールは閉じないので内部モジュールを複数のファイルに分割して定義することも可能です。そのため各ファイルを管理しやすいサイズに保つことが出来ます。

モジュールを読み込むファイルを作ります。

/// <reference path='util/strings.ts' />
(function() {
  var foo = '     foo';
  var bar = 'bar      ';
  console.log(app.util.strings.trimLeft(foo));
  console.log(app.util.strings.trimRight(bar));
})();

TypeScript はコンパイル時に型情報が必要なので、trimLeft()関数の型情報が必要になります。そのため、コードの先頭に///から始まる reference タグを記述して、参照したい型情報のファイルパスを記述する。

ちなみにこのままコンパイルするとapplication.tsutil/strings.tsがそれぞれ別ファイルとしてコンパイルされるため、HTML側で両方とも読み込む必要があります。手間がかかる上にパフォーマンス的にもよろしくないので、1つのファイルに連結するのがセオリーです。タスクを以下のように定義しなおします。

gulp.task('ts', () => {
    return gulp.src('./src/typescripts/**/*.ts')
        .pipe(typescript({
            target: 'ES5',
            removeComments: true,
            out: 'app.js'
        }))
        .js
        .pipe(gulp.dest('./dest/assets/js'));
});

outオプションに生成する JS ファイル名を指定します。これにより、複数の TS ファイルが一つの JS ファイルに連結された状態で生成されます。連結された JS ファイルは reference タグで指定された順序で参照ファイルを読み込まれ、依存関係が適切に処理されます。前回の記事で紹介した Browserify の機能と同じですね。

コンパイル後の JS ファイルはこちら。

var app;
(function (app) {
    var util;
    (function (util) {
        var strings;
        (function (strings) {
            function trimLeft(str) {
                return str.replace(/^s+/, '');
            }
            strings.trimLeft = trimLeft;
            function trimRight(str) {
                return str.replace(/s+$/, '');
            }
            strings.trimRight = trimRight;
        })(strings = util.strings || (util.strings = {}));
    })(util = app.util || (app.util = {}));
})(app || (app = {}));
(function () {
    var foo = '     foo';
    var bar = 'bar      ';
    console.log(app.util.strings.trimLeft(foo));
    console.log(app.util.strings.trimRight(bar));
})();

比較的仕組みがシンプルで分かりやすいのが内部モジュールの特徴です。グローバル変数が一つ生成されてしまうのがデメリットですが、許容できるのであれば有用な手法です。

外部モジュール (External Module)

CommonJS や Asynchronous Module Definition (AMD) が定める仕様に従ってモジュールをエクスポート/インポートする仕組みです。

export function trimLeft(str: String): string {
  return str.replace(/^s+/, '');
}
export function trimRight(str: String): string {
  return str.replace(/s+$/, '');
}

外部モジュールを使用する場合は、モジュールはファイルによって表されます。したがってコードを module {} で囲む必要がなくなります。

外部モジュールを使用するには、 require関数の呼び出しに importキーワードを指定します。この箇所はコンパイル時にモジュールを読み込むコードに変換されます。

import strings = require('./util/strings');
var foo = '     foo';
var bar = 'bar      ';
console.log(strings.trimLeft(foo));
console.log(strings.trimRight(bar));

tsify - TypeScript をコンパイルするための Browserify プラグイン

Browserify によって JS ファイルの依存関係を解決 (適切な順序で JS ファイルを連結する) することが出来ますが、tsify はこれに TypeScript のコンパイル機能を追加することが出来るプラグインです。

Node パッケージをインストールします。

$ npm i -D tsify

タスクを定義します。

gulp.task('tsify', () => {
    return browserify()
        .add('./src/typescripts/external/application.ts')
        .plugin('tsify', {
            target: 'ES5',
            removeComments: true
        })
        .bundle()
        .pipe(source('app_external.js'))
        .pipe(gulp.dest('./dest/assets/js'));
});

tsify にも gulp-typescript のようにオプションを渡すことが出来ます。渡せるオプションは、tsc コマンドに渡すオプションと同じです。$ tsc -hで内容を確認できます。

実行してみます。

$ gulp tsify
[20:40:17] Using gulpfile ~/Documents/sandbox/try_gulp/try_typescript/gulpfile.js
[20:40:17] Starting 'tsify'...
[20:40:18] Finished 'tsify' after 1.01 s

コンパイル後の JS はこちら。

(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
var strings = require('./util/strings');
var foo = '     foo';
var bar = 'bar      ';
console.log(strings.trimLeft(foo));
console.log(strings.trimRight(bar));
},{"./util/strings":2}],2:[function(require,module,exports){
function trimLeft(str) {
    return str.replace(/^s+/, '');
}
exports.trimLeft = trimLeft;
function trimRight(str) {
    return str.replace(/s+$/, '');
}
exports.trimRight = trimRight;
},{}]},{},[1]);

こちらは内部モジュールと違って全体が関数スコープで囲われました。 tsify を使わずに Browserify だけでコンパイルすると define()関数の関係で require.js が必要になるみたいなので、このプラグインには助けられます。

これで TypeScript を学習するための環境が整いましたので、コツコツ勉強していきたいと思います。

脚注

脚注
1 特に深い意味はありません。