19
22

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-12-30

概要

はじめに

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

19
22
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
22

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?