Skip to content

package.json

name

如果计划发布一个包,在 package.json 文件中,最重要的是 nameversion 字段,因为它们是必需的

nameversion 组合起来形成一个被认为是完全唯一的标识符。

对包的更改应该伴随着版本的更改。如果你不计划发布你的包,nameversion 字段是可选的。

name 是你的项目的名称。

一些规则:

  1. 名称必须小于等于 214 个字符,包括作用域(对于作用域包)。
  2. 带有作用域的包的名称可以以点(.)或下划线(_)开头,但是不能没有作用域。
  3. 新的包名称不能包含大写字母。
  4. 名称最终会成为 URL 的一部分、命令行的参数和文件夹名称。因此,名称不能包含任何非 URL 安全的字符。

一些建议:

  1. 不要使用与核心 Node 模块相同的名称。
  2. 不要在名称中包含 jsnode。因为你编写的是 package.json 文件,所以可以假设是 JavaScript,而可以使用 engines 字段指定引擎。
  3. 名称可能会作为参数传递给 require() 函数,所以它应该是一个简短但合理描述的名称。
  4. 在你对名称过于依赖之前,可能需要检查 npm 注册表,看看是否已经有相同名称的包存在。npmjs.com
  5. 名称可以选择性地添加作用域前缀,例如 @myorg/mypackage

version

version 字段用于标识包的版本号,它应该遵循语义化版本控制规范,并且在发布新版本时进行适当的升级。

version 必须是可被 semver 解析的,semver 是作为 npm 的一个依赖项捆绑在一起的。(你可以使用 npm install semver 来自己使用它。)

npm 使用语义化版本控制(Semantic Versioning)规范来管理版本号。

语义化版本控制分为三个部分:主版本号、次版本号和补丁版本号。具体规则如下:

  1. 主版本号(Major):当你做了不兼容的 API 修改时,应该升级主版本号。
  2. 次版本号(Minor):当你添加了向后兼容的功能时,应该升级次版本号。
  3. 补丁版本号(Patch):当你进行向后兼容的 bug 修复时,应该升级补丁版本号。
  4. 版本号可以包含预发布标识(如 betaalpha)和构建标识(如 build-123)。

例如,一个有效的版本号可以是 1.0.02.3.11.2.0-beta.1 等。

当你发布新的版本时,你应该根据你的修改类型适当地升级主版本号、次版本号或补丁版本号,并在 package.json 文件中更新 version 字段。

description

description 字段用于描述包的简短说明。

类似于 HTML 网页 TDKdescriptiondescription 字段可以帮助用户快速了解包的作用。

keywords

keywords 字段用于描述包的关键字,它是一个字符串数组

类似于 HTML 网页 TDKkeywordskeywords 字段可以帮助用户快速了解包的作用。

也有助于用户的检索。

homepage

homepage 字段用于指定包的主页。

axios 仓库为例:

json
{
  "homepage": "https://axios-http.com"
}

bugs

bugs 字段用于指定包的 issue 跟踪地址。

可以是一个 URL,也可以是一个邮箱地址。但推荐使用 URL

json
{
  "bugs": {
    "url": "https://github.com/axios/axios/issues",
    "email" : "project@hostname.com"
  }
}

license

license 字段用于指定包的许可证。

license 字段可以是一个字符串,也可以是一个对象。

如果是一个字符串,那么它应该是一个有效的 SPDX 许可证标识符。

如果是一个对象,那么它应该包含 type 字段,用于指定许可证类型,以及 url 字段,用于指定许可证的 URL 地址。

json
{
  "license": "MIT"
}
json
{
  "license": {
    "type": "MIT",
    "url": "https://www.opensource.org/licenses/mit-license.php"
  }
}

author

author 字段用于指定包的作者。

author 字段可以是一个字符串,也可以是一个对象。

如果是一个字符串,那么它应该是一个有效的 name <email> (url) 格式的字符串。

json
{
  "author": "Barney Rubble <b@rubble.com> (http://barnyrubble.tumblr.com/)"
}

如果是一个对象,那么它应该包含 name 字段,用于指定作者名称,以及 email 字段和 url 字段,用于指定作者的邮箱地址和个人主页。

json
{
  "author": {
    "name" : "Barney Rubble",
    "email" : "b@rubble.com",
    "url" : "http://barnyrubble.tumblr.com/"
  }
}

contributors

contributors 字段用于指定包的贡献者。

