tsx の tsImport で読む TS ファイルは type: module の無いプロジェクトで default が CJS interop ラップされる
TypeScript
CLI
知識
ツールがユーザー配置の TypeScript ファイル(プラグイン・シナリオ・設定スクリプト等)を tsx の tsImport API で動的ロードする設計では、読み込まれるファイルのモジュール種別が「ツール側の設定」ではなく「そのファイルから最も近い package.json」で決まることを前提にする。
誤解しやすい条件(実測した壊れ方)
- 利用側プロジェクトの package.json に type: module が無いと、tsx は対象の .ts を CommonJS として変換する。ファイル内の default エクスポートは exports.default への代入に変換され、tsImport が返す ESM 名前空間の default は module.exports 全体(default プロパティと __esModule フラグを持つオブジェクト)になる。つまり mod.default が期待した関数ではなく、関数を1段包んだオブジェクトになる。
- ツール開発側のリポジトリ(type: module)で試すと再現せず、利用側プロジェクトで初めて発火する。テストでこの差異を「テスト環境固有の癖」としてモックで封じると、実利用での壊れ方をテストが隠す形になる(実際にそうなっていた)。
- 表面のエラー(default export が関数でない)はユーザーのファイルの書き方の問題に見えるため、型注釈の除去など無関係な修正で遠回りしやすい。利用者側の暫定回避は module.exports への代入形式だが、これは配布ドキュメントの example が壊れたままになるので恒久解にしない。
対処
ローダー側で default の両形状を受ける: mod.default が関数ならそれを、オブジェクトならその中の default プロパティを取り出し、どちらでもなければエラーにする。利用者に書き方の変更を強いる文書対応より、ローダーの interop 吸収が正しい修正点。
検証
type: module の無い package.json を持つ一時ディレクトリに default エクスポートの .ts を置き、実際に tsImport 経由でロードして関数が返ることを確認する。ツール自身のリポジトリ内での再現確認はモジュール種別の前提が違うため代理にならない。