ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Nx 锁文件解析器测试夹具生成指南:为 NPM / Yarn / Pnpm 构建可维护的 Mock 数据

Nx 锁文件解析器测试夹具生成指南:为 NPM / Yarn / Pnpm 构建可维护的 Mock 数据 Nx 锁文件解析器测试夹具生成指南为 NPM / Yarn / Pnpm 构建可维护的 Mock 数据【免费下载链接】nxThe Monorepo Platform that amplifies both developers and AI agents. Nx optimizes your builds, scales your CI, and fixes failed PRs automatically. Ship in half the time.项目地址: https://gitcode.com/GitHub_Trending/nx/nxNx 在构建项目依赖图时需要解析package-lock.json、yarn.lock、pnpm-lock.yaml与bun.lock等锁文件并把其中的外部依赖映射为项目图节点。为了对这套解析逻辑进行稳定、可复现的单元测试Nx 仓库在 packages/nx/src/plugins/js/lock-file/fixtures目录下维护了一整套夹具fixtures数据而fixtures/README.md 正是这套夹具的生成手册它给出两个可直接运行的 Node.js 脚本分别用于从真实的node_modules中抽取数据生成 NPM V1 与 Yarn/Pnpm 两类 mock。读完本文你将掌握这套夹具的生成原理、脚本的逐行语义以及它们与 Nx 锁文件解析器npm-parser、yarn-parser、pnpm-parser、bun-parser之间的对应关系从而能够为解析器的回归测试独立生产新的夹具数据。夹具在 Nx 锁文件解析体系中的位置在深入脚本之前先明确这些夹具服务于什么。Nx 的锁文件功能集中在 packages/nx/src/plugins/js/lock-file 目录其核心入口 lock-file.ts 对外暴露了统一的 APIgetLockFileNodes()解析锁文件内容生成ProjectGraphExternalNode外部节点集合Nx 用它把每个 npm 包建模为图中的一个外部节点getLockFileDependencies()解析锁文件生成RawProjectGraphDependency依赖边getLockFileName()/lockFileExists()根据检测到的包管理器npm / yarn / pnpm / bun返回对应的锁文件名与存在性判断createLockFile()/generatePrunedDeployOutput()在generate-package-json流程中生成剪枝后pruned的锁文件只保留部署目标真正依赖的包。不同包管理器的锁文件格式差异极大npm 是 JSONv1/v2/v3 三种结构yarn 是自定义文本格式v1 与 berry 又有差异pnpm 是 YAMLv6/v9bun 则是二进制或文本格式。因此解析逻辑被拆分到四个独立解析器中各自拥有对应的单元测试解析器源码对应测试支持的锁文件npm-parser.tsnpm-parser.spec.tspackage-lock.jsonv1/v2/v3yarn-parser.tsyarn-parser.spec.tsyarn.lockv1 与 berrypnpm-parser.tspnpm-parser.spec.tspnpm-lock.yamlv6/v9bun-parser.tsbun-parser.spec.tsbun.lock/bun.lockb这些 spec 文件从fixtures导入.fixture与.ts模板字符串形式的锁文件内容构造出各种真实场景workspaces、peer 依赖、可选依赖、重复包、git/alias 依赖、pnpm 剪枝回归等。夹具数据是否贴近真实node_modules布局直接决定了这些测试的价值——这正是 README 中两个生成脚本存在的意义它们不是手工编写的示例而是从真实安装目录中提取出来的快照。脚本一为 NPM V1 mock 提取 peer dependencies原文档的第一个脚本用于生成NPM V1 mocks的peerDependencies数据其筛选规则是只渲染那些声明了 peer 依赖的包Renders only those packages that have peer dependencies.。NPM V1 锁文件lockfileVersion: 1的特点是只有嵌套的dependencies树、没有packages索引区因此夹具需要从package.json的peerDependencies/peerDependenciesMeta字段入手构造数据。完整脚本如下const readFileSync require(fs).readFileSync; const readdirSync require(fs).readdirSync; const existsSync require(fs).existsSync; let report ; const packageNames []; function processNodeModules(path .) { if (existsSync(${path}/node_modules)) { readdirSync(${path}/node_modules).forEach((folder) { if (folder.startsWith()) { readdirSync(${path}/node_modules/${folder}).forEach((subfolder) { packageNames.push(${path}/node_modules/${folder}/${subfolder}); processNodeModules(${path}/node_modules/${folder}/${subfolder}); }); } else { packageNames.push(${path}/node_modules/${folder}); processNodeModules(${path}/node_modules/${folder}); } }); } } processNodeModules(); packageNames.forEach((path) { const filePath ${path}/package.json; if (existsSync(filePath)) { const content readFileSync(filePath, utf-8); const peerDependencies JSON.parse(content).peerDependencies; const peerDependenciesMeta JSON.parse(content).peerDependenciesMeta; const output JSON.stringify({ ...(peerDependencies { peerDependencies }), ...(peerDependenciesMeta { peerDependenciesMeta }), }); if (output {}) return; report ${filePath.slice(2)}: ${output},\n; } }); console.log(report);脚本行为逐段拆解递归遍历node_modulesprocessNodeModules(path)检查当前目录下是否存在node_modules子目录若存在则遍历其中的每一个包目录。对于以开头的目录scoped 包如angular/core还要再深入一层读取 scope 下的子包目录并把路径加入packageNames数组之后对每个找到的包目录继续递归调用processNodeModules从而把嵌套依赖的node_modules也一并扫描进来。这一步与 npm 安装时嵌套 node_modules的物理布局完全一致保证了夹具覆盖到深层传递依赖。过滤出声明了 peer 依赖的包对每个包的package.json读取peerDependencies与peerDependenciesMeta两个字段只有两者之一存在时才生成输出。peerDependenciesMeta记录了 peer 依赖是否为 optional例如 pnpm 锁文件中的peerDependenciesMeta: { firebase-tools: { optional: true } }这在解析 pnpm/npm 的 peer 处理逻辑时至关重要。生成键值对文本输出形如node_modules/pkg/package.json: {peerDependencies: {...}, peerDependenciesMeta: {...}},的行。filePath.slice(2)去掉路径前缀./得到相对路径直接可以粘贴进测试夹具的映射表。与解析器源码的印证为什么夹具要专门为 NPM V1 保留 peer 信息看 npm-parser.ts 中对锁文件版本的划分源码注释直接写明/** * NPM * - v1 has only dependencies * - v2 has packages and dependencies for backwards compatibility * - v3 has only packages */v1 结构中没有packages节点包与包之间的关系只能靠嵌套的dependencies树与各包的requires推断peer 依赖的建模需要依赖package.json元数据作为补充这正是该脚本产出数据的用途。仓库中的 package-lock.json.fixture 等文件即包含这类场景npm-parser 测试通过它们验证 v1 锁文件的节点与依赖边解析结果。脚本二为 Yarn 与 Pnpm mock 提取包版本原文档的第二个脚本面向Yarn 和 Pnpm mocks筛选规则是只渲染被提升hoisted的依赖Renders only hoisted dependencies.。yarn 与 pnpm 的锁文件在顶层直接列出所有解析后的包及其精确版本yarn v1 的namerange:块、pnpm 的packages:区因此夹具只需要包路径 → 版本号的映射即可。完整脚本如下const readFileSync require(fs).readFileSync; const readdirSync require(fs).readdirSync; const existsSync require(fs).existsSync; let report ; const packageNames []; readdirSync(node_modules).forEach((folder) { if (folder .pnpm) return; if (folder.startsWith()) { readdirSync(node_modules/${folder}).forEach((subfolder) { packageNames.push(${folder}/${subfolder}); }); } else { packageNames.push(folder); } }); packageNames.forEach((packageName) { const path node_modules/${packageName}/package.json; if (existsSync(path)) { const content readFileSync(path, utf-8); const version JSON.parse(content).version; report ${path}: {version: ${version}},\n; } }); console.log(report);脚本行为逐段拆解只扫描顶层目录与脚本一不同这里直接对node_modules顶层调用readdirSync不做递归。这是因为 yarn/pnpm 的锁文件刻画的是解析结果全集顶层提升的包已经代表了安装布局的主体夹具无需模拟嵌套目录。跳过.pnpm虚拟存储if (folder .pnpm) return;明确排除 pnpm 的虚拟存储目录。.pnpm下是以nameversion命名的、包含全部包副本的扁平结构而 pnpm 锁文件的packages:区恰恰是这种扁平清单的映射测试夹具通常只保留提升层的引用即可避免数据冗余。scoped 包二级展开对scope开头的目录再读取一层子目录把scope/pkg这样的完整包名加入列表与脚本一中的 scoped 处理保持一致。产出版本映射读取每个包的package.json的version字段输出形如node_modules/pkg/package.json: {version: 1.2.3},的行。Nx 解析 yarn/pnpm 锁文件时主要依据锁文件中的版本与resolution字段构造外部节点此映射为夹具提供了校验基准。与解析器源码的印证yarn-parser 与 pnpm-parser 都需要把锁文件条目映射为统一的ProjectGraphExternalNode。在 lock-file.ts 中可以看到yarn/bun 路径还会额外读取根package.json参与解析const packageJson packageManager yarn || packageManager bun ? readJsonFile(join(context.workspaceRoot, package.json)) : undefined;而 pnpm 路径则直接解析锁文件内容getPnpmLockfileNodes(contents, lockFileHash)。无论哪条路径最终都要把字符串版本解析为语义化版本并构造外部节点本脚本产出的{version: ...}数据正是测试断言中节点version字段的对照来源。仓库中的 workspaces/package.json.fixture声明了workspaces: [packages/*]与对应的yarn.lock.ts、pnpm-lock.yaml.ts即属于此类 hoisted 布局下的典型夹具。夹具的实际应用场景从解析到剪枝__fixtures__目录按场景分子目录组织README 中的两个脚本只是数据来源实际测试消费这些数据的方式非常多样从测试文件中可以观察到几类典型用法解析器单元测试如 npm-parser.spec.ts 直接引用__fixtures__/auxiliary-packages/package-lock.json.fixture、__fixtures__/nextjs/package-lock.json.fixture、__fixtures__/npm-hoisting/package-lock.json.fixture等文件断言getNpmLockfileNodes产出的外部节点与依赖关系剪枝pruning回归测试pruned-output.spec.ts 与 project-graph-pruning.spec.ts 借助__fixtures__/pruning/下的typescript/、devkit-yargs/等场景验证createLockFile()只保留项目图裁剪后仍被引用的包回归与边界场景pnpm-regression/、pnpm-semver-range-specifier/、resolutions-and-patches/、bun/等子目录专门覆盖真实项目中出现过的解析问题防止修复被后续改动重新引入。如何基于脚本维护自己的夹具如果需要在本地复现或扩展这套夹具流程可以按以下步骤操作准备一个真实安装目录在临时目录中运行npm install或 yarn/pnpm让node_modules具备与目标项目一致的包布局运行脚本一生成 NPM V1 mock在安装目录根执行第一个脚本输出即node_modules/...: {peerDependencies: ...}的映射文本粘贴到测试夹具的 mock 映射中运行脚本二生成 Yarn/Pnpm mock在安装目录根执行第二个脚本得到提升包版本映射写入 fixture 文件并关联测试将生成的锁文件内容放入fixtures对应场景目录JSON 用.fixture后缀多行文本用.ts模板字符串再在对应的*-parser.spec.ts中引用并断言解析结果。需要注意的是两个脚本依赖fs的同步 APIreadFileSync/readdirSync/existsSync运行环境为 Node.js且脚本会从当前工作目录出发扫描node_modules执行前应确认所在目录正确。README 中的说明也隐含了二者的分工前提NPM V1 mock 关注 peer 元数据因为 v1 结构缺少packages索引Yarn/Pnpm mock 关注 hoisted 版本清单因为这两类锁文件的顶层结构本身就是解析结果的展开。小结__fixtures__/README.md提供的两个脚本本质上是把真实安装布局转化为解析器测试可断言的夹具数据的提取器一个面向 NPM V1 的 peer 依赖元数据一个面向 Yarn/Pnpm 的 hoisted 版本映射。理解它们就理解了 Nx 锁文件解析测试的数据来源与设计思路——从getLockFileNodes/getLockFileDependencies的图构建到createLockFile的剪枝输出再到 pruned deploy 产物的生成每一层逻辑都有对应的夹具场景作为可复现的验证锚点。【免费下载链接】nxThe Monorepo Platform that amplifies both developers and AI agents. Nx optimizes your builds, scales your CI, and fixes failed PRs automatically. Ship in half the time.项目地址: https://gitcode.com/GitHub_Trending/nx/nx创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表