※調査には、ChatGPT/Geminit/perplexity/GitHub Copilot使ってますが、ここに書いてある文章は、全部自分で考え記載してます。
問題の概要:症状・影響など
typescriptにて、コンパイルして実行したら、「Error [ERR_MODULE_NOT_FOUND]: Cannot find module xxxx」が発生した。
特におかしいところはなく、copilotとかに聞いてもなかなか解決しなかった。。。
環境情報
実行環境は、下記の通り
$ node -v v23.8.0 $ npm -v 11.1.0
package.jsonの内容を一部抜粋
{
"dependencies": {
"@google/genai": "^1.27.0",
"dotenv": "^17.2.3",
"node-schedule": "^2.1.1",
"rimraf": "^6.0.1"
},
"devDependencies": {
"@types/node": "^24.9.1",
"@types/node-schedule": "^2.1.8",
"ts-node": "^10.9.2",
"ts-node-dev": "^2.0.0",
"typescript": "^5.9.3"
}
}
調査(仮説・探索)
1. ts-nodeが悪さしてる?
仮説:ts-nodeが悪さしてる可能性
検証:tsc使うように変えてみて同じ事象発生するか確認
いちいちコンパイルするのが面倒だったので、ts-nodeで実行していたが、こいつが問題を引き起こしてるんじゃないかと感じて、tsc使ってコンパイル→実行に変えた。
しかし、改善せず。。。
まぁ、実行する前からIDEでエラー言われてるから、ts-nodeの問題じゃないよね。。。
ts-nodeが問題ではなさそうだが、いろいろ実験的な要素を含むって調べてて出てきたので、調査進めていくうえでトラップになりそうなので、tscでコンパイル→実行の流れは元に戻さず調査を続行。
結果:ts-nodeではなさそう。ただ、原因がわかりづらくなりそうなので、tscを使ったコンパイル実行の流れはせっかくなので利用する。調べてると、ESMってワードが出てきたので、それが臭そう
2. ESMってなんぞ?
仮説:ESMってやつが理解できてないせいで、本当は設定ミスに気づけてない
検証:ESMについて調査して、設定を再確認して直せば解消できるのでは?
前回の調査で出てきたESMってのについて調べることに。
何やらモジュールの何かの仕様っぽい。
以前まで、import Xxx from "../xxx/xxx" みたいなのでimportして利用できてたんだが、なぜかエラーが出る。
.js`つけろみたいなことを言われて、つけて問題なくなったので、そのままにコンパイル実行すると、dist配下に吐き出したものが、なぜかsrc配下のものを参照しようとする結果に。。。
ESMが古い仕様なのではと思い調べたら、ECMAScript で定義されているモジュール機能が ECMAScript Modules です。ES6(ES2015) で追加されました。 と出てきたので、古いってわけではなさそう。※古いってのは、人によって変わるかもだが、ES2015は比較的新しいものだと思ってる。
だから、ESMが適用されているのはいいっぽい。
"module": "NodeNext" と "moduleResolution": "NodeNext"の設定を加えたけど、解消はしなかった。しかし、間違った対応ではなさそうだったので、このままにしておく。
結果:ESMが問題ではなさそう
3. importが間違ってる?
仮説:.jsつけろって言われたけど、本当はつけなくてもいいのでは?
検証:importから.js外してみる
.jsつけるのが間違ってると思い、外したが、ダメだった。。。
.tsにしてもIDEでエラーになる。
結果:importの問題ではなさそう
そして、半日彷徨うはめに。。。
4. tsconfig.jsonの設定が怪しい?
仮説:importをjsするとコンパイル前を見て、参照がおかしくなるのなら、コンパイルの設定が間違っているのではないか?
検証:設定を見直して、パスを置換するような方法ないか調べた結果、rewriteRelativeImportExtensions という何か拡張子を書き換えてくれそうな設定を発見し、trueを設定してコンパイル実行
結果:動いた。。。
誤った道・失敗談:失敗も正直に
ビルドスクリプトがおかしいのかと思って、rootDir/outDirをいじっていた。。。
コンパイルしたファイルがプロジェクトルートに出てきたときは、どれがコンパイルで作ったファイルかパニクってしまった。。。
ちゃんと安全を確保したらgitに細かくコミットしておくべきだった。
git使うにしても、ブランチ切って検証したり、不要なフォルダなんかをコミットしないように、gitignoreに追加するのは忘れずにやる必要がある。
GitHub Copilotくんには、インポートについてくどいくらいに聞かれた。
やっかいだと思ったのは、それっぽい回答で、疑念を抱きにくい文章で返ってきたので、なかなか気づけなかった。いや、copilot使ってなかったら、もっと沼ってた可能性もあるが。。。
// before
{
"compilerOptions": {
"module": "NodeNext",
"moduleResolution": "NodeNext"
}
}
// after
{
"compilerOptions": {
"module": "NodeNext",
"moduleResolution": "NodeNext",
"rewriteRelativeImportExtensions": true
}
}
※rewriteRelativeImportExtensionsはTS 5.7以上で利用可能
解決策・検証:具体的な修正方法と動作確認
どうやら、.jsつけたtsファイルは、コンパイルしたときにリンクを読み替えてくれてないらしい。
かと言って、.js外すとエラーになる。
なので、tsconfig.jsonのcompilerOptionsに、rewriteRelativeImportExtensionsを追加して、trueを設定する。そうすると、.tsがつけられるようになり、コンパイルした際にパスを読み替えてくれる。
distに吐き出さなければいいだけでは?って思いもしたが、過去に参加してたプロジェクトで、jsとtsがごっちゃになって、どれが生成で作ったのか分からず、コンパイルで作ったものを修正してしまったという事案があった。どれに手を入れればいいのかわかりにくいって事象があったので、tsとjsを同じフォルダに置くのは辞めた。
学び・予防策:今回の発見から得た教訓
標準仕様のキャッチアップができてない
標準仕様の話が弱いってことが分かった。。。
頻繁にやる必要はないと思うが、年末年始のタイミングでキャッチアップするような習慣が必要かもしれない。tsのコンパイルオブションへの理解を深めましょう
tsconfig.jsonに定義している値がどういう意味合いなのか、有名なやつは理解しておく必要がある。
じゃないと、コンパイルしたときに変な挙動になってメダパニ状態になる。生成AIは万能ではない
今回みたいに、知識不足だと、提案される対応に振り回される。
ちゃんと知識があれば、すんなりクリアできた事象だった。これからの時代は、基礎の重要性が増すってのを身をもって実感した。ただ、生成AIに依存してると、鍛えられない気がする。一回振り回されてみないとダメなのかも知れない。「生成AIでバグも解消~」って言っているやつは、たぶん、こういう目にあってないやつだと思う。聞き方変えれば、回答にたどり着けたかも知れないが、問題にぶつかってたときの俺では無理だった。やっぱり、ハイスキルな人が使うことで威力が最大限発揮されるのがAIなんだと実感した。
参考リンク
もう迷わない!JavaScriptのモジュール入門(Part 1)〜ES Modules と CommonJS〜 #ECMAScript - Qiita
https://zenn.dev/teppeis/articles/2021-10-typescript-45-esm
https://zenn.dev/chot/articles/migrate-from-cjs-jest-to-esm-vitest
https://blog.koh.dev/2024-04-23-nodejs-typescript-module/
https://openillumi.com/ts-esm-fix-module-not-found-js/