TypeScriptのビルドからテスト・カバレッジレポート, そしてgulpでconnect+watch

  • 12
    いいね
  • 0
    コメント

概要

はじめに

jQueryを使ってセコセコとクライアントサイドのJavaScriptを書いていた時代からすごい変化しています。浦島太郎状態から脱却するために、最近の動向に沿った開発ができるように環境構築してみました。
TypeScriptでコードを書いてユニットテスト・カバレッジレポート作成を行いつつ開発を進められるようにします。
まずは、各ステップの操作を手動で確認したあと、gulpで自動的に行えるようにします。

目標

  1. コード本体はTypeScriptで書く,ユニットテストも書く
  2. TypeScriptをECMAScript5のCommonJSにコンパイル
  3. 生成されたJavaScriptをユニットテストする、カバレッジも出す。
  4. webpackでまとめる。
  5. ファイルを更新したら自動ビルドする。

以上の操作をgulpで一括して行いたいと思います。

より多くのブラウザを対象にするためにECMAScript5(IE9以上対応)を使用します。

やってみた

前提

  • npmは使える状態のであるとします。npmでTypeScriptのコンパイラをインストールします。
  • グローバルでインストールしていない場合、node_modules/.bin以下に存在します。明記はしませんがコマンドのパスに注意してください。

TypeScriptからの手動操作

gitリポジトリの初期化

$ git init ts-smaple
$ cd ts-sample

初期化

npmや各コマンドの初期化処理を実行します。

$ npm init
$ npm install typescript typings --save-dev
$ tsc --init
$ typings init
$ typings install

ディレクトリ構成は最終的に次のようにします。

ディレクトリ構成
ts-sample/
├── README.md
├── gulpfile.js
├── htdocs
│   └── index.html
├── node_modules
│   ├── @types
│   ├── 
│
├── package.json
├── src
│   ├── Person.ts
│   └── main.ts
├── test
│   └── PersonTest.ts
├── tsconfig.json
└── typings.json

tscofing.jsonの記述

TyepScriptのコンパイルオプションであるtsconfig.jsonを記述します。

tsconfig.json
{
    "compilerOptions": {
        "module": "commonjs",
        "target": "es5",
        "noImplicitAny": true,
        "sourceMap": true
    },
    "files":[
        "typings/index.d.ts",
        "src/main.ts"
    ],
    "exclude": [
        "node_modules/"
    ]    
}

CommonJS形式のECMAScript5を出力するように設定しています。

tsconfig.jsonは公式サイトの以下を参考にします。

コンパイラオプションは日本語翻訳された方いらっしゃいました。

かんたんなTypeScriptを記述

今回はサンプルとして簡単なTypeScriptを書きました。

ありがちな内容ですが、Personクラスを定義し、main.tsから呼び出して、jQueryを介してbodyに書き込みます。

src/Person.ts
export class Person {    
    readonly id: number;
    readonly name: string;    

    constructor(_id: number, _name: string) {
        this.id = _id;
        this.name = _name;        
    }
}
src/main.ts
import {Person} from './Person';
import * as $ from 'jquery';

var person: Person = new Person(1, "yoko");
$(() => {
    $('body').html('彼は' + person.name + 'です。');
});

さてこのコードをビルドするためにjQueryが必要なのでインストールします。
jQuery本体とjQueryの型定義を指定します。(TypeScriptのコンパイル自体は@types/jqueryだけでOKですが。)

$ npm install jquery @types/jquery --save-dev

この段階でTypeScriptからJavaScriptへのコンパイルであればできるかと思います。

$ tsc

ユニットテストの記述

先ほどのPersonクラスのテストを書きます。
今回はテストフレームワークとしてmochaを使います。

まずは依存させるパッケージをインストールします。

$ npm install mocha @types/mocha --save-dev
$ npm install power-assert @types/power-assert --save-dev

次のテストコードを書きます。コンストラクタのアサーションテストです。

test/Person.ts
import * as assert from 'power-assert';
import {Person} from '../src/Person';

describe('PersonTest', () => {
    it('コンストラクタのテスト', () => {
        const person:Person = new Person(10, 'hiroyky');
        assert.equal(person.id, 10);
        assert.equal(person.name, 'hiroyky');
    });
});

これをJavaScriptにコンパイルして、mochaでユニットテストを実行します。最初なので手動でやります。

$ tsc
$ mocha test/*.js


  PersonTest
    ✓ コンストラクタのテスト


  1 passing (8ms)

さて、tscでコンパイルしてからmochaを実行しました。
これを次のようにすることで同時に実行します。また、jsファイルは生成されません。
後で登場するwebpackで1つのjsファイルのみを生成物とするため、途中のjsは邪魔なので生成しないようにします。

$ npm install ts-node --save-dev
$ mocha --compilers ts:ts-node/register test/**/*.ts

