文章目录
- 前言
- 1. npm/yarn 现在还有幽灵依赖问题吗?
- 2. pnpm 解决了幽灵依赖问题吗?
- 3. pnpm 是如何解决的?
- 举例说明
- 1. pnpm 的 node_modules 结构原理
- 结构示意
- 2. 实际演示幽灵依赖的杜绝
- 步骤1:初始化项目并安装依赖
- 步骤2:在代码中引入未声明的依赖
- 步骤3:运行代码
- 3. 查看 pnpm 的 node_modules 结构
- 4. 总结
前言
1. npm/yarn 现在还有幽灵依赖问题吗?
有。
- npm(包括 v9)和 yarn(classic/berry)默认依然采用“扁平化 node_modules”结构。
- 只要依赖被扁平化安装到顶层 node_modules,项目代码就可以直接 require/导入未在本项目 package.json 声明的依赖(即幽灵依赖)。
- 只要依赖树发生变化(比如间接依赖被移除),你的代码就会因为找不到依赖而报错。
2. pnpm 解决了幽灵依赖问题吗?
pnpm 基本解决了这个问题。
- pnpm 采用了“严格的 node_modules 隔离”机制(hoisting less,严格依赖树)。
- 每个包只能访问自己 package.json 里声明的依赖,不能直接 require 间接依赖。
- 如果你在代码里 require 了未声明的依赖,pnpm 安装后运行会直接报错(找不到模块)。
3. pnpm 是如何解决的?
- pnpm 在 node_modules 下采用了“软链接+隔离”结构。
- 每个包的 node_modules 只包含自己声明的依赖(通过 symlink 指向全局 store),不会自动把所有依赖都扁平化到顶层。
- 这样,只有在 package.json 里声明的依赖才能被 require 到,未声明的依赖不会被自动暴露出来。
举例说明
假设你的 package.json 没有声明 moment
,但代码里用了:
const moment = require('moment');
- 用 npm/yarn 安装,如果有其他依赖间接依赖了 moment,代码可能能跑。
- 用 pnpm 安装,运行时会直接报错:
Cannot find module 'moment'
。
1. pnpm 的 node_modules 结构原理
- pnpm 不像 npm/yarn 那样把所有依赖都“扁平化”到顶层 node_modules。
- pnpm 会在项目的 node_modules 下为每个包建立一个“虚拟隔离环境”,每个包只能访问自己 package.json 里声明的依赖。
- 具体实现方式是:pnpm 在 node_modules 里用符号链接(symlink)指向全局 store 里的真实包内容,并且只为声明的依赖建立链接。
结构示意
假设你的项目结构如下:
project/
node_modules/
.pnpm/
react@18.1.0/
lodash@4.17.21/
react -> .pnpm/react@18.1.0/node_modules/react
lodash -> .pnpm/lodash@4.17.21/node_modules/lodash
footer/
node_modules/
react -> ../../../.pnpm/react@18.1.0/node_modules/react
.pnpm
目录存放所有真实包内容。node_modules/react
是指向.pnpm/react@18.1.0/node_modules/react
的符号链接。- 每个包(如 footer)自己的 node_modules 只包含自己声明的依赖的链接。
2. 实际演示幽灵依赖的杜绝
步骤1:初始化项目并安装依赖
pnpm install
步骤2:在代码中引入未声明的依赖
假设 footer
包的 package.json 没有声明 moment
,但你在代码里写了:
const moment = require('moment');
步骤3:运行代码
pnpm run start
结果:
Error: Cannot find module 'moment'
pnpm 会直接报错,因为 footer
的 node_modules 里没有 moment
的符号链接。
3. 查看 pnpm 的 node_modules 结构
你可以用如下命令查看符号链接结构:
tree /F node_modules
在 Windows 的 cmd 终端中,你可以使用 cd 命令切换到指定目录。
具体命令如下:
cd /d d:\js\getting-started-example
/d 参数用于切换驱动器(比如从 C: 切换到 D:)。
你会发现只有声明的依赖才会出现在每个包的 node_modules 里,未声明的依赖不会被暴露出来。
4. 总结
-
pnpm 通过“符号链接+隔离”机制,保证每个包只能访问自己声明的依赖,彻底杜绝了幽灵依赖。
-
这样可以极大提升依赖安全性和可维护性,适合大型 Monorepo 项目。
-
npm/yarn:幽灵依赖问题依然存在,需靠开发者自律和工具检测(如 depcheck)。
-
pnpm:通过 node_modules 隔离机制,从根本上杜绝了幽灵依赖,强制每个包声明自己的依赖。