前端开发
用 pnpm 审计并缩减 JavaScript…
在 pnpm 仓库中清理无用依赖、去重版本、按传递闭包排序并做 Keep/Replace/Remove 分诊,同时保持 lockfile、workspace 布局与依赖范围风格不变。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 46/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 85/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 44/100 ×0.15
适用场景
用 pnpm 审计并缩减 JavaScr通用
解决思路
- 先跑只读的 /check-npm 硬化门禁:若有检查 FAIL,报告表格与修复片段,清理过程中不得削弱 pnpm-workspace.yaml、.npmrc、CI 安装参数或 Renovate 年龄门禁;若用户只要求硬化,则在 /check-npm 后停止。
- 注意 pnpm 11+ 的脚本与发布年龄策略位于 pnpm-workspace.yaml(不再读取 package.json#pnpm 字段),建议配置前先对照已安装的 pnpm 主版本核验每个键,绝不添加不支持的键,清理期间不得降低已有的 minimumReleaseAge。
- 确认仓库使用 pnpm(存在 pnpm-lock.yaml、pnpm-workspace.yaml,或根 package.json 的 packageManager / devEngines.packageManager.name 为 pnpm);若不是 pnpm 则停止,清理过程中不迁移包管理器。
- 遵守仓库安装策略(如 pnpm install --frozen-lockfile --ignore-scripts);常用命令:安装/更新 lockfile 用 pnpm install --ignore-scripts(加仓库参数),移除直接依赖用 pnpm remove <pkg> --ignore-scripts,添加/更新用 pnpm add <pkg>@<version> --ignore-scripts,解释依赖用 pnpm why <pkg>,去重 lockfile 用 pnpm dedupe(若 lockfile 变化再 pnpm install --ignore-scripts),查版本用 pnpm outdated <pkg>,一次性工具用 pnpm --config.ignore-scripts=true dlx <pkg>@<version> <args...>(固定版本,已在 lockfile 中时优先 pnpm exec)。
- 除非用户在同一条消息中明确写“allow scripts”,否则 pnpm install、pnpm add、pnpm remove 一律加 --ignore-scripts(并说明将运行哪些脚本及风险);pnpm dlx 不接受 --ignore-scripts,需用 pnpm --config.ignore-scripts=true dlx;若依赖确实需要构建脚本(如原生模块),先无脚本完成,再询问是否执行特定的手动重建(如 pnpm rebuild <pkg>)。
- 在任何添加或升级具名包版本的命令(pnpm add、带新/升级直接版本的 pnpm dlx)前做 ≥72 小时新鲜度检查:curl -s https://registry.npmjs.org/<package-name>,解析版本(固定 pkg@1.2.3 用该版本,范围/latest/未指定用 dist-tags.latest),读取 time["<version>"],若发布不足 72 小时则停止并告知包名、版本与确切年龄,建议改用较旧的已知良好固定版本,除非用户写“override freshness check”;@grafana/* 作用域包豁免新鲜度检查但仍需 --ignore-scripts;新鲜度检查失败后未经用户批准不得替换为其他版本。
- 建立基线:收集所有 package.json 与 workspace 边界(pnpm-workspace.yaml)、pnpm-lock.yaml、pnpm-workspace.yaml 安全设置(minimumReleaseAge、strictDepBuilds、blockExoticSubdeps、allowBuilds)、.npmrc/CI 安装策略(如 --frozen-lockfile、--ignore-scripts)、各清单分区的直接依赖名(dependencies、devDependencies、peerDependencies、optionalDependencies)、现有验证命令,并做 CI 抽查(安装应使用 pnpm install --frozen-lockfile 且脚本阻断与 workspace 配置一致,标记每次运行都重新生成 lockfile 的工作流)与 Renovate/Dependabot 检查(记录 npm 包的 minimumReleaseAge,清理期间不得降低);记录基线指标:git status --short、wc -l pnpm-lock.yaml,若已安装 node_modules 则估算体积,以 lockfile 缩减为主要指标。
- 扫描所有 workspace package.json 依赖分区中的不安全直接依赖协议:标记非 semver 范围、workspace:、patch:、npm: 别名到 semver 的值,以及 git:/github:/tarball URL/user/repo 简写/file:/link:/exec: 等;不要静默移除被标记项,除非用户要求修复,否则报告为单独的硬化 PR。
- 移除无用直接依赖:用静态分析器(knip、depcheck 或仓库原生工具)作为起点而非证据,未安装时用固定版本的 pnpm --config.ignore-scripts=true dlx <tool>@<version> <args...>(先做新鲜度检查);对每个候选项在代码、配置、包脚本、构建工具、测试与文档中搜索包名与已知导入路径,检查是否被已发布包清单、peer 契约、插件加载器、CLI 命令或动态 require/import 需要,仅在确无真实使用时移除,然后运行 pnpm install --ignore-scripts(加仓库参数)与聚焦验证;若仅用于脚本或配置,考虑在 dependencies 与 devDependencies 之间移动而非移除。
- 去重 monorepo 直接版本:查找同一直接依赖在多个包清单中以不同版本/范围声明的情况,优先使用现有策略(精确固定、caret 范围、catalog/protocol、workspace 协议或集中约束),用 syncpack list-mismatches 或等价工具发现,在可共享同一兼容版本时统一直接范围,优先清单级一致性而非添加 pnpm.overrides,pnpm.overrides 仅用于传递依赖收敛或安全修复并记录原因;去重后运行 pnpm install --ignore-scripts(加仓库参数)并检查清单与 lockfile 差异,再考虑 pnpm dedupe(谨慎应用,可能改变传递解析)。
- 按传递 lockfile 闭包排序:对每个重要直接依赖估算闭包(从该依赖可达的传递 lockfile 条目),同时报告总闭包(可达的全部包)与独占闭包(移除该依赖后消失且不被其他直接依赖保留的包);用确定性测量:保存基线 lockfile 指标,临时从所属清单移除一个直接依赖,运行 pnpm install --ignore-scripts(加仓库参数),测量 lockfile 行/条目缩减与包数缩减,在测量下一个依赖前还原清单与 pnpm-lock.yaml(如 git checkout -- <manifest> pnpm-lock.yaml);对大型传递包用 pnpm why <pkg>,按影响与风险而非仅原始体积排序。
- 用闭包数据寻找低风险高影响升级:针对较新 minor/patch 版本能减少传递依赖的直接依赖,用 pnpm outdated 检查可用的非主版本,审阅 changelog/release notes 中的依赖树变化,对目标版本做新鲜度检查后一次升级一个依赖或紧密的一组(pnpm add <pkg>@<version> --ignore-scripts),运行 pnpm install --ignore-scripts(加仓库参数)并比较闭包指标,运行聚焦测试与相关 build/typecheck 命令;除非用户明确接受迁移风险,避免主版本升级。
- 内联琐碎用法:用闭包排序找出使用量小且明显但传递成本大的直接依赖,仅当以下全部成立时内联:用法极小且易于完整刻画;等价代码比保留依赖更短或更清晰;行为有测试覆盖或可用小型特征测试覆盖;该依赖不解决跨平台、安全、解析、Unicode、locale 或规范合规边界情况;所需行为简单时优先使用原生 API 而非新的替代依赖。
- 应用 e18e 指南:先做新鲜度检查固定 @e18e/cli@<version> 并在 dlx 安装时禁用脚本,运行 pnpm --config.ignore-scripts=true dlx @e18e/cli@<version> analyze 与 pnpm --config.ignore-scripts=true dlx @e18e/cli@<version> migrate --dry-run,并查阅 https://e18e.dev/docs/replacements/;将建议视为候选而非强制,验证 bundle/运行时行为并运行测试,仅在用户批准后将 e18e 替换映射为 Replace-with-Better。
- 对每个非平凡直接依赖(尤其在第 4–7 步之后)打一个分诊标签:Keep(必需、传递成本值得、维护良好)、Replace-with-Better(必需但存在维护更好或更安全的替代)、Replace-with-Internal(必需但外部风险值得内部实现)、Remove(可移除或内联)、Needs-user-review(用法模糊、策略权衡或需人工验证的变更)。
- 替换与新直接依赖规则:未经用户明确批准不得新增直接依赖(包括替换);等价时优先 Remove(内联/原生 API)而非 Replace-with-Better;Replace-with-Better 需说明原因(维护性、安全性、更小的依赖树),优先选择积极维护、广泛采用、来自可信维护者的包;遵守仓库 minimumReleaseAge / Renovate 门禁,命令级 72 小时新鲜度是下限而非绕过更严格配置的许可。
- 安全规则:小批量工作使 lockfile 差异可审阅;绝不盲信无用依赖工具,需核验导入、配置文件、脚本、生成代码钩子、框架约定、插件名、CLI 与动态导入;可编写脚本与解析来核验 package.json 与 lockfile 的依赖账目;将 peerDependencies、optionalDependencies、包 bin 使用、测试夹具与已发布包清单视为更高风险;除非替代被证明等价,不得移除或内联用于安全、解析、加密、Unicode、URL 处理、日期/时间、i18n 或平台兼容性的依赖;清理期间不得切换包管理器、删除 pnpm-lock.yaml 或重写 workspace 结构;将 pnpm dedupe 视为可能改变行为,保留结果前检查 lockfile 差异并运行聚焦验证;前后测量直接依赖数、lockfile 行数或条目数、包数以及可得的 node_modules 估算体积。
- 报告结果:/check-npm 结果(PASS/FAIL 摘要,FAIL 时链接修复)、移除或移动的直接依赖、去重的直接版本、lockfile 行/条目缩减、可得的包或 node_modules 缩减估算、推迟的高影响候选及原因(含等待批准的替换)、基线中的不安全协议/CI/Renovate 发现、运行的验证命令与结果、供应链信息(已做新鲜度检查或豁免的版本、所有 install/add 均带 --ignore-scripts、pnpm dlx 带 --config.ignore-scripts=true);当移除依赖基于静态分析而非运行时覆盖时明确说明风险。
- 报告格式(仅用于依赖清理发现,不用于 GitHub Actions 工作流分诊):每个发现包含 Evidence(包名与版本/范围、清单路径与分区、支持信号如导入位置、pnpm why、knip/depcheck 命中、闭包测量、/check-npm 行,或仅针对 pnpm install 策略的工作流文件+步骤)、Decision(分诊标签之一)、Proposed change(确切的清单/lockfile 命令或编辑,尚未应用时明确说明)、Rationale(体积即独占/总闭包、供应链、维护性或验证风险)、User review required(需运行的测试/build/typecheck、peer/插件/CLI 消费者、已发布包或动态导入风险、Replace-with-Better 或任何新直接依赖前的批准);非平凡发现时每个包(或每个协议/CI 发现)用一个块,而非单行摘要。
适用边界 · 注意事项
- 不适用于 Go modules 或完整的 GitHub Actions 工作流审计(action 选择、权限、固定版本),后者应使用专门的工作流审计。
- 仓库未使用 pnpm 时不适用——不要在清理过程中迁移包管理器。
- 用户只要求硬化(而非缩减)时,在 /check-npm 后即停止,不执行后续清理步骤。