← 記事一覧に戻る

pnpm で "Cannot find module" が出る・npm では動くのに pnpm で動かない時の原因と対策【phantom dependency / .npmrc】

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 が依存する lodashnode_modules/lodash に展開されます。

node_modules/
  express/
  lodash/      ← express の依存なのに直接 require できてしまう
  body-parser/

このため、package.jsonlodash を書いていなくても 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.jsondependencies または 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.jsontypes フィールドも確認します。

{
  "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 への移行チェックリスト

  1. package-lock.json または yarn.lock を削除して pnpm import または pnpm install でロックファイルを生成
  2. depcheck を実行して phantom dependency を洗い出す
  3. 不足している依存を pnpm add で追加
  4. ビルド・テストを実行してエラーがないか確認
  5. 応急処置として 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 に整理しましょう。