contributors 字段是一个数组,数组中的每一项可以是一个字符串,也可以是一个对象

contributors 与上述 author 字段的格式和使用方式相同

区别在于 author 指定单个人,而 contributors 是数组,指代人的集合

funding

funding 字段用于指定包的资助信息。

它包含 type 字段和 url 字段。

type 字段用于指定资助类型,url 字段用于指定资助地址。

json
{
  "funding": {
    "type" : "individual",
    "url" : "http://example.com/donate"
  },

  "funding": {
    "type" : "patreon",
    "url" : "https://www.patreon.com/my-account"
  },

  "funding": "http://example.com/donate",

  "funding": [
    {
      "type" : "individual",
      "url" : "http://example.com/donate"
    },
    "http://example.com/donateAlso",
    {
      "type" : "patreon",
      "url" : "https://www.patreon.com/my-account"
    }
  ]
}

files

可选的 files 字段是一个文件模式数组,用于描述当你的包作为依赖项安装时应包含的条目

文件模式遵循类似于 .gitignore 的语法,但是与 .gitignore 的行为相反:包含一个文件、目录或通配符模式(、**/ 等)将使得在打包成 tarball 时该文件被包含在内。如果省略该字段,将默认为 ["*"],表示包含所有文件。

无论文件数组中是否存在,某些特殊的文件和目录始终会被包含或排除(请参阅下文)。

你还可以在你的包的根目录或子目录中提供一个 .npmignore 文件,用于阻止文件被包含。

在包的根目录中,它不会覆盖 files 字段,但在子目录中会覆盖。

.npmignore 文件的工作原理与 .gitignore 文件类似。如果存在 .gitignore 文件而缺少 .npmignore 文件,则将使用 .gitignore 文件的内容。

使用 package.json#files 字段包含的文件无法通过 .npmignore.gitignore 排除。

某些文件始终会被包含,不受设置的影响:

  • package.json
  • README
  • LICENSE / LICENCE
  • main 字段指定的文件
  • READMELICENSE 可以具有任何大小写和扩展名。

相反的,一些文件始终会被排除,不受设置的影响:

  • .git
  • CVS
  • .svn
  • .hg
  • .lock-wscript
  • .wafpickle-N
  • .*.swp
  • .DS_Store
  • ._*
  • npm-debug.log
  • .npmrc
  • node_modules
  • config.gypi
  • *.orig
  • package-lock.json (use npm-shrinkwrap.json if you wish it to be published)

main

main 字段用于指定包的入口文件。

main 只能设置为字符串

json
{
  "main": "./main.js"
}

如果未设置 main 字段,默认情况下它将是包根目录中的 index.js

browser

browser 字段用于指定包在 browser 环境中的入口文件。

browser 字段可以设置为字符串或对象

当设置为字符串时,它代表的是客户端浏览器环境下的入口文件。此时,会覆盖 main 字段的设置

json
{
  "browser": "./browser.js"
}

当设置为对象时,它代表的是客户端浏览器环境下的文件映射解析。以 axios 库为例:

json
{
  "browser": {
    "./lib/adapters/http.js": "./lib/helpers/null.js",
    "./lib/platform/node/index.js": "./lib/platform/browser/index.js",
    "./lib/platform/node/classes/FormData.js": "./lib/helpers/null.js"
  }
}

上面的 ./lib/adapters/http.js 代表,在浏览器环境下将该文件映射为 ./lib/helpers/null.js 文件。后续两条配置同理

关于 browser 字段的更多信息,请参考 package-browser-field-spec

TIP

截止到这里,可能有一个疑问:设置 browser 字段后,代码是如何区分浏览器环境与 Node.js 环境的?

其实,可以将 browser 看做约定的准则、规范。环境的区分,依然要借助于打包器的实现

譬如 webpack 中可以使用 target 字段来指定打包的目标环境。

target 设置为 web 时,webpack 会将 browser 字段的配置应用到打包结果中

又譬如 rollup 插件 @rollup/plugin-node-resolve 也提供了对应的 browser 配置项。

bin

bin 字段用于定义可执行命令(binaries)的路径和名称。

当使用 npm 安装指定包时,npm 会自动创建符号链接(symbolic link),将存在的 bin 字段中的可执行文件链接到 /usr/local/bin 目录中。

它可以设置为字符串,譬如:

json
{
  "name": "eslint",
  "bin": "./bin/eslint.js"
}

此时, 会采取 name 值作为命令名称。

如果需要设置多个命令,可以设置为对象,譬如:

