Vitest を導入しただけで tsc がコケるようになった
本スクラップについて
以下のエラーについて追求する。
$ tsc --noEmit
node_modules/vite/dist/node/index.d.ts:6:41 - error TS2307: Cannot find module 'rollup/parseAst' or its corresponding type declarations.
6 export { parseAst, parseAstAsync } from 'rollup/parseAst';
                                          ~~~~~~~~~~~~~~~~~
Found 1 error in node_modules/vite/dist/node/index.d.ts:6
error Command failed with exit code 2.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
結論から言うと、以下で解決した。
{
  "compilerOptions": {
    // 一部抜粋
    "moduleResolution": "bundler",
  },
}
のだが、どういう理屈で発生してなぜ治ったのかよくわからなかったので、順に明らかにしていく。
背景
テストフレームワークに Jest を使用しているプロダクトで、Vitest への移行を行った。
その際、vitest の global API を宣言無く呼び出せるようにするため、その型定義を読み込むように tsconfig.json を追加した。
{
  "compilerOptions": {
    // 一部抜粋
    "moduleResolution": "node",
    "types": ["vitest/globals"],
  },
}
vitest 移行後のテストは全て通るようにまで一気に修正したが、tsc による型チェックが通らなくなった。
$ tsc --noEmit
node_modules/vite/dist/node/index.d.ts:6:41 - error TS2307: Cannot find module 'rollup/parseAst' or its corresponding type declarations.
6 export { parseAst, parseAstAsync } from 'rollup/parseAst';
                                          ~~~~~~~~~~~~~~~~~
Found 1 error in node_modules/vite/dist/node/index.d.ts:6
error Command failed with exit code 2.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
手っ取り早い解決
エラーメッセージで雑にググると、以下の Discussion にて、まったく同じ報告があり、そこですぐに解決されていた。
Vite リポジトリであることや、Rollup でのエラーであることから、vitest でなく vite, それも Vite 5 からであることがうかがえる。
You need to use moduleResolution: 'bundler' (or node16/nodenext) to fix it. Rollup only export those types from the "exports" field which those resolution settings respect.
とのことなので、 moduleResolution についておさらいすれば良さそう。 import をどのように解決するかのアプローチの設定だった気がするが細かくは理解してない。
 moduleResolution について
この記事が最高に良くまとまってる。というか半年前に読んでブコメを残してた。のでこういう記事があったことは覚えてたんだけど、結局 Node16 や bundler を設定した時にどうなるかは覚えてなかった。
 package.json における、モジュールシステムの宣言
- 
type: "module"の場合は ESM で提供することを宣言できる - `type: "commonjs" の場合は CJS で提供することを宣言できる-
 - 
mainフィールドにはインポートした際のエントリポイントを指定できる (CJS, ESM 共通) - 
exportsフィールドには、インポートするパスごとに、CJS, ESM それぞれのエントリーポイントを指定できる- 
mainが定義されていてもこちらが優先される 
 - 
 
TypeScript 側のモジュール解決
tsconfig.json の moduleResolution フィールドで設定する
- 
Node/Node10- CJS 専用
 - 
mainフィールドのみ使用される 
 - 
Node16/NodeNext- 
typeの値によって CJS か ESM か決まる - 
exportsフィールドからエントリーポイントを決定する - 拡張子や index の補完を行うの自動補完は行われない
 
 - 
 - 
Bundler- 拡張子や index の補完を行うの自動補完を行う
 - 
exportsフィールドによってエントリーポイントが決定する 
 
Rollup について
今回エラーが発生したのは rollup/parseAst でした。
6 export { parseAst, parseAstAsync } from 'rollup/parseAst';
Vite 5 を使用しているので、Rollup も v4 が使用されます。
$ yarn why vite
=> Found "vite@5.0.6"
$ yarn why rollup
=> Found "rollup@4.6.1"
rollup の package.json から rollup/parseAst を確認する。
cjs あるくね?
あー違う。現状だと
"moduleResolution": "Node",
が使われてたんだ。だから昔ながらの main フィールドしか見えてない。
だから require('rollup') だったら、dist/rollup.js を見つけられるけど、 require('rollup/parseAst') だと解決できないんだ。
完全に理解した。完。