Menu

package.json 详解:脚本、依赖与版本

package.json 里都有什么:重要的字段、脚本如何工作,以及 semver 版本范围如何决定 npm 安装哪个版本。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

Node 项目的清单文件

每个 Node.js 项目的根目录下都有一个 package.json。它是一个普通的 JSON 文件,用来描述项目:名称、版本、依赖什么、提供哪些命令。npm 做任何事情时都会读取它。删掉它,npm 就完全不知道你的项目是什么了。

创建它最快的方式是 npm init:

npm init -y

-y 参数会跳过提示,接受默认值。你会得到类似这样的内容:

{
  "name": "my-app",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

这只是一个最基本的骨架。其中大多数字段单独来看作用不大,等你添加了依赖和脚本,它们才变得有用。

dependencies 与 devDependencies

几乎所有的重活都由两个字段承担:dependencies 和 devDependencies。两者都是从包名到版本范围的映射。

区分它们只有一个原因:dependencies 是代码运行时需要的包。devDependencies 是只在开发时需要的包:测试运行器、代码检查工具、构建工具、类型检查器。当别人把你的包作为他们的依赖安装时,npm 会下载你的 dependencies,跳过你的 devDependencies。

npm 会自动更新这些字段。npm install express 会在 dependencies 中添加一行,npm install --save-dev vitest 会在 devDependencies 中添加一行。你很少需要手动编辑它们。

版本范围:^、~ 与确切版本

像 ^4.19.0 这样的版本字符串并不是确切的版本,而是范围。npm 遵循 semver(语义化版本),它把版本分成 MAJOR.MINOR.PATCH 三部分:

  • MAJOR(主版本)升级会破坏向后兼容。
  • MINOR(次版本)升级会增加功能,但不会破坏任何东西。
  • PATCH(补丁版本)升级用来修复 bug。

你会到处见到的两个运算符:

"express": "^4.19.0"   // >= 4.19.0 and < 5.0.0  (any 4.x.x at or above 4.19.0)
"express": "~4.19.0"   // >= 4.19.0 and < 4.20.0 (any 4.19.x at or above 4.19.0)
"express": "4.19.0"    // exactly 4.19.0

^ 是 npm 安装包时使用的默认符号。它相信次版本和补丁版本的升级会保持兼容。~ 更保守,只接受补丁版本的更新。单独的版本号则会精确锁定。

问题在于:“我刚刚安装的版本”和“范围允许的版本”不是一回事。如果你今天安装了 express@4.19.0,一个月后队友安装你的项目时,^4.19.0 可能会解析成 4.19.5。这就是 package-lock.json 的作用:它记录了实际解析出的确切版本,让每个人都得到同样的依赖树。记得提交它。

scripts:项目的命令入口

scripts 字段用来为常用命令定义快捷方式。放在里面的任何命令都可以用 npm run <name> 运行:

关于脚本,有几点需要了解:

  • npm start、npm test 以及少数几个其他名称不需要 run 关键字。其他脚本都需要用 npm run <name>。
  • 脚本在一个 shell 中执行,node_modules/.bin 会加入 PATH,所以你可以直接调用已安装包中的可执行文件。即使 vitest 没有全局安装,"test": "vitest" 也能运行。
  • 脚本可以串联起来:"build": "npm run lint && npm run compile"。用 && 表示“按顺序运行,失败就停止”。
  • pre<name> 和 post<name> 脚本会自动运行。如果你有 prebuild,它会在 build 之前运行,不需要额外配置。

脚本就是项目的命令入口。一个好的 package.json 意味着新的贡献者可以克隆仓库,运行 npm install,然后运行 npm run dev / npm test,无需阅读任何 wiki。

入口:main、exports 与 type

这些字段告诉 Node(以及打包器)如何加载你的包。

  • type 决定 .js 文件如何解析。"module" 表示 ESM(import / export)。省略它或设为 "commonjs" 则表示 CommonJS(require)。完整的说明见 CommonJS 与 ESM 那一篇文档。
  • main 是旧式的入口,也就是 require("my-lib") 解析到的文件。较老的工具仍然会使用它。
  • exports 是现代的、更严格的替代方案。它精确规定使用者可以导入哪些文件,以及对应的子路径。没有列在这里的文件,导入时会失败,这是特性,不是 bug。公开的 API 由你掌控。

如果你只是在构建一个应用(而不是发布一个包),这几个字段里你大概只需要关心 type。

一个贴近实际的 package.json

把以上内容结合起来,一个小型 Node 应用的 package.json 实际上大致是这样的:

注意 engines.node。它只是建议性的:如果用户的 Node 版本不匹配,npm 会发出警告(开启 engine-strict 时会报错)。对于任何要发布的东西,这都是好习惯。

值得了解的字段

你还会遇到这些字段:

  • private: true:防止你不小心把包发布到 npm。任何不打算发布的项目都应该设置它。
  • license:SPDX 标识符,比如 "MIT" 或 "ISC"。对任何公开的项目都很重要。
  • repository、bugs、homepage:显示在 npm 仓库的包页面上。
  • bin:如果你的包附带命令行工具,在这里把命令名映射到脚本文件。安装之后,这些命令就可以运行了。
  • workspaces:用于 monorepo;告诉 npm 把子目录当作链接在一起的包来处理。

这些字段你不需要全部用上,只需要用对适合你当前工作的那些。

常见陷阱

几个经常让人犯错的地方:

  • 提交 node_modules。不要这样做。把它加入 .gitignore。有了 package.json 和 package-lock.json,任何人都可以用 npm install 重新生成它。
  • 不提交 package-lock.json。一定要提交它。没有锁文件,“在我的电脑上能运行”就成了真实存在的问题,因为 semver 范围会随着时间解析成不同的版本。
  • 把运行时依赖放进 devDependencies。你的应用在本地可能能运行,因为本地装了开发依赖,但到了跳过它们的生产环境就会出错。只要你发布的代码用到了它,它就属于 dependencies。
  • 手动修改版本却不重新安装。修改 package.json 中的版本后要运行 npm install,否则 node_modules 和锁文件就会不同步。

下一章:Node 运行时

package.json 告诉 Node 你的项目是什么。Node 运行时则决定它如何运行:模块解析、内置模块、全局对象,以及底层的事件循环。这就是下一页的内容。

常见问题

package.json 是用来做什么的?

它是 Node.js 项目的清单文件。它记录项目的名称和版本、依赖哪些包、可以用 npm run 运行哪些脚本,以及入口文件和模块类型等元数据。npm install 会读取它来决定下载什么。

dependencies 和 devDependencies 有什么区别?

dependencies 是代码在运行时需要的包,比如 express 或 react。devDependencies 只在开发或构建时需要,比如测试运行器、打包器和代码检查工具。当别人把你的包作为他们的依赖安装时,npm 会跳过你的 devDependencies。

package.json 版本号中的 ^ 和 ~ 是什么意思?

它们是 semver 版本范围运算符。^1.2.3 允许任何不低于 1.2.3 的 1.x.x 版本(主版本相同)。~1.2.3 更严格,只允许不低于 1.2.3 的 1.2.x 版本(次版本相同)。单独写 1.2.3 会锁定确切的版本。package-lock.json 会记录最终解析出的确切版本,让每次安装都能复现。

如何创建 package.json 文件?

在一个空目录中运行 npm init 并回答提示的问题,或者运行 npm init -y 接受默认值,立刻得到一个文件。你也可以手动编写,它就是一个 JSON 文件。真正必需的字段只有 name 和 version。

Coddy 编程语言插图

和 Coddy 一起学编程

开始