本节摘要:免安装模式(Plug'n'Play)是包管理领域最激进的实验:不再生成 node_modules,改用一张映射文件告诉运行时每个包的确切位置——安装退化为"生成查找表"。本节勘验它的两个动机(根治幽灵依赖、砍掉安装 IO)、工作原理、以及生态兼容代价的来源。读完你应能判断自己的项目适不适合这条路线,以及它对包管理未来形态的启示。
免安装模式的出发点是一句质问:我们为什么需要 node_modules? 把 2.3 节的结论摆上来回看:目录布局只是 Node 模块查找算法的物理配套——运行时沿目录逐级向上找包,所以包管理器必须把文件摆对位置。换句话说,node_modules 是"目录查找"这套机制的存在前提,不是天然真理。质问继续:如果让运行时直接被告知"包在哪",目录是不是就不需要了?
免安装模式把这个质问做成了产品:安装后不生成 node_modules,而是生成一张映射文件(.pnp.cjs),记录每个包的确切落点;运行时加载这张表,require 任何包都先查表直达。目录查找被查找表取代,物理布局从"必须正确"降级为"根本不存在"。
映射文件的逻辑结构可以简化成这样(实际内容为可执行脚本,此处示意):
映射表条目: lodash → 指向缓存目录中 lodash 4.17.21 的位置 @matrix/utils → 指向仓库内 packages/utils 源码目录 lodash 的依赖集合 → 声明 lodash 自己能看见哪些包(依赖隔离清单)
注意第三条:映射表不仅记录位置,还记录每个包的可见范围。运行时加载模块前先查表确认"这个引入是否在当前包的可见清单里",不在就直接拒绝。这正是幽灵依赖的死刑判决——2.4 节说的"第三档根治",机制上就是这张可见性清单:未声明的引入在运行时第一秒就被拦截,而不是等部署炸雷。
安装在免安装模式下的形态也随之改变:解压、链接、写目录这些 IO 全部消失,安装器只做两件事——把包放入项目内缓存(或全局缓存),生成映射表。全新安装的速度因此逼近 4.4 节实验里"热缓存"的下限,且配套的零安装选项允许把项目内缓存提交进版本库,克隆即依赖就绪,一次 4.4 节实验的结论在这里被推到了逻辑终点。

免安装模式的每个优点背后都站着同一个代价:生态默认目录存在。加载路径被接管后,凡是自建模块解析的工具都要重新适配——构建器、测试框架、代码检查工具,各家适配进度不一;老版本的某些工具链干脆永远不适配。这就是免安装模式推广多年仍未成为默认选项的核心原因:它要求的不是用户换命令,而是整个工具生态跟着改协议。
适配之外还有两处细节代价。一是调试体验的变化:报错堆栈指向缓存内的路径,初见者容易迷失;二是部分隐式依赖目录形态的库(运行时扫描目录、按约定加载文件)需要显式配置才能工作。这些都不是原理缺陷,而是"改变前提"的必然摩擦。
采用判断可以收敛成三问:项目工具链是否都有成熟适配?团队是否愿意为严格性付出配置成本?是否强烈需要零安装或极致安装速度?三问都是肯定,免安装值得一试;有一问答否,pnpm 的隔离布局(第七章横评详述)能以更小的迁移成本拿到幽灵依赖根治的大半收益。
即便永远不采用免安装模式,它的思想资产也值得收割。其一,它证明了布局不是宿命:node_modules 是历史选择的产物,而历史选择可以被重新谈判——后来者(pnpm 的符号链接布局、运行时内置包管理)都在不同程度上继承了这份谈判成果。其二,它把严格性从约定升级为强制的思路,已经在主流工具的依赖校验、脚本白名单等功能里落地。其三,零安装把"依赖即资产"的视角带入版本管理——依赖不再是环境状态,而是仓库内容的一部分。
免安装模式的真正遗产,不是那几个百分点的安装提速,而是它证明了包管理的"物理形态"是一个可以重新设计的设计空间。
想在一两个仓库里试水免安装,标准动作并不复杂。启用走配置:把工具切到二代版本管理之下,项目配置里把链接器模式设为免安装——一次安装即可生成映射文件,此后 node_modules 不再出现。编辑器需要配套:官方的编辑器适配层负责让类型提示与补全认识映射表,装一次即可。回退更简单——把链接器模式改回常规目录模式,重新安装,目录回来,世界如旧。这正是形态实验的友好之处:它的开关在配置层,不在数据层。
试水期间的观察清单只有三项:构建工具的兼容性(各家对新解析协议的适配版本要求)、脚本与工具链里有无硬编码目录路径、以及持续集成环境的缓存配置是否需要随项目内缓存调整。三项全绿,再讨论是否扩大试点——顺序不要反,扩大试点的成本永远高于退出的成本。
免安装不是幽灵依赖的唯一解,pnpm 的隔离布局(7.1 节)是另一条路线——两者严格性的来源不同,值得放在一起对表。严格性的机制不同:隔离布局靠物理结构让未声明的包"找不到"——每个包的目录里只链接自己声明的依赖;免安装靠运行时查表让未声明的包"加载不了"——一个在文件系统层设卡,一个在模块加载层设卡。兼容性成本不同:隔离布局对绝大多数工具透明(链接对进程而言就是普通目录),摩擦集中在少数硬编码路径的场景;免安装接管了加载协议,需要全工具链适配。迁移成本不同:隔离布局基本是换工具即得,免安装需要配置与生态核对(见上一节的三项观察)。
选择的经验法则:优先用隔离布局拿走大部分收益,把免安装留给对安装速度与严格性都有极致需求、且工具链可控的项目。两条路线殊途同归的地方在于理念——把"未声明即不可用"从团队约定升级为机器强制,这正是 2.4 节治理清单的终极形态。
机制层面的实验看完了,下一节回到声明层收尾:五种依赖字段各自的精确语义——这是写库与写插件的人每天都要面对、也最容易想当然的一层。