概要
はじめに
jQueryを使ってセコセコとクライアントサイドのJavaScriptを書いていた時代からすごい変化しています。浦島太郎状態から脱却するために、最近の動向に沿った開発ができるように環境構築してみました。
TypeScriptでコードを書いてユニットテスト・カバレッジレポート作成を行いつつ開発を進められるようにします。
まずは、各ステップの操作を手動で確認したあと、gulpで自動的に行えるようにします。
目標
- コード本体はTypeScriptで書く,ユニットテストも書く
- TypeScriptをECMAScript5のCommonJSにコンパイル
- 生成されたJavaScriptをユニットテストする、カバレッジも出す。
- webpackでまとめる。
- ファイルを更新したら自動ビルドする。
以上の操作を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を記述します。
{
"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に書き込みます。
export class Person {
readonly id: number;
readonly name: string;
constructor(_id: number, _name: string) {
this.id = _id;
this.name = _name;
}
}
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
次のテストコードを書きます。コンストラクタのアサーションテストです。
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 help
やistanbul 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です。
module.exports = {
entry: "./src/main.js",
output: {
path: __dirname/build,
filename: "bundle.js"
}
};
更に、以下のようにすることでtsファイルを直接1つのjsファイルにまとめることができます。ts-loaderをインストールします。
また、devtool: 'source-map'
を指定してすることで、デバッグ時にデバッグツールが元のTypeScriptのコードを参照できるようにしています。これが無いと不便なのでデバッグ時はつけておきます。
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の書き方は公式のドキュメントに譲るとして、以下のように記述しました。
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 test
、gulp build
とすればそれぞれユニットテスト, ビルドが実行されます。
また、defualtのtaskにあるように
$ gulp
とすればユニットテストとビルドが一括して行われます。ユニットテストに失敗したらそこで止まります。
gulpでwebサーバを作成
静的ファイルのみのページの動作検証を行うためにサーバを用意します。本来Apacheでもnginxでも良いのですが、ここではgulp-connectを使ってサクっと開発webサーバを立ててみます。
$ npm install gulp-connect --save-dev
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に次のタスクを追記します。
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日現在
- TypeScriptのコンパイル設定
- ユニットテストとカバレッジレポート
- webpack
- gulp-server
- watch