返回

package.json 差异比较

开发工具

正在载入

正在加载工具

工具代码会在打开时按需载入,请稍候。

本工具的全部运算都发生在你的浏览器中,输入内容不会发送到任何服务器。

关于这个工具

将两份 package.json 作为结构化声明进行比较。工具按包名汇总 dependencies、devDependencies、optionalDependencies 和 peerDependencies,再单独列出 scripts、engines 及其他顶层元数据。每项变化都保留前后值,并标为新增、删除、修改或移动,方便审核清单编辑。工具不启动包管理器、不执行脚本文本;解析和比较都在当前浏览器页面内完成。

常见用途

  • 审核合并请求中构建工具从开发依赖移到运行依赖,同时修改版本范围的变更。
  • 提交前检查构建或测试脚本、Node.js 运行环境声明、packageManager、overrides 和包入口元数据。
  • 按类别筛选当前审核的内容,同时生成包含全部声明变化、输出顺序确定的 JSON 摘要。

使用方法

  1. 1.粘贴变更前后的 package.json,或载入示例。两侧都必须是严格 JSON 对象;不接受注释、尾逗号或解码后重复的键。
  2. 2.点击比较,检查统计及前后值,再按变化类型或字段类别筛选。较大结果每页显示 100 项;标为移动的依赖也可能同时修改了声明字符串。
  3. 3.复制或下载完整 JSON 前阅读秘密信息提示。筛选不会移除导出行;修改任一输入或切换界面语言会清除旧结果。

可复现的清单比较示例

格式与普通键顺序

{
  "before": "{\n  \"config\": {\n    \"b\": 2,\n    \"a\": 1\n  },\n  \"dependencies\": {\n    \"beta\": \"2.0.0\",\n    \"alpha\": \"1.0.0\"\n  }\n}",
  "after": "{\n  \"dependencies\": {\n    \"alpha\": \"1.0.0\",\n    \"beta\": \"2.0.0\"\n  },\n  \"config\": {\n    \"a\": 1,\n    \"b\": 2\n  }\n}"
}
{
  "error": null,
  "counts": {
    "added": 0,
    "removed": 0,
    "changed": 0,
    "moved": 0
  }
}

两份清单的声明相同。调整普通键顺序不会产生变化;这里的输出展示错误和计数摘要。

新增、删除和修改依赖

{
  "before": "{\n  \"dependencies\": {\n    \"demo\": \"^1.0.0\"\n  },\n  \"devDependencies\": {\n    \"old\": \"1.0.0\"\n  }\n}",
  "after": "{\n  \"dependencies\": {\n    \"demo\": \"^2.0.0\"\n  },\n  \"optionalDependencies\": {\n    \"extra\": \"~1.2.0\"\n  }\n}"
}
{
  "error": null,
  "counts": {
    "added": 1,
    "removed": 1,
    "changed": 1,
    "moved": 0
  }
}

新增 extra,删除 old,demo 的 SemVer 范围字面文本改变。仅比较声明,不推断已安装版本。

移动与声明修改同时发生

{
  "before": "{\n  \"devDependencies\": {\n    \"demo\": \"^1.0.0\"\n  }\n}",
  "after": "{\n  \"dependencies\": {\n    \"demo\": \"^2.0.0\"\n  }\n}"
}
{
  "error": null,
  "counts": {
    "added": 0,
    "removed": 0,
    "changed": 0,
    "moved": 1
  }
}

demo 仍存在,但从 devDependencies 移到 dependencies,且范围改变。只计一项移动,完整报告保留两侧分区映射。

脚本、运行环境与包管理器

{
  "before": "{\n  \"scripts\": {\n    \"build\": \"vite build\"\n  },\n  \"engines\": {\n    \"node\": \">=20\"\n  },\n  \"packageManager\": \"npm@10.8.0\"\n}",
  "after": "{\n  \"scripts\": {\n    \"build\": \"tsc && vite build\",\n    \"test\": \"node --test\"\n  },\n  \"engines\": {\n    \"node\": \">=22\"\n  },\n  \"packageManager\": \"npm@11.0.0\"\n}"
}
{
  "error": null,
  "counts": {
    "added": 1,
    "removed": 0,
    "changed": 3,
    "moved": 0
  }
}

新增 test 脚本,build 命令、Node.js 运行环境声明和 packageManager 值分别修改,不执行任何命令。

条件键优先级与数组顺序

{
  "before": "{\n  \"exports\": {\n    \".\": {\n      \"import\": \"./a.js\",\n      \"default\": \"./b.js\"\n    }\n  },\n  \"files\": [\n    \"dist\",\n    \"README.md\"\n  ]\n}",
  "after": "{\n  \"exports\": {\n    \".\": {\n      \"default\": \"./b.js\",\n      \"import\": \"./a.js\"\n    }\n  },\n  \"files\": [\n    \"README.md\",\n    \"dist\"\n  ]\n}"
}
{
  "error": null,
  "counts": {
    "added": 0,
    "removed": 0,
    "changed": 2,
    "moved": 0
  }
}

