npm link 不生效的根本原因是它只创建符号链接,不触发构建、不刷新缓存、不重载模块,且运行时仍加载 lib/dist 产物而非 src 源码,需手动调整 main 字段、禁用 symlinks 解析、清空缓存并重启服务。

直接改 node_modules 里的代码,npm link 后依然不生效?不是你改错了,而是链路没打通——npm link 只建立符号链接,不触发构建、不刷新缓存、不重载模块,尤其在 Webpack/Vite 等工具下,修改几乎必然被忽略。
npm link 后为什么改源码没反应?
根本原因在于:link 只是让项目“引用”本地包的源码目录,但实际运行时加载的仍是已打包/编译后的产物(如 lib/ 或 dist/),而非你正在编辑的 src/。尤其当被 link 的包本身有构建流程(比如 postinstall 编译、TS 输出到 lib),你的 src 修改完全不会进入运行时。
- Webpack/Vite 默认从
main或module字段指定的入口加载,通常是lib/index.js,不是src/index.ts - ESM 模块解析会优先找
exports字段定义的路径,link 后仍走原路径 - 开发服务器缓存了已解析的模块树,不监听 symlink 目录下的变化
如何让 linked 包的 src 修改实时生效?
核心思路:绕过构建产物,强制开发环境加载源码,并确保 HMR 能捕获变更。
- 在被 link 的包中,把
package.json的main字段临时改成指向src/index.ts(或src/index.js),同时确保types字段也同步调整 - 如果用 TypeScript,删掉
lib/和dist/目录,并在项目中启用skipLibCheck: true避免类型报错 - 在主项目中,检查是否启用了
resolve.symlinks: false(Webpack)或resolve.symlinks = false(Vite),否则 symlink 会被自动解析为真实路径,失去热更新能力 - Vite 用户可在
vite.config.ts中加:server.watch.ignored = ['!**/your-linked-package/src/**'],防止被误忽略
常见踩坑点:link + 构建工具组合失效
你以为 link 完就万事大吉,其实构建工具早就在背后悄悄“绕开”了你。
-
webpack --watch默认不监听node_modules下的 symlink 目录,需显式配置watchOptions.followSymlinks = true - Vite 的
optimizeDeps.include若包含该包,它会被预构建进node_modules/.vite/deps/,后续修改完全无效——必须清空node_modules/.vite并重启 dev server - React/Vue 项目若开启
fastRefresh,它只对当前项目内文件生效,linked 包需手动加react-refresh/babel插件并确保 Babel 处理 symlink 路径 - VSCode 的 IntelliSense 可能仍按
node_modules/xxx/lib提示,需在tsconfig.json的paths中手动映射:"your-package": ["../your-package/src"]
更稳的替代方案:pnpm workspace 或 alias
长期依赖 npm link 调试,迟早撞墙。真正可持续的做法是放弃 symlink,改用工程化方式接入本地包。
- 用
pnpm workspace:把包和项目放在同一 monorepo 下,pnpm install后天然共享源码,无需 link,修改即生效 - Webpack 中用
resolve.alias指向本地路径:{ 'your-package': path.resolve(__dirname, '../your-package/src') } - Vite 中等价配置:
resolve.alias = { 'your-package': new URL('../your-package/src', import.meta.url).pathname } - 如果必须用 npm,可用
patch-package+prestart脚本替代 link,避免终端状态干扰
最常被忽略的一点:即使所有配置都对,VSCode 终端里启动的 dev server 仍可能沿用旧进程缓存。每次改完 linked 包,别只刷新浏览器——关掉终端、杀掉所有 node 进程、再 npm run dev。否则,你在 src 里敲了十次保存,实际跑的还是第一次 link 时的快照。











