スタンドアロンビルドで `NODE_PATH` をサポート
Tailwind CSS のスタンドアロン CLI が NODE_PATH 環境変数を認識するようになりました。これにより、標準の node_modules 以外の場所にあるモジュールを、Node.js をインストールせずに解決できます。
背景
スタンドアロン CLI は、Node.js なしで Tailwind CSS を実行できる配布形態ですが、カスタムの NODE_PATH を使ったモジュール解決には対応していませんでした。Node.js 経由で実行する場合はこの制約がなく、同じ機能を利用できていました。
この問題の発端は、#19391 におけるビルド時の意図しない動作にあります。スタンドアロンビルドでは一部の環境変数をビルド時にインライン展開していましたが、CI 環境(macOS)上で実行されたため、NODE_PATH がランナー固有のパス(/Users/runner/work/.../node_modules)として埋め込まれてしまっていました。この問題を修正する際に NODE_PATH のサポートそのものが無効化され、スタンドアロン CLI では明示的に NODE_PATH が空文字列として定義されるようになりました。
一方、#16274 では Node.js 版の @tailwindcss/node パッケージに対して NODE_PATH のサポートが追加されており、スタンドアロン版との間に機能差が生じていました。本 PR はその差を解消します。具体的なユースケースとして挙げられているのが Phoenix LiveView のコロケーション機能で、テンプレートから抽出されたスクリプトを通常の node_modules とは異なるディレクトリに配置し、NODE_PATH を通じて解決する仕組みです。CSS のコロケーションでも同様のアプローチを採用したいが、スタンドアロン CLI がそれを妨げているという課題がありました。
技術的な変更
今回の変更は2ファイルにまたがっており、いずれも最小限の修正で目的を達成しています。
build.ts からの NODE_PATH の無効化を削除:
packages/@tailwindcss-standalone/scripts/build.ts では、NODE_PATH を空文字列にインライン化する define エントリが削除されました。
変更前:
define: {
'process.env.NAPI_RS_NATIVE_LIBRARY_PATH': JSON.stringify(''),
// No need to support additional NODE_PATHs in the standalone build
'process.env.NODE_PATH': JSON.stringify(''),
},
変更後:
define: {
'process.env.NAPI_RS_NATIVE_LIBRARY_PATH': JSON.stringify(''),
},
これにより、ビルド時に NODE_PATH が空文字列として固定されなくなり、実行時の環境変数がそのまま参照されるようになります。
compile.ts での複数パス対応:
packages/@tailwindcss-node/src/compile.ts では、NODE_PATH に複数のパスが含まれる場合を正しく処理するよう修正されました。
変更前:
const modules = ['node_modules', ...(process.env.NODE_PATH ? [process.env.NODE_PATH] : [])]
変更後:
const modules = ['node_modules', ...(process.env.NODE_PATH ? [...process.env.NODE_PATH.split(path.delimiter)] : [])]
path.delimiter を使って NODE_PATH の値を分割することで、Unix では : 区切り、Windows では ; 区切りという OS 標準のセパレータに自動対応しています。変更前の実装では NODE_PATH 全体を単一パスとして扱っていたため、複数パスが指定された場合に正しく動作しない問題もありました。
設計判断
path.delimiter による OS 依存の吸収が本変更の要点です。NODE_PATH の仕様では複数のパスをOSのパスセパレータで区切って指定できますが、変更前のコードはこの仕様に対応していませんでした。path.delimiter は Node.js 標準モジュール path が提供する定数であり、Unix/Windows の差異を明示的に吸収しています。
ビルド時の define による無効化を「削除する」という形で変更を実装したことも注目に値します。NODE_PATH を空にするコードを追加した経緯が、ビルド環境固有の問題への暫定対処だったため、その対処ごと取り除くことで、他の環境変数と同様に実行時解決へと戻しています。これは「無効化のための特別処理を除去する」という最小変更の方針と一致しています。
まとめ
スタンドアロン CLI が NODE_PATH を正しく扱えるようになったことで、Node.js 版との機能差が解消されました。Phoenix LiveView のような、標準の node_modules 以外のディレクトリからのモジュール解決を必要とするユースケースで、Node.js のインストールを強いることなくスタンドアロン CLI をそのまま活用できます。