Edited at

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

More than 1 year has passed since last update.


概要


はじめに

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ディレクトリ以下にレポートが作成されているはずです。

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日現在