exports 条件顺序和 files 数组顺序改变,计两项元数据修改。这种保守的结构比较不判断实际运行行为是否改变。

非仓库及别名声明

{
  "before": "{\n  \"dependencies\": {\n    \"demo\": \"^1.0.0\"\n  }\n}",
  "after": "{\n  \"dependencies\": {\n    \"demo\": \"file:../demo\",\n    \"alias\": \"npm:demo@^2.0.0\",\n    \"local\": \"workspace:*\",\n    \"source\": \"github:example/demo#main\"\n  }\n}"
}
{
  "error": null,
  "counts": {
    "added": 3,
    "removed": 0,
    "changed": 1,
    "moved": 0
  }
}

新增别名、工作区和 Git 项;demo 改成本地文件声明。语法标签不会触发下载、文件系统读取或包解析。

拒绝解码后重复的键

{
  "before": "{\"name\":\"demo\",\"na\\u006de\":\"other\"}",
  "after": "{}"
}
{
  "error": {
    "side": "before",
    "code": "duplicateKey"
  },
  "counts": {
    "added": 0,
    "removed": 0,
    "changed": 0,
    "moved": 0
  }
}

name 与 na\u006de 解码为同一个键。比较在变更前输入处停止,不使用被覆盖的值或局部结果。

清单审核的常见误区

  • 粘贴锁文件、JavaScript 对象、带 JSONC 注释或尾逗号的内容,而非严格 package.json JSON。
  • 把声明分类或范围修改理解为实际安装版本、升级建议或安全结论。
  • 把移动数当作分区编辑次数:它按受影响的包名计数,每个包只计一次。
  • 以为筛选显示能脱敏或排除导出的变化;完整报告仍保留原始值,包括秘密信息。
  • 忽略 exports/imports 的条件顺序,或误以为差异中展示的脚本已被执行。

限制与说明

  • 这是有边界的声明比较,不是 npm schema 校验器、依赖解析器或已安装版本比较。不读取锁文件、不获取包、不查询注册表、不执行脚本,也不判断兼容性、漏洞或升级降级。含义相同但写法不同的版本范围仍可能显示为修改。
  • 每侧最多 262,144 个 UTF-8 字节、32 层深度和 20,000 个 JSON 值。拒绝任意层级的重复键、无效 JSON、非对象根值、非有限数字、不安全整数及无法保持十进制值往返转换的数字。依赖分区、scripts 和 engines 的值必须是字符串;出现时 name、version、packageManager 必须为字符串,overrides 必须为对象。任何错误都会停止比较,不返回局部差异。
  • 依赖按四个支持的分区汇总。包名仅存在一侧时为新增或删除;存在于两侧但所属分区改变时只计一项移动,即使声明字符串同时变化,或包仍留在其他分区。分区相同但字面值不同为修改。空的依赖、脚本、运行环境分区等同于缺失分区;overrides 和 peerDependenciesMeta 作为元数据比较,不展开成已解析依赖。
  • 忽略普通对象键顺序和 JSON 格式,保留字符串内容与数组顺序。exports 和 imports 内所有嵌套对象的键顺序都会保留,因为条件优先级可能受影响。其他元数据按顶层字段比较;嵌套编辑展示整个字段的结构化前后值。声明类型仅用于提示,包括未知语法,不校验或解析包目标。 exports/imports 内任意层级的整数索引对象键都会被拒绝,避免 JavaScript 自动换序而掩盖条件顺序的变化。
  • 本工具不上传或持久保存输入。JSON 导出原样保留变化字段的值,包括私有 URL、脚本命令和秘密信息,不做脱敏。无论筛选条件或当前页如何,都导出全部变化。报告被复制、保存或分享后,不再由页面控制。

常见问题

依赖修改是否表示已经升级?

不是。工具只比较声明文本。^1.0.0 变成 ~1.5.0、latest 变成 next,或仓库范围变成本地路径,都不能确定实际安装版本。锁文件与包管理器解析不在本工具的比较范围内。

移动依赖与删除后新增有什么不同?

同一包名会跨四个依赖分区合并查看。两侧都存在但所属分区改变时只标一项移动。两侧分区映射同时显示声明变化;一个包存在于多个分区时,不会悄悄合并为单一值。

为什么只调整 JSON 顺序有时也会出现差异?

忽略格式和普通对象键顺序,但数组保持顺序,exports/imports 内嵌套对象也保持键顺序,因为条件匹配优先级可能依赖它。因此即使值不变,条件换序也会显示。

是否支持 workspace、别名、Git 和本地路径?

这些声明保留为字面字符串,并标注为工作区、npm 别名、Git 或本地文件路径;精确版本、SemVer 范围、URL、发布标签及未知语法也会分类。工具不访问、不执行、不解析任何目标,分类不代表有效性或安全保证。

相关工具