ビルドスクリプトのトップレベルawaitを関数内に移動してインポート時の破損を修正
ビルドスクリプト build.js がモジュールとしてインポートされる際に、トップレベルで package.json を読み込もうとして失敗していた問題を修正しました。バージョン情報の取得を build() 関数内に移動することで、実際のビルド実行時まで評価を遅延させています。
背景
build.js がアプリ側からインポートされると、モジュール評価時点でトップレベルの await readFile(...) が即座に実行されていました。アプリのビルド環境では、この時点でバンドルされたディレクトリがまだ生成されていないケースがあり、package.json の読み込みに失敗してビルドスクリプト全体が壊れる原因となっていました。
PR の説明にある通り、「このファイルはアプリからインポートされる可能性があり、その時点ではバンドル済みディレクトリがまだ生成されていない場合がある」という前提が考慮されていなかったことが問題の本質です。
技術的な変更
packageData と version の取得、および初期ログ出力をトップレベルから build() 関数の先頭へ移動しました。
変更前:
const packageData = JSON.parse(await readFile(join(getRootDir(), 'package.json'), 'utf-8'));
const version = packageData.version;
let buildContexts = { ... };
console.log(`${chalk.hex('#ef6741')('🦊 Web Awesome')} v${version}\n`);
if (isDeveloping) spinner.info('Development mode');
export async function build(options = {}) {
// ...
}
変更後:
let buildContexts = { ... };
export async function build(options = {}) {
// packageData and version need to be set within the `build()` function because this file gets imported by the app,
// which may not have generated its bundled directory yet, so this needs to be "lazily" evaluated.
const packageData = JSON.parse(await readFile(join(getRootDir(), 'package.json'), 'utf-8'));
const version = packageData.version;
console.log(`${chalk.hex('#ef6741')('🦊 Web Awesome')} v${version}\n`);
if (isDeveloping) {
spinner.info('Development mode');
}
// ...
}
あわせて if (isDeveloping) のブロックが波括弧付きのスタイルに統一されました。これは機能的な変更ではなく、スタイルの一貫性のための修正です。
設計判断
「遅延評価(Lazy Evaluation)」によるファイルシステムアクセスのタイミング制御が採用されました。
ES モジュールのトップレベル await はモジュール評価時に即座に実行されるため、import するだけで副作用が発生します。ビルド対象のアプリがこのスクリプトをインポートした場合、ビルド完了前の状態でファイルシステムにアクセスしてしまうという競合状態が生じていました。修正では package.json の読み込みを build() 関数内に閉じ込めることで、実際にビルドが実行されるまでアクセスを遅延させています。
副作用を持つ処理を関数スコープに限定するこのアプローチは、モジュールの再利用性と安全性を高める設計原則に沿っています。
まとめ
トップレベル await によるモジュール評価時の副作用が、インポートされる側のスクリプトで問題を引き起こすという典型的なパターンへの対処です。ファイルシステムアクセスのような副作用を関数スコープに限定することで、モジュールのインポート安全性を確保しています。