gulp v4によるフルTypeScriptなビルド環境

SPAを作るには、webpack等の、色々寄せ集めて一つのJSにバンドルしてくれるツールを用いてビルドしますが、サーバサイドやローカル実行ツールのプロジェクトはそもそもビルドする必要すらありません。しかし、何らかの都合でビルドをしたい場合もあるでしょう。

  • JS以外のファイルをコンパイルしたい
    • TypeScriptで書いてる
    • SCSSとかコンパイルする
    • ドキュメントを生成する
    • テストは除いてリリースビルドを作る
  • ファイルをコピーしたり、ダウンロードしたりして構成を整える
    • フォントファイル!私はこれ
  • 難読化する

自分にニーズが無い時にはピンと来ないのですが、必要になったらどうにかしないといけない。そこでGulp、そしてv4。

https://gulpjs.com/

聞くところによると3年の沈黙を経て、昨年末ギリギリにv4.0.0がリリースされたそうです。gulp使ったことあるのは3年ぐらいは前だったかも。後方互換を保ちつつも、イマドキ風に生まれ変わりました。そしてこれがまたTypeScriptと相性が良いことが判明。

$ yarn add gulp@next gulp-typescript typescript ts-node @types/gulp @types/node@9 del -D

まず関連パッケージをインストールしますが、この時にgulpは@next、@types/nodeは@9とバージョン明示しないと本日時点ではNG。gulpはlatestがまだ3系なのは良いとして、@types/nodeの本日時点のlatestである10.0.0はTypeScriptをコンパイルできない。理由は@types/node@10.0.0とTypeScript本体のlib.d.tsでURLなど一部宣言が被って爆死しちゃうから。いずれ@types/nodeが治すでしょうけど、今日時点では@types/node@9で、治れば @types/gulp -> @types/vinyl-fs: "" -> @types/node: "" とバージョン指定してない連鎖のための10.0.0混入だからインストールする必要もなくなる。

@types/gulpは勇み足なのか本体がまだ3系をメインにしているのに普通に4系のものが入る。

// script/build.ts
import { src, dest, symlink } from 'gulp';
import * as ts from 'gulp-typescript';
const del = require('del');

(function () {
    // クリーンアップ
    del('lib');

    // コンパイル
    src(['src/**/*.ts', '!src/**/__tests__/*.ts'])
        .pipe(ts({
            target: 'es6',
            module: 'commonjs',
            strict: true,
            lib: ['es6', 'dom'],
        }))
        .pipe(dest('lib'));

    // 小サイズファイルコピー
    src('src/**/*.txt').pipe(dest('lib'));

    // 大サイズファイルはリンク
    src('src/**/*.otf').pipe(symlink('lib'));
})();

ビルドファイルは伝統のgulpfile.jsではなく何でも良いので適当な名前の.tsを作ります。ここではbuild.tsにしました。v4でgulp.taskを使わなくてよくなったのでこんな感じにかけます。プラグインもこれまでのものが使えます。ts-node入れてるのに.tsをビルドしててパッと見は奇妙なのですが、これが必要な時もある。

// package.json
{
    "scripts": {
        "build": "ts-node script/build.ts"
    },
    "devDependencies": {
        "@types/gulp": "^4.0.5",
        "@types/node": "9",
        "del": "^3.0.0",
        "gulp": "^4.0.0",
        "gulp-typescript": "^4.0.2",
        "ts-node": "^6.0.1",
        "typescript": "^2.8.3"
    }
}

package.jsonのscriptで、ts-nodeの呼び出しを書きます。以上。プロジェクトルートにscriptディレクトリを作ってそこにコマンドを一つづつファイルで書いて置いておけば良いし、共通のものなんかは関数エキスポートしてどっかでまとめて取り込んで組み立てればいいし。実行は当然以下の通り。

$ yarn build

すげー簡単。超素敵。。。だが冷静にGulpの使い所ってどれだけあるんだろうか。さらにこのぐらいのことを書くのにほとんど似たような素のnode.jsでかけるのに余計なものを詰め込んでまでTypeScriptが要るだろうか?

課題

gulp-typescriptが、オプションでpathsが効かないなど、本家tscと挙動が違う。

結論

gulpじゃなくてもいいや。

// package.json
{
    "scripts": {
        "build": "rimraf lib && tsc -p tsconfig.prod.json && cp src/font/*.otf lib/font",
        "lint": "tslint --project ./ './src/**/*.ts'",
        "test": "jest"
    }
}

元に戻った。例えばこんなこともしてます。

const _ = require('lodash');
const fs = require('fs');

(function () {
    const packageJson = _.omit(
        require('./package.json'),
        ['scripts', 'jest', 'devDependencies'],
    );
    _.set(packageJson, 'bin.helloworld', 'index.js');
    if (fs.existsSync('dist/package.json')) {
        fs.unlinkSync('dist/package.json');
    }
    fs.writeFileSync('dist/package.json', JSON.stringify(packageJson, null, 4));
})();

これは素のnode.jsなスクリプト。ちょっとビルドを整えるぐらいだったらこれでも不自由ないかとも思う。Gulpの良さはgulp.srcで対象ファイルをかき集めてきて、gulp.destでそのまま書き出せることでしょうかね。他に複雑なことをするのであれば、ファイルコピーだけでもgulpの方がわかりやすく便利なので出番が出てきます。複雑なことをするかですね。