Visual Studio Code ではビルド等のタスクを実行するために外部コマンドを呼び出します。以前の記事 では cmd.exe
を叩くという原始的な方式でしたが、勉強がてら gulp.js に乗り換えました。
今回のタスクは以下になります。素朴なスタンドアロンの単体アプリです。(プロジェクト全体は GitHub にあります。)
-
build
: 複数の TypeScript (.ts) ファイルをコンパイルして、単体の JavaScript (.js) ファイルを生成する。 -
test
: デバッグ用に、生成した .js ファイルを使う .html をブラウザで開く。 -
release
: リリース用に、生成した .js ファイルを圧縮 + 最適化する。 -
clean
: 中間ファイルを一括で削除する。
必要なモジュールの収集
まず最初に npm init
で package.json
を生成し、npm install --save-dev
で依存関係を追加しながら必要なモジュールを集めます。次回以降、まっさらな状態から再度環境を構築する際に npm install
だけで必要なモジュールをインストールできるようになります。
java -version # 前もってJavaを導入しておく。closure compiler用。
npm init # 設問には適当に答える
npm install --save-dev typescript
npm install --save-optional tslint
npm install --global gulp-cli
npm install --save-dev gulp
npm install --save-dev gulp-closurecompiler
npm install --save-dev gulp-insert
npm install --save-dev gulp-newer
npm install --save-dev gulp-open
npm install --save-dev gulp-typescript
npm install --save-dev del
JavaScriptの圧縮 + 最適化には選択肢がありますが、圧縮率が高く、積極的な最適化を見込める Google Closure Compiler を使うことにしました。また、gulp と連携するための拡張モジュールも、これまた何種類かありますが、モジュールのインストール時に compiler.jar を一緒にダウンロードしてくれて楽という理由から、gulp-closurecompiler にしました。
タスクの作成
タスク本体は gulpfile.js
に記述しますが、 Visual Studio Code から gulp を呼び出すために tasks.json
も必要です。なお、tasks
に1つ1つタスク名を書かなくても、VSCode は gulpfile.js
のタスクを認識してくれるらしく、workbench.action.tasks.runTask
の一覧に表示され、実行もできました。
tasks.json
{
"version": "0.1.0",
"command": "gulp",
"isShellCommand": true,
"showOutput": "never",
"suppressTaskName": false,
"args": [ "--no-color" ],
"tasks": [
{
"taskName": "build",
"showOutput": "silent",
"isBuildCommand": true,
"isWatching": false,
"problemMatcher": [
"$tsc",
"$gulp-tsc"
]
}
]
}
※problemMatcher
の指定には、いまいち確信を持てていません。
build
TypeScriptをJavaScriptにコンパイルするタスク build
を gulpfile.js
に記述します。build
という名前のタスクは、Visual Studio Code から workbench.action.tasks.build
としてワンボタンで実行できます。
tsconfig.json
を使う場合には gulp.src()
ではなく ts.createProject()
を使うとのこと。
また、コンパイル前に gulp-newer
を挟み、.ts ファイルに更新がない場合はコンパイルをスキップします。今回は、複数の入力ファイルを1個の出力ファイルにする処理なので、newer()
の引数は出力ファイル名を指定します。一般的な、入力と出力が1対1対応の場合は出力ディレクトリを指定します。
var newer = require("gulp-newer");
var ts = require("gulp-typescript");
gulp.task("build", function () {
var project = ts.createProject("./src/tsconfig.json", {
outFile: OUT_NAME
});
return project.src().
pipe(newer(DEBUG_DIR + OUT_NAME)).
pipe(ts(project)).
pipe(gulp.dest(DEBUG_DIR));
});
test
HTMLファイルをブラウザで開くタスク test
です。こちらも workbench.action.tasks.test
として実行できます。
依存関係に build
を追加し、必要に応じてコンパイル後に gulp-open
を使ってHTMLを開きます。もちろん start
(Windows), sensible-browser
(Linux) が手っ取り早いですが、移植性や統一感から、こういうやり方も良いかなと思います。
var open = require("gulp-open");
gulp.task("test", ["build"], function () {
gulp.src("./index-debug.html").pipe(open());
});
release
タスク release
は build
で作ったJavaScriptファイルを加工して最適化します。Closure Compilerに渡す前に、コード全体を (function() {
と })();
で包んで、より積極的な最適化を促します。同時に、var NDEBUG=true
を定義して動作確認のためのコードを削って欲しい旨も伝えます。
なお、build
と test
以外のタスクは workbench.action.tasks.runTask
でタスク一覧を表示させてから選択して実行する必要があります(もしくは端末から gulp release
)。
var insert = require("gulp-insert");
var minify = require("gulp-closurecompiler");
gulp.task("release", ["build"], function () {
return gulp.
src(DEBUG_DIR + OUT_NAME).
pipe(newer(RELEASE_DIR)).
pipe(insert.wrap("(function() {\nvar NDEBUG=true;\n", "\n})();")).
pipe(gulp.dest(WRAPPED_DIR)).
pipe(minify({ fileName: OUT_NAME })).
pipe(gulp.dest(RELEASE_DIR));
});
さて、ここが今回のハマり所だったのですが、gulp-closurecompiler
はパイプラインで加工中のコードを処理できず、ディスク上に実体のあるファイルが必要でした。そのため、途中で gulp.dest()
を挟んで中間ファイル WRAPPED_DIR
ディレクトリに書き出しています1。gulp本体はパイプラインを使った流れ作業を推奨する設計思想だと思うのですが、呼び出す外部プログラムや拡張モジュールが対応していなければ仕方ないということでしょうか……。
また、元ファイルに変更がなければ処理をスキップできるよう newer
を加えていますが、処理対象がない場合に gulp-closurecompiler
がクラッシュしました。モジュール自体の不具合と思われ、下記の1行を追加することで解決しました。gulpfile.js の書き方で回避できるならそうしたいのですが……。
beforeEnd = function() {
+ if (files.length === 0) { this.queue(null); return; }
clean
タスク clean
は中間ファイルを削除します。gulpで rm -rf
相当を行う標準的な書き方だそうです。
var del = require("del");
gulp.task("clean", function(cb) {
del([BIN_DIR, DEBUG_DIR], cb);
});
さいごに
Visual Studio Code のタスクを gulp で管理できるようになりました。タスクの依存関係を定義し、ファイルの最終更新日時をチェックして必要な場合のみ処理できたので、古来からの Makefile 程度にはなりました。
cmd.exe
ベタ書きと比べて、新しいことをできるようになったわけではないですが、少なくともWindowsに完全に依存していたことは脱却できました。パイプラインの連結により効率的に処理でき、タスクの並列化もしやすいようなので、より複雑で時間のかかるケースではもっと利点が生きると思われます。
-
当初は
gulp-rename
を使っていましたが、複数の入力ファイルがある一般的にはなケースを考えると、別ディレクトリを用意する(もしくは rename で拡張子のみ変更する)ほうが汎用性が高いと思われます。 ↩