🧩

VSCodeでnode_modulesから始まるパスが補完される問題とTS Serverの仕組み

に公開

🧠 はじめに

VSCodeを使っていて、クイックフィックス(インポート補完)でnode_modules/から始まるパスが出てくる
この他でも、なんか補完がうまくいかない…とモヤるのはあるあるですよね。

同じ構成のリポジトリでも 補完が正しく動く場合と動かない場合 があり、原因がつかみにくい問題です。

この記事では、実際に遭遇したケースをもとに

  • VSCodeのクイックフィックスがどこを参照しているか
  • なぜnode_modules/が補完に出るのか
  • 原因となったexportsの書き方とその直し方

を解説します。

ちょっと細かい話ですが、似た問題に直面している方のお役に立てればと思います!

🧩 環境と前提

  • TurboRepo構成
  • React(TypeScript)
  • VSCode使用
  • Node.js 18+(動作確認は v23.10.0)
  • monorepo 構成:
apps/
|- web/
`- admin/
packages/
|- ui/
`- utils/

💥 起きていた問題

apps/web 配下でクイックフィックスを使うと、こんな補完が出てきました👇

import { Button } from 'node_modules/@my-org/ui/src/Button/Button';

ほんとはこうなってほしいのに😣:

import { Button } from "@my-org/ui/Button";

同じTurboのチュートリアルで作った別のリポジトリでは正常に補完されたため、「環境」ではなく「設定」に原因がありそうです。

🔍 疑ったポイント

  1. VSCodeのキャッシュ(TS Server再起動)
  2. tsconfig.jsonbaseUrl / paths の指定漏れ
  3. package.json"name""type" の設定
  4. exports の書き方

調査の結果、exports フィールドの記述方法が原因でした。

🧭 そもそも、VSCodeのクイックフィックスはどこを見てるのか?

VSCodeで補完が動くとき、内部的に TypeScriptTS Server がモジュールを探索して提案を出しています。

では、モノレポの場合 TS Server はどの階層のパスを探索するのか、
実際にプロジェクトの構造を確認しながら挙動を調べてみました。

$pnpm i でパッケージのインストールを行った後、@my-org/ui のコピーが apps 配下それぞれと packages 配下の実態で合わせて3箇所に存在しました。

apps/
|- web/
| `- node_modules/
|   `- @my-org/ui
`- admin/
  `- node_modules/
    `- @my-org/ui
packages/
`- ui/

試しに一番近い apps/web/node_modules を削除したところ、補完ではパッケージの提案そのものが出てこなくなりました。

この挙動から TS Serverpackage.json をもとにパッケージ境界を判断していて、その中で完結する依存関係だけを見ていそうです。

⚙️ packages配下で補完が効くもの・効かないもの

packages/uipackages/utils のように複数のパッケージがある場合、
一部では正常に補完され、一部ではnode_modulesパスが出る現象がありました。

差異を見たところ exports の設定に違いがありました。
以下のような package.json の記述だと、VSCode側が解釈できないようです。

// ❌ 補完が壊れる例
{
  "exports": {
    "./*": {
      "import": ["./src/*/*.ts"]
    }
  }
}

今回の場合は、*/*のように*を複数回使った書き方が TS Server では読み取れなかったようです。ビルドは通るのがはまったポイントでもありました…🥲

✅ 修正例: TS Server が解釈できる exports

TypeScriptTS Server は、Node.jsの完全な exports 仕様をカバーしていません。
VSCodeの補完は TS Server に依存しているため、 TS Server が読める形式に書く必要があります。

// ✅ 補完が正しく動く例
{
  "exports": {
    "./*": {
      "import": "./src/*/index.ts"
    }
  }
}

これにより、クイックフィックスで補完されるパスが @my-org/ui の形式になり、 node_modules/ 経由では出てこなくなります。

🧩 まとめ

  • VSCodeのクイックフィックスは、TS Server の解決結果を使っている
  • TS Server は「一番近い node_modules」だけを見る(なければ補完しない)
  • exports の書き方が TS Server 非対応だと、node_modules パスで補完される

✏️ おわりに

インポートの問題は今すぐ対処しなくて良くても、イラぁ…とくる頻度は多いんですよね😇
裏側の仕組みを知ると、次に似たトラブルに出会ってもすぐ原因を探せるようになるかなと思います。

同じ現象に悩んでいる人の助けになれば幸いです🙏

Discussion