vscode中import报cannot find module的根本原因是语言服务未穿透symlink,需在jsconfig.json或tsconfig.json中配置"moduleresolution": "node"、显式include symlink路径,并重启语言服务器。

VSCode 里 import 报 Cannot find module,但文件明明存在、node -e "console.log(require.resolve('xxx'))" 也能正常输出路径——问题不在 Node 运行时,而在 VSCode 的语言服务没穿透 symlink。
jsconfig.json 或 tsconfig.json 必须存在且含 moduleResolution
JavaScript/TypeScript 语言服务默认不启用 Node.js 模块解析逻辑,必须显式声明。没有配置文件,VSCode 就只在已打开的文件里查定义,跨 symlink 的模块直接被忽略。
- 项目根目录下创建
jsconfig.json(JS 项目)或tsconfig.json(TS 项目) - 确保
"compilerOptions": { "moduleResolution": "node" }已设置(TypeScript 项目还需"allowSyntheticDefaultImports": true等兼容项) - 如果 symlink 指向的是外部包(如
npm link my-utils),且该包没有package.json或缺失"types"字段,语言服务无法识别其导出,此时需手动在目标包中补全package.json或生成.d.ts
include 字段必须显式列出 symlink 目录
VSCode 默认不递归扫描 node_modules 或任意软链路径,哪怕它物理上就在工作区里。语言服务只索引 include 中声明的 glob 模式匹配到的路径。
- 在
jsconfig.json的"include"数组中加入 symlink 目标路径,例如:"include": ["src/**/*", "my-linked-package/**/*"] - 路径必须是相对于配置文件所在目录的相对路径;若 symlink 是绝对路径(如
/Users/me/dev/my-utils),需先用ln -s在项目内建一个相对软链(如ln -s /Users/me/dev/my-utils linked-utils),再把"linked-utils/**/*"加入include - 不要依赖
"**/*"全局匹配——某些版本的语言服务会跳过符号链接目录,即使 glob 写对了
调试时 __dirname 和 import.meta.url 行为不一致
Node.js 运行时和 VSCode 调试器对 symlink 的处理视角不同:__dirname 指向 symlink 路径,而 import.meta.url 解析后指向真实路径。这会导致路径拼接失败、断点不命中、require() 找不到模块。
- 在
.vscode/launch.json中必须添加"resolveSourceMapLocations"配置,例如:"resolveSourceMapLocations": ["${workspaceFolder}/**", "!**/node_modules/**"] - 代码中统一用
fileURLToPath(import.meta.url)替代__dirname构造路径,避免因 symlink 导致路径错位 - 动态 require 模块时,优先用
require.resolve()而非字符串拼接路径,它能正确穿透 symlink
重启语言服务比重启 VSCode 更关键
修改配置文件或建立新 symlink 后,VSCode 不会自动重载语言服务索引。你改了 jsconfig.json,但旧缓存还在,报错照旧。
- 按
Ctrl+Shift+P(Windows/Linux)或Cmd+Shift+P(macOS),输入并执行Developer: Restart Language Server - 不要只点「Reload Window」——它不保证语言服务重建索引;只有重启语言服务器才能强制重新解析
include路径和 symlink 结构 - 如果 linked 包更新了导出(比如新增函数),但跳转仍失效,大概率是语言服务没重读它的
.d.ts,此时同样需要重启语言服务器
真正卡住人的不是 symlink 本身,而是语言服务和调试器这两层各自维护一套路径视图,且默认互不同步。配置写对只是前提,触发索引重建才是让改动生效的最后一环。