json
{
  "name": "eslint",
  "bin": {
    "eslint": "./bin/eslint.js",
    "eslint-cli": "./bin/eslint-cli.js"
  }
}

此时,会采取 bin 对象的 key 作为命令名称。

所有的脚本 JavaScript 文件的头部需要声明 Shebang,以便 Node.js 能够正确地执行它们。

譬如:#!/usr/bin/env node

TIP

/usr/bin/env 是一个常见的 Unix 和类 Unix 系统中的标准目录和命令。

它是一个可执行文件,通常用于在环境变量中查找并执行指定的命令。

当在命令行中执行 /usr/bin/env command 时,操作系统会搜索环境变量 $PATH 中列出的目录,以找到名为 command 的可执行文件,并使用找到的第一个匹配项来执行该命令。

例如,假设在环境变量 $PATH 中包含 /usr/local/bin/usr/bin 这两个目录,并且在这两个目录下都存在名为 node 的可执行文件。

如果在命令行中执行 /usr/bin/env node,则操作系统将查找并执行 $PATH 中找到的第一个 node 可执行文件。

使用 /usr/bin/env 命令有助于提高脚本的可移植性,因为它使用环境变量来确定要使用的命令。这样,无论命令在文件系统中的确切路径是什么,都可以确保脚本在不同的系统和环境中都能正常执行

Shebang 行中使用 /usr/bin/env 是一种常见的做法,如 #!/usr/bin/env python#!/usr/bin/env node

这样做可以确保在不同的系统中使用正确的解释器路径,而无需硬编码特定的解释器路径。

repository

repository 字段用于指定包的仓库地址。

repository 字段可以是一个字符串,也可以是一个对象。

譬如:

json
{
  "repository": "eslint/eslint",

  "repository": "https://github.com/axios/axios.git"
}

其中,eslint/eslint 是一个简写,表示包的存储库位于 GitHub 上的 eslint 组织下的 eslint 存储库。

它等价于 https://github.com/eslint/eslint.git

如果是一个对象,那么它应该包含 type 字段,用于指定仓库类型,以及 url 字段,用于指定仓库地址。

json
{
  "repository": {
    "type": "git",
    "url": "https://github.com/eslint/eslint.git"
  }
}

type 值可以设置为 gitsvnmercurial 以及 cvs 等等。

需要注意的是,type 属性并不影响包的实际行为或安装过程。

type 仅用于提供关于包的版本控制存储库的元数据,以便用户和开发者能够方便地访问和查找相关信息。

如果这个包的 package.json 文件并不在根目录,譬如 monorepo 形式的仓库,那么可以利用 directory 字段进一步指定:

json
{
  "repository": {
    "type": "git",
    "url": "https://github.com/facebook/react.git",
    "directory": "packages/react-dom"
  }
}

scripts

scripts 字段用于指定一系列的脚本命令。

scripts 字段是一个对象,对象的每一个 key 都是一个脚本命令的名称,value 是该脚本命令的执行命令

譬如:

json
{
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  }
}

config

config 字段用于指定自定义的配置信息。

譬如:

json
{
  "config": {
    "apiEndpoint": "https://api.example.com",
    "port": "8080",
    "timeout": 5000
  }
}

当在 package.json 中定义了上述信息后,可以通过 process.env 来访问这些信息。

js
console.log(process.env.npm_package_config_apiEndpoint)

console.log(process.env.npm_package_config_port)

console.log(process.env.npm_package_config_timeout)

dependencies

dependencies 字段用于指定包的依赖项。

譬如:

json
{
  "dependencies": {
    "axios": "^0.21.1",
    "chalk": "^4.1.0"
  }
}

devDependencies

devDependencies 字段用于指定包的开发依赖项。

当我们的包要提供给其他人使用时,我们需要将项目中的相关开发依赖声明到 devDependencies 字段中。

这样,别人在利用 npm install 安装我们的包时,就不会安装这些多余的开发依赖了。

譬如:

json
{
  "devDependencies": {
    "eslint": "^7.18.0",
    "eslint-config-standard": "^16.0.2",
    "eslint-plugin-import": "^2.22.1",
    "eslint-plugin-node": "^11.1.0",
    "eslint-plugin-promise": "^4.3.1",
    "eslint-plugin-standard": "^5.0.0"
  }
}

peerDependencies

peerDependencies 字段用于指定包的对等依赖项。

