资讯动态

pnpm11 @pnpm/installing.modules-yaml:node_modules/.modules.yaml 状态文件的读写实现详解

发布时间:2026/9/20 15:38:53 来源:尧图企业网站定制
pnpm11 pnpm/installing.modules-yamlnode_modules/.modules.yaml 状态文件的读写实现详解【免费下载链接】pnpmFast, disk space efficient package manager项目地址: https://gitcode.com/gh_mirrors/pn/pnpmnode_modules/.modules.yaml是 pnpm 安装器在每次安装后落盘的一份“modules 状态清单”记录虚拟存储位置、提升策略、跳过/待构建的包等关键信息pnpm/installing.modules-yamlpnpm11 工作区包就是负责读取与写入这份文件的唯一权威模块。本文以 包 README 为骨架结合 源码实现 与 测试用例完整讲解其文件命名由来、数据字段、向后兼容迁移逻辑、写入时的字段裁剪策略以及它在 pnpm11 安装流程中的下游消费方。包的定位与安装方式README 给出的安装与用法非常直接pnpm add pnpm/installing.modules-yamlimport {write, read} from pnpm/installing.modules-yaml await write(node_modules, { hoistedAliases: {}, layoutVersion: 1, packageManager: pnpm1.0.0, pendingBuilds: [], shamefullyFlatten: false, skipped: [], storeDir: /home/user/.pnpm-store, }) const modulesYaml await read(node_modules)README 的 API 概览只有两条read(pathToDir): PromiseModulesObject—— 从指定目录读取.modules.yamlwrite(pathToDir, ModulesObject): Promisevoid—— 向指定目录写入.modules.yaml。需要说明的是README 中的read/write是对外语义的简化写法源码实际导出的函数名 是readModulesManifest(modulesDir)与writeModulesManifest(modulesDir, modules)。从 package.json 可以确认该包的信息包名pnpm/installing.modules-yaml描述为 “Reads/writesnode_modules/.modules.yaml”当前版本1101.0.2运行入口为lib/index.jsmain纯 ESMtype: module运行时依赖只有 5 个pnpm/fs.graceful-fs、pnpm/types、is-windows、ramda、read-yaml-file引擎要求node: 22.13。文件命名与存储格式为什么叫 .modules.yaml 却常是 JSON源码中文件名是一个常量并附带了关键的注释解释src/index.ts#L14-L16// The dot prefix is needed because otherwise npm shrinkwrap // thinks that it is an extraneous package. const MODULES_FILENAME .modules.yaml点前缀的作用是让npm shrinkwrap这类工具不会把该文件误判为一个“多余的安装产物”。更值得注意的是存储格式。虽然文件名是.yaml但当前版本写出的内容其实是JSONJSON 本身是合法 YAML 子集await fs.mkdir(modulesDir, { recursive: true }) await fs.writeFile(modulesYamlPath, JSON.stringify(saveModules, null, 2))而读取端采用“先 JSON、后 YAML”的双路解析策略src/index.ts#L55-L69先按 UTF-8 读入文件内容尝试JSON.parse若 JSON 解析失败回退到read-yaml-file按 YAML 解析——源码注释明确写道 “Manifests written by old pnpm versions are YAML”即旧版 pnpm 写的是真正的 YAML这一回退就是为兼容历史文件而保留的文件不存在ENOENT时返回null而不是抛错空文件0 字节会返回undefined而不报错这一点由 fixtures/empty-modules-yaml 中那个空的.modules.yaml对应测试用例test/index.ts#L131-L134验证但解析不了的非空文件必须抛错。测试用例里有一段很有信息量的注释test/index.ts#L136-L142// Callers must not mistake an unreadable state file for a missing one: // that reads as layout drift and purges node_modules on every install. const modulesDir temporaryDirectory() fs.writeFileSync(path.join(modulesDir, .modules.yaml), not: [valid) await expect(readModulesManifest(modulesDir)).rejects.toThrow()换言之如果读取端把“损坏的状态文件”误当成“没有状态文件”上层安装器会把它理解为 node_modules 布局漂移进而每次安装都清空重建 node_modules——所以这里必须 fail loudly。另外还有一条针对 JSON 特性的防御重复 key 的 JSON 对象按“最后一个值生效”处理对应测试 构造了同一条超长 dep path 出现两次的 JSON断言读取结果保留的是后一个值public。Modules 数据结构字段逐项解析ModulesRaw接口src/index.ts#L22-L46定义了状态文件的完整字段集导出的Modules类型在其基础上把ignoredBuilds从字符串数组改成了Set。逐字段说明如下字段类型含义hoistedDependenciesHoistedDependencies核心字段dep path → 别名到public/private提升位置的映射决定哪些包出现在 node_modules 根hoistPattern/publicHoistPatternstring[]可选提升模式与公共提升模式与 pnpm 的hoist-pattern/public-hoist-pattern配置对应includedRecordDependenciesField, boolean记录本次安装包含了哪几类依赖dependencies/devDependencies/optionalDependencieslayoutVersionnumbernode_modules 布局版本号是上层判断“能否复用现有 node_modules”的关键依据nodeLinkerhoisted \| isolated \| pnp可选记录本次安装使用的 node_modules 链接策略packageManagerstring写入该文件时的包管理器版本如pnpm5.1.8pendingBuildsstring[]尚未执行构建脚本的包列表ignoredBuildsDepPath[]读入后为SetDepPath被忽略构建脚本的 dep path 集合skippedstring[]因平台不匹配等原因被跳过的包prunedAtstring最近一次修剪时间UTC 字符串storeDirstring全局内容寻址存储CAFS store路径virtualStoreDirstring项目内虚拟存储目录默认modulesDir/.pnpmvirtualStoreDirMaxLengthnumber虚拟存储路径长度上限缺省值 120injectedDepsRecordstring, string[]可选注入依赖的映射记录hoistedLocationsRecordstring, string[]可选提升位置的补充记录allowBuildsRecordstring, boolean \| string可选构建脚本的审批状态virtualStoreOnlyboolean可选标记该 node_modules 由virtualStoreOnly安装如pnpm fetch产生此时记录的提升模式被强制置空下次安装时不得与用户配置比较hoistedAliases/shamefullyHoist—仅作向后兼容保留读取旧文件时用于迁移见下一节读取端的字段规范化逻辑src/index.ts#L70-L112会补齐若干默认值virtualStoreDir缺失时补为path.join(modulesDir, .pnpm)若是相对路径则相对modulesDir解析为绝对路径prunedAt缺失时补为当前 UTC 时间virtualStoreDirMaxLength缺失时补为120。向后兼容从 pnpm 5 的 shamefully-hoist 到 hoistedDependencies.modules.yaml的历史格式经历过一次重要变化旧版如 pnpm 5使用shamefullyHoist布尔值加hoistedAliases映射来表达提升关系新版改用结构化的hoistedDependencies。readModulesManifest中专门有一段switch (modules.shamefullyHoist)迁移逻辑src/index.ts#L79-L105shamefullyHoist: true时若publicHoistPattern缺失则补为[*]若只有hoistedAliases而没有hoistedDependencies则把每个别名映射为publicshamefullyHoist: false时若publicHoistPattern缺失则补为[]同样把hoistedAliases迁移为hoistedDependencies但所有别名标为private。仓库里保留了真实的旧版 fixture 文件可以直接对照阅读。old-shamefully-hoist/.modules.yaml 是一个由pnpm5.1.8写出的 YAML 文件hoistPattern: - * hoistedAliases: /accepts/1.3.7: - accepts /array-flatten/1.1.1: - array-flatten /body-parser/1.19.0: - body-parser included: dependencies: true devDependencies: true optionalDependencies: true layoutVersion: 4 packageManager: pnpm5.1.8 pendingBuilds: [] registries: default: https://registry.npmjs.org/ shamefullyHoist: true skipped: [] storeDir: /home/zoli/.pnpm-store/v3 virtualStoreDir: .pnpmold-no-shamefully-hoist/.modules.yaml 则是shamefullyHoist: false的对应版本。两条向后兼容测试test/index.ts#L80-L104断言读入前者后publicHoistPattern变为[*]且三个包全部标记为public读入后者后publicHoistPattern为[]且全部标记为private。这两个 fixture 也顺带展示了旧文件的两个特征内容是纯 YAML 而非 JSON以及包含一个新版写入时会被主动丢弃的registries字段见下文。写入逻辑字段裁剪、排序与 Windows 特判writeModulesManifestsrc/index.ts#L115-L149在落盘前做了一系列确定性处理保证同一状态反复写入时字节稳定、且不带入过期信息ignoredBuilds从Set还原为数组——Set无法被 JSON 序列化落盘前Array.from展开skipped排序——if (saveModules.skipped) saveModules.skipped.sort()消除数组顺序带来的无意义 diff删除空值字段hoistPattern为null或空串时删除注释说明 YAML 写作者无法处理undefined字段publicHoistPattern为null时删除virtualStoreOnly为假时删除hoistedAliases在“为 null或两个 hoist pattern 均为 null”时删除主动丢弃registries字段——源码注释解释得很清楚src/index.ts#L136-L139pnpm 11 及更早版本会把上次安装使用的 registry 记录在文件里而现在 registry 直接从项目配置读取因此旧文件里残留的registries会在首次重写时丢失避免持有一份配置变更后立即过期的拷贝。这一点有专门的测试验证test/index.ts#L144-L171写入一个带registries: { default: ... }的 manifest 后读回原始文件断言registries字段已不存在virtualStoreDir的绝对/相对路径处理src/index.ts#L140-L146// We should store the absolute virtual store directory path on Windows // because junctions are used on Windows. Junctions will break even if // the relative path to the virtual store remains the same after moving // a project. if (!isWindows()) { saveModules.virtualStoreDir path.relative(modulesDir, saveModules.virtualStoreDir) }非 Windows 平台写相对路径通常是.pnpmWindows 平台保留绝对路径。原因是 Windows 上 pnpm 使用 junctionjunction 在“项目目录被移动而虚拟存储相对位置不变”时同样会失效所以必须记录绝对路径才能正确校验。测试用例test/index.ts#L36-L39正验证了这一点path.isAbsolute(raw.virtualStoreDir)应当等于isWindows()的返回值 6. 最后fs.mkdir(modulesDir, { recursive: true })确保目录存在再以JSON.stringify(saveModules, null, 2)写入 2 空格缩进的 JSON。测试矩阵从往返一致到长路径场景test/index.ts 覆盖了该包的全部关键行为可以视为一份“可验证的规格书”往返一致性writeModulesManifestreadModulesManifest的结果与输入深度相等round-trip包括node_modules目录不存在的场景L106-L129超长 dep path构造scope/package1.0.0(${p.repeat(1001)})这样的 dep path验证带 1000 字符 peer 依赖片段的条目也能完整往返L41-L67——这类长路径正是 pnpm 虚拟存储目录名长度管理virtualStoreDirMaxLength缺省 120要面对的现实损坏文件必须拒绝与空文件容错见前文旧格式迁移两个 pnpm 5 的 fixture 验证shamefullyHoist/hoistedAliases到hoistedDependencies的迁移正确性registries字段剥离验证新写入不再携带旧版的 registry 快照。谁在消费这个包pnpm11 中的下游引用pnpm/installing.modules-yaml是 pnpm11 中多处安装链路的公共依赖从各包的package.json依赖关系看均为workspace:*依赖主要消费方包括installing/deps-installer/src/install/validateModules.ts 与 installing/deps-installer/src/install/link.ts安装器在安装前后读取/重写该文件校验现有 node_modules 与当前配置的兼容性building/after-install/src/index.ts 与 building/commands/src/policy/approveBuilds.ts、getAutomaticallyIgnoredBuilds.ts安装后构建脚本的审批/忽略策略会读写pendingBuilds、ignoredBuilds、allowBuilds等字段workspace/injected-deps-syncer/src/index.ts工作区注入依赖的同步逻辑依赖其中的injectedDeps记录installing/deps-restorer/src/index.ts 与 installing/context/src/index.ts锁文件快速恢复与安装上下文构建时读取该状态以决定能否复用 node_modules此外deps/graph-builder、deps/inspection/tree-builder、global/commands、patching/commands等包也在依赖列表中见 pnpm11/deps/graph-builder/package.json 等说明它同时服务于构建依赖图与检查类命令。从源码结构看.modules.yaml实际上是 pnpm 判断“node_modules 是否仍有效”的唯一事实来源layoutVersion、hoistedDependencies、included等字段共同决定了安装器是走完整重建、增量修补还是直接跳过。理解这个包的读写行为也就理解了 pnpm 增量安装机制的底层契约。参考文件索引README包的对外文档安装、用法、API 概览MIT 许可src/index.tsreadModulesManifest/writeModulesManifest完整实现与Modules类型定义test/index.ts往返、长路径、损坏文件、旧格式迁移等全部测试package.json包元信息、依赖与引擎要求test/fixtures/old-shamefully-hoist/.modules.yaml / old-no-shamefully-hoist/.modules.yaml / empty-modules-yamlpnpm 5 时代的真实 YAML 样本与空文件样本【免费下载链接】pnpmFast, disk space efficient package manager项目地址: https://gitcode.com/gh_mirrors/pn/pnpm创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价