npm 和 Yarn Classic 默认采用扁平化提升策略,将尽可能多的依赖(包括传递依赖)提升到 node_modules 根目录。这种设计的初衷是减少目录嵌套深度,兼容早期 Node.js 的模块解析行为。其副作用是项目代码可以访问任意层级的依赖,形成幽灵依赖,同时同一依赖的多个版本无法并存,可能引发版本冲突。
pnpm 默认将依赖提升到 node_modules/.pnpm/node_modules(隐藏目录),而非根目录。这意味着依赖对 node_modules 内部的包可见,但对外部代码不可见,在兼容性与隔离性之间取得平衡。用户可通过 .npmrc 配置灵活调整提升行为:hoist=false 完全禁止提升,实现最严格隔离;public-hoist-pattern[] 将匹配模式的包提升到根目录,用于兼容特定工具;shamefully-hoist=true 则将所有依赖提升到根目录,行为等同于 npm。
大多数现代工具(Vite、Webpack 5、TypeScript 等)已兼容 pnpm 的非扁平结构。少数依赖扁平化结构的工具(如 React Native 的 Metro 打包器、部分 Webpack 4 配置、某些 serverless 部署环境)需要借助 node-linker=hoisted 配置生成无符号链接的扁平 node_modules,此时会失去幽灵依赖保护,但保留 pnpm 的 store 和锁文件优势。