假设我们的包依赖于特定版本的第三方库,那么为了保证兼容性,可以将其声明为对等依赖项。

譬如:

json
{
  "peerDependencies": {
    "vue": "^2.0",
  }
}

上述配置代表,我们的项目只能依赖于 vue2.x 版本。

当我们安装了不符合要求的 vue 版本时,npm 会给出警告

TIP

npm@3.0npm@6.0npm install 不会安装 peerDependencies

npm@7.0 及以上版本,npm install 会安装 peerDependencies

另外,笔者测试了本机上的 yarn@1.22.17 版本执行 yarn 时,不会安装 peerDependencies

peerDependenciesMeta

peerDependenciesMeta 字段用于指定包的对等依赖项的元数据。

目前,peerDependenciesMeta 字段只支持 optional 属性

譬如,我们创建了 module-a

json
{
  "peerDependencies": { 
    "winston": "> 1.0.0 <= 1.2.10",
    "foo": "~2.3.0"
  },
  "peerDependenciesMeta": {
    "winston": {
      "optional": true
    }
  }
}

那么,在利用 npm install 安装 module-a 时,如果 winston 的依赖不满足 > 1.0.0 <= 1.2.10,此时 npm 不会警告。

因为,此处设置了 peerDependenciesMeta 中的 winstonoptionaltrue

但是,如果 foo 的依赖不满足 ~2.3.0,此时 npm 会警告。

bundledDependencies

bundledDependencies 字段用于指定包的捆绑依赖项。

捆绑依赖项是指在项目打包过程中将依赖项的完整内容包含在项目中,而不是通过安装依赖项来引用它们。

这在某些情况下是有用的,例如当你的应用程序需要在某些环境中运行(如无网络连接的服务器或离线应用程序)。

bundleDependencies 字段是一个字符串数组,包含了项目的捆绑依赖项的名称。

譬如:

json
{
  "bundledDependencies": [
    "jquery",
    "vue"
  ]
}

当你运行 npm pack 命令时,npm 将会包含这些依赖项的完整内容,并将它们打包到生成的 .tgz 文件中。

在使用这个打包文件部署应用程序时,捆绑的依赖项将被解压并包含在项目中。

需要注意的是,bundleDependencies 是一个过时的字段,而在现代的 Node.js 项目中,更常用的方法是使用工具如 webpackParcel 来打包应用程序,并通过依赖项的引用来管理依赖关系

optionalDependencies

optionalDependencies 字段用于指定包的可选依赖项。

可选依赖项是指在安装项目的依赖项时,如果遇到可选依赖项无法安装或编译的情况,不会导致整个安装过程失败。

换句话说,可选依赖项是对项目功能的补充或增强,但不是必需的。

optionalDependencies 字段是一个对象,其中键是可选依赖项的名称,值是版本范围或 URL

譬如:

json
{
  "optionalDependencies": {
    "eslint": "^7.18.0",
  }
}

当运行 npm install 命令安装项目的依赖项时,NPM 会尝试安装这些可选依赖项。

如果可选依赖项无法安装成功,NPM 将继续安装其他依赖项,并输出警告消息指示哪些可选依赖项未被安装。

可选依赖项在某些情况下很有用,例如当你的项目需要与某个特定的库或插件进行集成,但该库或插件并非必需时。

在这种情况下,你可以将其列为可选依赖项,并在项目中通过条件逻辑或动态加载来使用它们。

需要注意的是,可选依赖项的使用应慎重。在编写代码时,你应该始终考虑可选依赖项的缺失,并编写适当的逻辑来处理它们不存在的情况,以确保项目的健壮性和可移植性。

engines

engines 字段用于指定项目运行的 Node.js 版本范围。

这对于确保你的项目在特定的 Node.js 版本上能够正常运行非常有用,因为不同的 Node.js 版本可能具有不同的语法和功能特性。

engines 字段是一个对象,其中可以包含以下键:

  • node:指定你的项目所需的最低 Node.js 版本或版本范围。你可以使用类似 >=10.0.0 的语法来指定范围。

  • npm:指定你的项目所需的最低 npm 版本或版本范围。

譬如:

json
{
  "name": "my-project",
  "version": "1.0.0",
  "engines": {
    "node": ">=12.0.0",
    "npm": ">=6.0.0"
  }
}

在上述示例中,engines 字段指定了项目所需的最低 Node.js 版本为 >=12.0.0 ,以及所需的最低 npm 版本为 >=6.0.0

