
要旨
- tsc のパスエイリアスはモジュール解決のために存在するもので、出力結果を書き換えるものではない
- 対策をしなければ、型定義ファイルにパスエイリアスがそのまま残り、npm パッケージとして公開されてしまう
- これにより、使用側で型の解決が正しく動作しないなどの問題が発生する
- 出力結果に含まれるパスエイリアスを書き換えたいなら、tsc 以外のツールを使う必要がある
- 今回は tsc-alias パッケージを使用して、ビルド後にパスエイリアスを相対パスに書き換える方法を採用した
はじめに
これはわたしが実際に体験した話なんですけどね。
クラウドサインには Delta Wing UI という、内製の UI コンポーネントライブラリがあるんですよ。
それで、あるプロジェクトで使って画面を作ろうとしたんですけどね。仮に、そのプロジェクトをRプロジェクトとでもしておきましょうか。
で、その R プロジェクトで作業してたら、なーんか、うまくいかないんですよ。
いやだなー、怖いなーと思って。いろいろ見て回ってみたんです。
Theme 型の引数には何を渡せばいいのかなーって。
するとドンッ! と型補完のポップアップが現れて。不思議に思ってじーっと眺めてみたんですよ。
そしたら違うんですよ、仕様と……!

Theme って言うもんですから、success とか warning とか、そういうのが出てくるのかなーと思っていたら、 content とか dialog とか drawer とか main なんて出てくるんですよ。

南無阿弥陀仏、南無阿弥陀仏……! とお経を唱えながら Delta Wing UI の実装を見に行くと、

こっちは大丈夫そう。なぁんだ見間違いかと思って、もう一度Rプロジェクトの方で参照してみると……
ぎゃあああああああああ!

そのまま気を失ってしまったんですよ。
原因調査
ここからはもう、怖くて怖くて仕方がなかったんですけどね。
こうなっては、腹をくくって調査するしかないなと思って。リポジトリを掘り下げていくうちに、あることに気がついたんです。
型定義ファイルの import 文に、パスエイリアスが使われているんですよ。

どうやらこの '~' っていうのは、Delta Wing UI のリポジトリの中では、そのルートを指す、そういうつもりで使われていたみたいなんですね。
ところがですよ、それがそのまま、R プロジェクトの方まで出てきちゃった。
R プロジェクトは R プロジェクトで、やっぱり tsconfig を持ってるわけですよ。えぇ。
Nuxt のプロジェクトなんでね、ご丁寧に .nuxt/tsconfig.json っていうファイルがあって、そこには「~ は、このプロジェクトのルートを指しますよ」って書いてある。
ですからね、Delta Wing UI からやってきたはずの Dialog.d.ts に書かれた ~/types/Alert というパスが、なんと、Rプロジェクトの <ルート>/types/Alert というファイルを指すことになっちゃってたんですよ。
普通なら都合よくこんなファイルがプロジェクトに存在することはないので、エラーになって済むんですけどね。
いたんですよ……!Rプロジェクトの方にも……!
なんでも、このRプロジェクトは Delta Wing UI が生まれるよりもずっと前からあったもんでね。
だから、自分たちでこしらえたコンポーネントだとか、その型定義だとかを、大事に抱えていたそうなんですよ。
おまけに定義が入れ替わっている。1

だから、違う内容になってしまったんですね。
背景の理解と対策
背景の理解
この怪奇現象を根本から断つにはね、どうやら Delta Wing UI からやってくる型定義、これにあのパスエイリアスを含めないようにしなきゃいけない、ってことが分かってきたんです。
調べてみるとね、TypeScriptの古い言い伝えが書かれた巻物(ドキュメント)に、はっきりと書いてあるんですよ。
「このパスエイリアスは、モジュールを解決するためのものであって、tsc が出力する成果物では、書き換えられない」ってね。
Note that this feature does not change how import paths are emitted by tsc, so paths should only be used to inform TypeScript that another tool has this mapping and will use it at runtime or when bundling.
これまでにもね、たくさんの人がこの現象について「助けてくれ」って issue をあげてたみたいなんですけどね。
でも、その issue はことごとく、ピシャリと、一蹴されてるんですよ。2
- Module path maps are not resolved in emitted code · Issue #10866 · microsoft/TypeScript
- Offer option to resolve module path maps in emitted code · Issue #26722 · microsoft/TypeScript
手段を決める
ということはですよ、この d.ts っていうやつをライブラリとして他の場所に配るんなら、普通に解決できるような記法(つまり、相対パスですね)で書いてやらなきゃいけないってことなんですね。
それでね、いくつか方法を考えたんですよ。
- パスエイリアスを使用せず、相対パスで import する
- ビルドプロセスでパスエイリアスを相対パスに書き換える
それでね、どうしたかというと、「今あるコードを全部書き直すのは、あまりにも骨が折れる」「それに、開発している間は、このパスエイリアスってやつも、あながち悪いやつじゃない」
……そんなことを考えましてね。結局、2つ目の方法をとることにしたんですよ。
そこで見つけたのがね、『tsc-alias - npm』っていう、ありがたいライブラリだったんです。
これがまた、使い方も簡単でね。ちゃんと手入れもされてる。
それに、PDF.jsだとか、n8nだとか、Rspackだとか、名だたる猛者たちも使ってるっていうじゃないですか。これなら、きっと大丈夫だろうと。
手段を実行する
ビルドっていうのは、buildっていうスクリプトで実行されてるんですね。
だから、そのスクリプトが終わった後に、すかさずこの「tsc-alias」を実行してやろうと。postbuild っていうスクリプトに、そいつを仕込んでやったんです。
// package.json { "scripts": { "build": "vue-tsc --project tsconfig.build.json && vite build", "postbuild": "tsc-alias -p tsconfig.build.json" } }
たったこれだけなんですよ。
するとどうでしょう。さっきまであのパスエイリアスがのさばっていた場所が、きれいな相対パスに書き換わって、ちゃんと意図したとおりに型が解決されるようになったんです。

おわりに
いやぁ、怖いですねぇ。
便利な機能だと思って使っていたものが、思わぬところで牙を剥くんですから。
あなたの d.ts は、大丈夫ですか……?
もしかしたら、あなたの知らないところで、パスエイリアスが、じっと、あなたを見ているかもしれませんよ……。