また、package.jsonのtestフィールドにテストコマンドを登録しておくことでnpm testとするだけでテストが実行されます。

"scripts": { 
    "test": "mocha --compilers ts:ts-node/register test/**/*.ts"
 },

カバレッジレポートの作成

せっかくなのでユニットテストのカバレッジレポートを生成してみましょう。

カバレッジレポートの作成にistanbulを使います。早速インストール。

$ npm install istanbul --save-dev

次のコマンドでテストとカバレッジレポート作成を行います。

$ istanbul cover _mocha

jsファイルに対してテストが実施され、coverageディレクトリ以下にレポートが作成されているはずです。

Screenshot from 2016-12-29 17-23-29.png

istanbul helpistanbul help coverなどとしてヘルプを見ることができます。

$ istanbul help cover                                                                                                                                                                                                                     
Usage: istanbul cover [<options>] <executable-js-file-or-command> [-- <arguments-to-jsfile>]

先ほどのコマンドはinstanbul coverでテストランナに_mochaを指定しています。mochaは``node_modules/.bin/mocha``にあります。

と言っても、これはTypeScriptをコンパイルしたJavaScriptのカバレッジレポートです。次の手順でTypeScriptのレポートを作成します。

$ (sudo) npm install remap-istanbul --global
$ cd ./coverage
$ remap-istanbul -i coverage.json -o lcov-report -t html 

webpackでjsファイルを一つにまとめる

さて、TypeScriptから生成されたJavaScriptは複数個存在し、requireでインポートしています。ご存知の通りWebブラウザ側のJavaScriptでは外部ファイルをインポートする機能はありませんので、webpackを使って、予め1つにまとめたjsファイルを作成し、配布します。
他にAMDというscriptタグを動的に生成するとこによってインポートする方法もあるようですがここでは行いません。(CPUメーカのAMDではありません。)

まずはwebpackをインストールします。

$ npm install webpack --save-dev
$ (sudo) npm install webpack --global

早速コンパイルで生成されたjsを一つにまとめてみましょう。すべてをまとめたjsファイルbundle.jsをhtdocsディレクトリ以下に生成します。
bundle.jsはPerson.jsだけでなくjQueryなど外部のライブラリも含んでいます。

$ webpack src/main.js htdocs/bundle.js

ここで、webpackの設定ファイルを記述することで、単にwebpackとコマンド実行するだけで良くなります。
ファイル名はwebpack.config.jsです。

webpack.config.js
module.exports = {
    entry: "./src/main.js",
    output: {
        path: __dirname/build,
        filename: "bundle.js"
    }
};

更に、以下のようにすることでtsファイルを直接1つのjsファイルにまとめることができます。ts-loaderをインストールします。

また、devtool: 'source-map'を指定してすることで、デバッグ時にデバッグツールが元のTypeScriptのコードを参照できるようにしています。これが無いと不便なのでデバッグ時はつけておきます。

webpack.config.js
var path = require('path');
module.exports = {
    entry: './src/main.ts',
    output: {
        filename: './bundle.js'
    },
    devtool: 'source-map',
    resolve: {
        root:[path.join(__dirname,'node_modules')],
        extensions: ['', '.webpack.js', '.web.js', '.ts', '.tsx', '.js']
    },
    module: {
        loaders: [
            { test: /\.ts$/, loader: 'ts-loader' }
        ]
    }
};
$ npm install ts-loader --save-dev
$ webpack

index.htmlを書く

webpackで生成されたjsを読み込むような簡単なhtmlを作成しておきます。
ビルドで生成されるbundle.jsを読み込むようにしています。

<!DOCTYPE html>
<html>
 <head>
  <meta charset="utf-8">
  <script src="bundle.js"></script>
 </head>
 <body>
 </body>
</html>

gulpの導入

ここまでは、TypeScriptのコンパイル, ユニットテスト,カバレッジレポート生成, webpackをそれぞれ行ってきました。
ここからは、これらの操作をgulpで一括して実行できるようにしつつ、開発用サーバをgulpで準備していきます。

gulpのインストール

まずはgulpをインストールします。これはグローバルでもインストールしておくと良いでしょう。

$ npm install "git://github.com/gulpjs/gulp.git#4.0" --save-dev
$ (sudo) npm install "git://github.com/gulpjs/gulp.git#4.0" --global

gulpfile.jsの作成

gulpfile.jsを作成します。gulpfile.jsの書き方は公式のドキュメントに譲るとして、以下のように記述しました。

gulpfile.js
const gulp = require('gulp');
const mocha = require('gulp-mocha');
const typescript = require('ts-node/register');
const webpack = require('gulp-webpack');
const webpackConfig = require('./webpack.config.js');

