Typescript + gulpでコマンドライン引数を指定してビルド

typescript logo プログラミング

TypescriptのコードをJavascriptライブラリにビルドする際、
s3などの外部からファイルを取得してきてそれをビルドに組み込みたい
ケースがありました。

その際、取得するファイル名をビルドコマンドで指定する形で実現しました。
これでワンコマンドでJSのビルドが最後まで行えるので非常にシンプルに使うことができます。

使用パッケージ

ビルドツールはVite、またタスクランナーとしてgulpを使用します。

Vite

Vite
次世代フロントエンドツール

Viteは非常に高速と評判のビルドツールです。

Vue.jsを開発したEvan You氏によって開発されています。

プロダクション環境用のビルドでは、内部でrollupを使用しています。

Rollup
compile JS code

gulp

gulp.js

gulpはタスクランナーとして長年有名なパッケージです。

ビルドや環境構築などの操作をタスクという単位で定義します。

各コードのトランスパイル/コンパイル、難読化、外部ファイルの取得などなど複数のプロセスが必要になってくるひとまとまりの作業を、タスク単位で定義、実行できるため
自動化、効率化という面ですごく便利です。

やりたいこと

以下のようなsettingファイルをimportし、その中身をconsole.logで出力するという
シンプルなtypescriptコードをjavascriptにビルドします。

↓main.ts↓

TypeScript
import settingJson from './setting';

console.log("DEBUG setting.json:"+JSON.stringify(settingJson));

↓setting.ts↓

TypeScript
export default {
  "desc": "this is default json file"
};

ただこのsetting.tsをそのままビルドに使うのではなく、
ビルド直前に、コマンドライン引数で指定した外部のJSONファイルを取得し、setting.tsのJSON部分をその内容で置き換える
といったことを行います。

例えば↓このようなJSONファイルがs3に保存してあるとして(ファイル名をjohn.jsonとします)

JSON
{"age": 20, "name": "John"}

↓このような感じでビルドコマンドに引数でファイル名を指定することで、指定ファイルに置き換えるというイメージです。

{何らかのビルドコマンド} --profile john

実践

ビルド前に外部ファイルを取得し、setting.tsの中身を置き換える処理ですが、
ビルドで使うViteは内部でrollupを使用しているため
今回はrollupのプラグインを自作する形で対応してみました。

rollupプラグインでソースコードを書き換え

Plugin Development | Rollup
compile JS code

プラグインでは様々なフックが定義可能であり、ビルドのワークフロー上でのフックの発火タイミングで定義したプラグインコードを実行させることができます。

例えばビルド開始時にはbuildStartフックが発火し、ビルド終了時にはbuildEndフックが発火します。

今回はtransformフックを使用します。
transformフックは、ビルド対象のモジュールのコードやソースマップに変更を加えるものです。
これを使用してsetting.tsのコードを変更します。

プラグインのコードとしては以下のようなイメージです。

JavaScript
const pluginPrepareSetting = {
	name: "prepare-setting",
	transform: async (code, id) => {
		if (id.match(/setting\.ts/)) {
			try {
				const res = await fetch("https://s3-sample-domain/john.json");
				const resJSON = await res.json();
		
				const ms = new MagicString(code);
				ms.overwrite(0, code.length, `export default ${JSON.stringify(resJSON)};`);
				return {
					code: ms.toString(),
					map: {
						mappings: ""
					}
				};
			} catch(e) {
				console.log("[Rollup Plugin prepare-setting] Error:"+e);
				return null;
			}
		}
	}
}

transformの引数のidはファイル名、codeはファイルのソースコード文字列です。

ファイルがsetting.tsの時、s3パスからjsonファイルを取得し、ソースコードを書き換えるといった処理を行っています。

gulpで取得ファイル名をコマンドライン指定

gulpコマンドではminimistパッケージを利用することにより、コマンドライン引数を取得することができます。

↓gulpfile.js↓

JavaScript
import minimist from 'minimist';

