pnpm で “Cannot find module” が出る・npm では動くのに動かない
pnpm に移行したとたん、こんなエラーに遭遇したことはありませんか?
Error: Cannot find module 'lodash'
Require stack:
- /app/src/utils.js
package.json には lodash を書いていないのに npm/yarn では動いていた。でも pnpm では動かない——その原因は phantom dependency(幽霊依存) です。
phantom dependency(幽霊依存)とは何か
npm/yarn の node_modules は「フラット構造」
npm や yarn(v1)は node_modules をフラットに展開します。たとえば express をインストールすると、express が依存する lodash も node_modules/lodash に展開されます。
node_modules/
express/
lodash/ ← express の依存なのに直接 require できてしまう
body-parser/
このため、package.json に lodash を書いていなくても require('lodash') が成功してしまいます。これが phantom dependency(package.json に明示していないのに使えてしまう依存)です。
pnpm の node_modules は「厳格な symlink 構造」
pnpm は独自の node_modules 構造を使います。直接依存しているパッケージだけがプロジェクトの node_modules に symlink され、間接依存は node_modules/.pnpm/ 以下に隔離されます。
node_modules/
express -> .pnpm/express@4.x/node_modules/express (symlink)
.pnpm/
express@4.x/
node_modules/
express/
lodash/ ← express の依存。プロジェクトから直接は require できない
この構造により、package.json に書いていないパッケージは require できなくなります。これが pnpm の正しい動作であり、npm/yarn の「幽霊依存」を許していた状態のほうが問題でした。
よくある症状とエラーメッセージ
pnpm 移行直後に以下のエラーが出たら phantom dependency が原因の可能性が高いです。
Error: Cannot find module 'lodash'
Error: Cannot find module 'uuid'
Error: Cannot find module 'axios'
Error: Cannot find module 'dayjs'
または TypeScript の場合:
Cannot find type definition file for 'node'.
Could not find a declaration file for module 'lodash'.
原因1:直接依存していないパッケージを使っている(最も多い)
診断方法
使っているパッケージが package.json の dependencies または devDependencies に書かれているか確認します。
# 例: lodashを使っているのにpackage.jsonにない場合
cat package.json | grep lodash # 何も出てこない → phantom dependency
正しい対処法:package.json に直接依存を追加する
# 本番コードで使う場合
pnpm add lodash
# 開発・ビルドツールとして使う場合
pnpm add -D lodash
型定義が別パッケージの場合は合わせて追加します。
pnpm add lodash
pnpm add -D @types/lodash
これが唯一の正しい解決策です。phantom dependency に頼っていたコードは、そもそも依存関係が不正な状態でした。
原因2:応急処置として shamefully-hoist を使う(非推奨だが移行期に有効)
大量の phantom dependency があり、すぐに全部修正できない場合の応急処置として .npmrc に以下を追加する方法があります。
# .npmrc
shamefully-hoist=true
これにより pnpm も npm/yarn と同様のフラット構造で node_modules を展開するため、phantom dependency が解消されます。
ただし、この設定は非推奨です。 pnpm の厳格な依存管理の恩恵(高速インストール・ディスク節約・依存の明確化)が失われます。あくまで移行期の一時的な対処として使い、段階的に正しい依存関係に修正していくことを推奨します。
# .npmrc変更後は再インストールが必要
pnpm install
原因3:pnpm workspace でパッケージ間の依存が解決できない
monorepo 構成(pnpm workspace)では、ワークスペース内のパッケージ同士の依存も明示的に宣言する必要があります。
ディレクトリ構成例
packages/
ui/
package.json (name: "@myapp/ui")
web/
package.json (@myapp/uiを使いたい)
pnpm-workspace.yaml
症状
packages/web から @myapp/ui を import しようとすると:
Error: Cannot find module '@myapp/ui'
対処法:workspace: プロトコルで依存を宣言する
packages/web/package.json に明示的に依存を追加します。
{
"name": "@myapp/web",
"dependencies": {
"@myapp/ui": "workspace:*"
}
}
追加後に pnpm install を実行します。
pnpm install
workspace:* はワークスペース内の最新バージョンを参照する特殊なプロトコルです。
原因4:@types/node など型定義パッケージが解決できない
TypeScript プロジェクトで以下のエラーが出ることがあります。
Cannot find type definition file for 'node'.
これも phantom dependency の一種で、npm では別のパッケージが @types/node を間接的にインストールしていたため動いていたケースです。
pnpm add -D @types/node
tsconfig.json の types フィールドも確認します。
{
"compilerOptions": {
"types": ["node"]
}
}
phantom dependency を一括で検出する方法
移行前に phantom dependency を洗い出すには pnpm の --strict-peer-dependencies オプションや、専用ツールを使います。
方法1: pnpm の dry-run で確認
# インストール前に問題を確認(実際にはインストールしない)
pnpm install --dry-run
方法2: depcheck で未宣言の依存を検出
pnpm add -D depcheck
npx depcheck
出力例:
Unused dependencies
* some-package
Missing dependencies
* lodash
* uuid
* axios
“Missing dependencies” に出てきたパッケージが phantom dependency です。これを元に pnpm add で追加していきます。
npm/yarn から pnpm への移行チェックリスト
package-lock.jsonまたはyarn.lockを削除してpnpm importまたはpnpm installでロックファイルを生成depcheckを実行して phantom dependency を洗い出す- 不足している依存を
pnpm addで追加 - ビルド・テストを実行してエラーがないか確認
- 応急処置として
shamefully-hoist=trueが必要な場合は設定し、段階的に削除する
# 移行手順の例
rm package-lock.json # または yarn.lock
pnpm import # 既存のロックファイルから pnpm-lock.yaml を生成
pnpm install
npx depcheck # phantom dependency を確認
まとめ
| 症状 | 原因 | 対処法 |
|---|---|---|
| npm では動くのに pnpm で “Cannot find module” | phantom dependency | pnpm add で明示的に依存追加 |
| 大量の phantom dependency で移行が大変 | フラット構造への依存 | 一時的に shamefully-hoist=true(後で削除) |
| workspace で別パッケージが見つからない | workspace 間の依存未宣言 | "workspace:*" で依存を宣言 |
| TypeScript の型定義が見つからない | @types パッケージが未宣言 | pnpm add -D @types/xxx |
pnpm の厳格な依存管理は一見不便に感じますが、依存関係を正しく宣言するよい機会です。phantom dependency を放置すると、間接依存のバージョンアップ時に突然壊れるリスクがあります。depcheck を使って一括検出し、正しい package.json に整理しましょう。