/** TypeScriptのコンパイル, webpackを実行 */
gulp.task('build', (done) => {
    gulp.src(['./src/*.ts'])
        .pipe(webpack(webpackConfig))
        .pipe(gulp.dest('./htdocs'));
    done();
});

/** ユニットテストの実行 */
gulp.task('test', (done) => {
    gulp.src(['./test/**/*.ts'])
        .pipe(mocha({
            'compilers': {
                ts: typescript
            }
        }))
        .once('error', () => {
            process.exit(1);
        })
    done();
});

gulp.task('default', gulp.series(
    'test',
    'build'
));

これでgulp testgulp buildとすればそれぞれユニットテスト, ビルドが実行されます。
また、defualtのtaskにあるように

$ gulp

とすればユニットテストとビルドが一括して行われます。ユニットテストに失敗したらそこで止まります。

gulpでwebサーバを作成

静的ファイルのみのページの動作検証を行うためにサーバを用意します。本来Apacheでもnginxでも良いのですが、ここではgulp-connectを使ってサクっと開発webサーバを立ててみます。

$ npm install gulp-connect --save-dev

gulpfile.jsに以下のタスクを追記します。

gulpfile.js
const connect = require('gulp-connect');

gulp.task('server', (done) => {
    connect.server({
        root: [__dirname + '/htdocs']
    });
    done();
});

gulp.task('default', gulp.series(
    'test',
    'build',
    'server' // 追記
));

gulp serverと実行することでWebサーバが起動します。ルートディレクトリはhtdocsと設定したため先ほど作成したindex.htmlが開かれます。
またdefaultタスクにもserverを追加したため、単にgulpと実行するだけでサーバの起動までが行われるうようになりました。

$gulp server                                                                             [] Starting 'server'...
[] Finished 'server' after 23 ms
[] Server started http://localhost:8080

早速、http://localhost:8080 にブラウザでアクセスして確認してみましょう。

ファイルの更新検知とリビルド

tsファイルなどの更新を監視して、更新があったらリビルドするようにします。ctrl+sなどで保存する度にビルドが実行されて便利です。

gulpfile.jsに次のタスクを追記します。

gulpfile.js
gulp.task('watch', (done) => {
    gulp.watch(['./src/**/*.ts', './test/**/*.ts'], gulp.series(
        'test',
        'build'
    ));
    done();
});

gulp.task('default', gulp.series(
    'test',
    'build',
    'server',
    'watch'  // 追記
));

gulpと実行するとサーバが起動しますが、その後tsファイルなどを更新するとビルド処理が自動的に実行されます。

gulpでカバレッジレポート

TypeScriptのコードをカバレッジレポートするには、gulpfileを次のように記述します。
tsファイルをjsファイルにsourcemap付きコンパイル、jsファイルでカバレッジレポートを生成したあと、remap-istanbulでTypeScriptのカバレッジレポートに変換する一連の作業をtask('coverage')で行っています。

const sourcemaps = require('gulp-sourcemaps');
const istanbul = require('gulp-istanbul');
const remapIstanbul = require('remap-istanbul/lib/gulpRemapIstanbul');

gulp.task('compile', (done) => {
    const options = {
        module: "commonjs",
        target: "es5",
        noImplicitAny: true,
        sourceMap: true
    };

    gulp.src(['./**/*.ts', '!node_modules/**'])
        .pipe(sourcemaps.init())
        .pipe(typescript(options))
        .js.pipe(sourcemaps.write('./'))
        .pipe(gulp.dest('./build/'))
        .on('end', done);    
});

gulp.task('coverage', gulp.series(
    'compile',
    (done) => {
        gulp.src('./build/src/**/*.js')
            .pipe(istanbul())
            .pipe(istanbul.hookRequire());
        done();
    },
    (done) => {
        gulp.src('./build/test/**/*.js')
            .pipe(mocha())
            .pipe(istanbul.writeReports())
            .on('end', done);
    },
    (done) => {
        gulp.src('./coverage/coverage-final.json')
            .pipe(remapIstanbul({
                reports: {
                'json': 'coverage/coverage.json',
                'html': 'coverage/html-report'
                }
            }))
        done();
    }));

defaultタスクの作成

defaultタスクはユニットテストとビルドを実行したあと、サーバを立ち上げてwatchするようにします。流石に毎回カバレッジレポートを作成する必要はないだろうと思い、coverageタスクは入れてないです。

gulp.task('default', gulp.series(
    'test',
    'build',
    'server',
    'watch'
));

まとめ

TypeScriptでコードを書いてビルド・テスト、デバッグするための環境を準備するところを振り返りました。一度構築すれば、今後はgulpと実行するだけでOKになります。

リポジトリ

今回作成したコードは

にアップロードしました。

参考文献

いずれも2016年12月29日現在