const options = minimist(process.argv.slice(2), {
    string: 'profile',
    default: {
        profile: 'me' // デフォルトの値
    }
});
const profile = options.profile;

const sampleTask = (done) => {
    console.log("profile:"+profile);
    done();
};

export { sampleTask };

上記のgulpfile.jsを用意して
gulp sampleTask –profile john
などと実行すると、sampleTask内のconsole.logでコマンドライン引数のjohnが出力されます。

まとめ

指定したファイルを外部から取得

setting.tsの内部JSONを取得ファイルのJSONで上書き

JSビルド

この一連の流れを設定したgulpfile.jsが以下になります。
(CommonJSではなく、ESM方式で記述しています)

JavaScript
import path from 'path';
import { fileURLToPath } from 'url';
import { build } from 'vite';
import typescript from '@rollup/plugin-typescript';
import MagicString from 'magic-string';
import minimist from 'minimist';

// ESMで__dirnameを使用できるようにする
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// gulpコマンドのコマンドライン引数を取得
const options = minimist(process.argv.slice(2), {
    string: 'profile',
    default: {
        profile: 'me' // デフォルトの値
    }
});
const profile = options.profile;

// rollupプラグイン
// profile変数の値のJSONファイルをs3から取得し、setting.tsでexportされているJSONを書き換え
// 取得するファイル名は${profile}.json
const pluginPrepareSetting = {
	name: "prepare-setting",
	transform: async (code, id) => {
		if (id.match(/setting\.ts/)) {
			try {
				const res = await fetch(`https://s3-sample-domain/${profile}.json`);
				const resJSON = await res.json();
		
				const ms = new MagicString(code);
				ms.overwrite(0, code.length, `export default ${JSON.stringify(resJSON)};`);
				return {
					code: ms.toString(),
					map: {
						mappings: ""
					}
				};
			} catch(e) {
				console.log("[Rollup Plugin prepare-setting] Error:"+e);
				return null;
			}
		}
	}
}

// Viteのビルドコマンドに渡すビルド情報
const userConfig = {
  build: {
    base: "/src/",
    target: "es2015",
    outDir: "dist",
    lib: {
      entry: path.resolve(__dirname, "main.ts"),
      name: "testAppExe",
      formats: ["iife"],
      fileName: (_) => "testAppExe.js"
    },
    rollupOptions: {
      input: path.resolve(__dirname, "src", "main.ts"),
      output: {
        dir: "dist",
        entryFileNames: "testAppExe.js",
        format: "iife",
        name: "testAppExe",
        plugins: [
          typescript()
        ]
      }
    }
  },
	plugins: [
		pluginPrepareSetting // 上記の自作プラグインを指定
	]
};

// gulpタスク定義
const createTag = async (done) => {
	await build(userConfig);

	done();
};

export { createTag };

そして開発環境のsrc/以下に、ソースファイル(上記のmain.tsとsetting.ts)を配置します。

package.jsonのscriptにgulpコマンドを設定します。

JSON
{
  ...
  "scripts": {
    "build": "gulp createTag"
  }
  ...
}

これで
npm run build –profile john
といった具合に、コマンドライン引数のprofileを指定してビルドします。

そうするとs3の{指定したprofile文字列}.jsonを取得し、モジュールを書き換えてビルドしてくれます。

ビルド生成されたtestAppExe.jsを、開発サーバを起動して下記のようなhtmlで読み込むと
コンソールに取得したJSONがconsole.logで出力されます。
このことから、ビルド時にsetting.tsでexportされているJSONが取得したJSONに書き換えられていることがわかります。

HTML
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + TS</title>
  </head>
  <body>
    <div id="app"></div>
    <script async src="/dist/testAppExe.js"></script>
  </body>
</html>

(※Viteでは開発サーバも超簡単に建てることができます。)

以上、
Typescriptビルド時にコマンドライン引数を使用し、指定した内容によってビルド対象のモジュールを操作する方法でした!

(今回はVite(rollup)、gulpを使って実践した一例でした。他にもいろいろやり方はあると思います。)

コメント

タイトルとURLをコピーしました