当其他人尝试安装或使用你的项目时,npm 会检查他们的 Node.jsnpm 版本是否满足 engines 字段中指定的要求。

如果不满足要求,npm 会显示警告消息,提醒他们升级 Node.jsnpm 以满足项目的要求。

TIP

默认情况下,本地环境不满足 engines 设置时,npm 会显示警告消息,但不会阻止安装或使用项目。

如果想要阻止,那么本地的 npm 需要设置 engine-stricttrue

shell
npm config set engine-strict=true

os

os 字段用于指定项目运行的操作系统。

os 字段是一个字符串数组,包含了项目所需的操作系统名称。

譬如:

json
{
  "os": [
    "darwin",
    "linux",
    "!win32"
  ]
}

在上述示例中,os 字段指定了项目所需的操作系统为 darwinlinux,非 win32

当其他人尝试安装或使用你的项目时,npm 会检查他们的操作系统是否满足 os 字段中指定的要求。

如果不满足要求,npm 会显示警告消息,提醒他们使用支持的操作系统。

cpu

cpu 字段用于指定项目运行的 CPU 架构。

cpu 字段是一个字符串数组,包含了项目所需的 CPU 架构名称。

譬如:

json
{
  "cpu": [
    "x64",
    "ia32",
    "!arm"
  ]
}

在上述示例中,cpu 字段指定了项目所需的 CPU 架构为 x64ia32,非 arm

当其他人尝试安装或使用你的项目时,npm 会检查他们的 CPU 架构是否满足 cpu 字段中指定的要求。

如果不满足要求,npm 会显示警告消息,提醒他们使用支持的 CPU 架构。

private

private 字段用于指定包是否为私有包。

private 字段是一个布尔值,如果设置为 true,则表示该包为私有包,否则为公共包。

譬如:

json
{
  "private": true
}

当设置为 true 时,npm 会在发布时给出警告。

publishConfig

publishConfig 字段用于指定发布包时的配置信息。

publishConfig 字段是一个对象,其中可以包含以下键:

  • registry:指定发布包时的仓库地址。
  • access:指定发布包时的访问级别,可以设置为 public 表示公开访问,或者 restricted 表示限制访问。
  • tag:指定发布包时的标签。
  • otp:指定发布包时的 OTP

npm 默认将 @scope/package 视作 private

因此,如果你想发布 @scope/package 包,那么需要设置 publishConfig 字段中的 access 属性为 public

譬如:

json
{
  "name": "@scope/package",
  "version": "1.0.0",
  "publishConfig": {
    "access": "public",
    "registry": "https://registry.npmjs.org/"
  }
}

workspaces

exports

exports 字段用于指定包的导出方式。

它与 main 字段类似,但是提供了更多的灵活性。可以将其看做高版本 Node.jsmain 字段。

exports 只在较高版本的 Node.js 才支持,因此为了保证 Node.js@12 及以下版本的兼容,Node.js 通常依然会设置 main 字段

譬如,假设有 only-for-npm-test@1.0.2 有以下设置:

json
{
  "name": "only-for-npm-test",
  "version": "1.0.2",
  "main": "index.js",
  "exports": {
    "./main.js": "./lib/main.js"
  }
}

假设我们在此 Node.js@8 版本的基础上,创建了一个 test.js

js
const result = require('only-for-npm-test')

console.log(result)

那么,此时执行 test.js 文件,是能够正常执行的。因为 Node.js@12 及以下版本不支持 exports 字段, 支持 main 字段。

让我们把 Node.js 切换到 v16 版本,此时执行 test.js 文件,会报错:

[ERR_PACKAGE_PATH_NOT_EXPORTED]: No "exports" main defined

此时,需要将代码修改为:

js
const result = require('only-for-npm-test/main.js')

console.log(result)

关于 exports 属性的更多介绍,可以参考packages_package_entry_points

TIP

package.json 中的 exportsmain 字段同时存在时,exports 的优先级更高,会覆盖 main 字段。

而在使用 require('only-for-npm-test/main.js'),要注意 main.js 相对 only-for-npm-test 的路径是 ./main.js

因此,在设置 exports 时,必须设置的是 ./main.js 而非 main.js

即:

json
{
  "exports": {
    "./main.js": "./lib/main.js"
  }
}

而非:

json
{
  "exports": {
    "main.js": "./lib/main.js"
  }
}

否则,会报错:

[ERR_PACKAGE_PATH_NOT_EXPORTED]:Package subpath './main.js' is not defined by "exports"