gulp を使って TypeScript なりを変換して SourceMap を使うには gulp-sourcemaps というモジュールを使います。

それを使えば gulp-typescript は変換後の *.js に SourceMap をつけてくれます。しかし、変換前の TypeScript の SourceMap は無視されてしまいます。不便なのでなんとかしてみます。

インストール

multi-stage-sourcemap という多段 SourceMap のためのモジュールがあるのでそれを使います。 TypeScript の前に実行する変換には gulp-header を使ってみます。

$ npm install -g gulp
$ npm install -D gulp gulp-header gulp-sourcemaps gulp-typescript multi-stage-sourcemap

加えて、 gulpfile を ES2015 で書くために babel-core を、node で SourceMap を確認するために source-map-support を、 gulpfile の中で使うために gulp-util をそれぞれ入れます。

$ npm install -D babel-core gulp-util source-map-support

コード

SourceMap の確認用に console.trace() を使ったコードを用意します。

// a.ts
console.trace();
console.trace();
console.trace();
console.trace();
console.trace();
console.trace();
console.trace();
console.trace();
console.trace();

スタックトレースを表示するだけのコードです。

これを変換するための gulpfile が以下です。

// gulpfile.babel.js

import gulp from 'gulp';
import header from 'gulp-header';
import sourcemaps from 'gulp-sourcemaps';
import typescript from 'gulp-typescript';
import multiStageSourcemap from 'multi-stage-sourcemap';

gulp.task('default', () => {
    const sourceMaps = new Map();
    return gulp.src(['*.ts'])
        .pipe(sourcemaps.init())
        .pipe(header('//0\n//1\n//2\n' + '\n'.repeat(10000)))
        .on('data', (file) => {
            const path = file.path.replace(/\.ts$/, '.js');
            sourceMaps.set(path, file.sourceMap);
        })
        .pipe(typescript())
        .on('data', (file) => {
            file.sourceMap = JSON.parse(multiStageSourcemap.transfer({
                fromSourceMap: JSON.stringify(sourceMaps.get(file.path)),
                toSourceMap: JSON.stringify(file.sourceMap)
            }));
        })
        .pipe(sourcemaps.write())
        .pipe(gulp.dest('.'));
});

gulp.src で TypeScript を読み込み、 sourcemaps.init で SourceMap の準備、 header で先頭にコードを追加、typescript で変換、 sourcemaps.write で SourceMap の書き出し、 gulp.dest で出力をしています。

途中 on('data', ...)header でつけられた SourceMap を記録して、その後 multiStageSourcemap.transfer でそれと TypeScript によってつけられた SourceMap を合成しています。

実行

実際に変換して、 node で実行してみます。素の状態だと SourceMap が効かないので require('source-map-support/register'); したあとで変換したコードを読み込みます。

require('source-map-support/register');
require('./a');

変換して実行します。

$ gulp
$ node run.js

Trace がずらずらと出てきます。 a.js ではなくちゃんと a.ts の位置が表示されました。 SourceMap がきちんと効いているようです。

ちなみに .pipe(header('//0\n//1\n//2\n' + '\n'.repeat(10000)))+ '\n'.repeat(10000) を消すと 3行目以降の SourceMap がずれました。追加したコードの行数以降にズレが出るようです。どこが原因なのかわからないので 10000 行追加して、 10000 行未満のファイルなら無理やり SourceMap が効くようにしました。バッドノウハウな感じで気持ち悪いですがこれで動くようです。

まとめ

  • multi-stage-sourcemap で複数の SourceMap の合成ができる
  • TypeScript 変換前後の SourceMap を合成すればいい