package.json 是 Node.js 项目的核心配置文件,它:
每个 Node.js 项目的根目录都应该包含一个 package.json 文件。
有多种方式创建 package.json:
# 交互式创建(回答一系列问题)
npm init
# 使用默认值快速创建
npm init -y
# 使用 yarn
yarn init
yarn init -y
# 使用 pnpm
pnpm init
使用 npm init -y 创建的默认内容:
{
"name": "my-project",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
| 分类 | 字段 | 说明 |
|---|---|---|
| 必需字段 | name, version | 包的唯一标识 |
| 描述性字段 | description, keywords, author, license, etc. | 包的元信息 |
| 入口点字段 | main, module, browser, exports, imports, type | 模块解析入口 |
| 文件字段 | files, bin, man, directories | 发布包含的文件 |
| 脚本字段 | scripts | npm 命令脚本 |
| 依赖字段 | dependencies, devDependencies, etc. | 依赖管理 |
| 环境字段 | engines, os, cpu | 运行环境要求 |
| 发布字段 | private, publishConfig | 发布控制 |
| 工作空间 | workspaces | Monorepo 配置 |
包名称,是包的唯一标识符。
命名规则:
作用域包(Scoped Packages):
{
"name": "@org/my-package"
}
作用域包的优点:
// 普通包名
{ "name": "lodash" }
// 作用域包名
{ "name": "@types/node" }
{ "name": "@vue/cli" }
{ "name": "@company/internal-utils" }
版本号,必须遵循语义化版本规范(SemVer)。
格式: MAJOR.MINOR.PATCH
{
"version": "1.2.3"
}
版本号含义:
| 部分 | 含义 | 递增时机 |
|---|---|---|
| MAJOR | 主版本号 | 不兼容的 API 修改 |
| MINOR | 次版本号 | 向后兼容的功能性新增 |
| PATCH | 修订号 | 向后兼容的问题修正 |
预发布版本:
// alpha 版本(内部测试)
{ "version": "2.0.0-alpha.1" }
// beta 版本(公开测试)
{ "version": "2.0.0-beta.1" }
// rc 版本(候选发布)
{ "version": "2.0.0-rc.1" }
简短描述包的功能,用于 npm 搜索结果展示。
{
"description": "A lightweight utility library for date manipulation"
}
关键词数组,提高包在 npm 搜索中的可发现性。
{
"keywords": ["date", "time", "utility", "format", "parse"]
}
项目主页 URL。
{
"homepage": "https://github.com/user/project#readme"
}
问题反馈地址。
// 简单形式
{
"bugs": "https://github.com/user/project/issues"
}
// 完整形式
{
"bugs": {
"url": "https://github.com/user/project/issues",
"email": "support@example.com"
}
}
包的许可证类型。
说明:npm 推荐使用 SPDX License ID 或 SPDX License Expression。如果是自定义许可证,可使用
SEE LICENSE IN <filename>,并在包根目录包含对应的许可证文件。
// SPDX 标识符
{ "license": "MIT" }
{ "license": "Apache-2.0" }
{ "license": "GPL-3.0-only" }
{ "license": "ISC" }
// 未开源
{ "license": "UNLICENSED" }
// 多许可证(用户可选择)
{ "license": "(MIT OR Apache-2.0)" }
// 自定义/非 SPDX 许可证
{ "license": "SEE LICENSE IN LICENSE" }
补充:旧式的 licenses: [{ type, url }] 或 license: { type, url } 写法已被 npm 标记为过时(deprecated),建议改用 SPDX 表达式。
常见许可证对比:
| 许可证 | 商业使用 | 修改分发 | 专利授权 | 需保留版权 |
|---|---|---|---|---|
| MIT | ✓ | ✓ | - | ✓ |
| Apache-2.0 | ✓ | ✓ | ✓ | ✓ |
| GPL-3.0-only | ✓ | ✓(开源) | ✓ | ✓ |
| BSD-3-Clause | ✓ | ✓ | - | ✓ |
| ISC | ✓ | ✓ | - | ✓ |
注:上表为“快速对比”而非严格法律结论。这里的“专利授权”指许可证文本中是否包含明确的专利许可条款;MIT/BSD/ISC 通常不包含显式专利授权条款,但在不同司法辖区可能存在“默示授权/禁止反悔”等解释空间。
包的作者和贡献者信息。
// 字符串格式
{
"author": "John Doe <john@example.com> (https://johndoe.com)"
}
// 对象格式
{
"author": {
"name": "John Doe",
"email": "john@example.com",
"url": "https://johndoe.com"
}
}
// 贡献者列表
{
"contributors": [
"Jane Smith <jane@example.com>",
{
"name": "Bob Wilson",
"email": "bob@example.com"
}
]
}
项目资助信息。
// 单一资助链接
{
"funding": "https://opencollective.com/myproject"
}
// 对象形式
{
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/myproject"
}
}
// 多个资助渠道
{
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/user"
},
{
"type": "patreon",
"url": "https://patreon.com/user"
}
]
}
源代码仓库地址。
// 简写形式(GitHub)
{
"repository": "github:user/repo"
}
// 完整形式
{
"repository": {
"type": "git",
"url": "https://github.com/user/repo.git"
}
}
// Monorepo 中指定目录
{
"repository": {
"type": "git",
"url": "https://github.com/user/monorepo.git",
"directory": "packages/my-package"
}
}
指定包的主入口文件,当其他模块 require() 或 import 包时使用。
{
"main": "./dist/index.js"
}
index.js指定浏览器环境的入口文件,用于替代 main。
{
"main": "./dist/index.js",
"browser": "./dist/browser.js"
}
也可以用对象形式替换特定模块:
{
"browser": {
"./lib/server.js": "./lib/browser.js",
"fs": false
}
}
指定 ES Module 格式的入口文件(非官方标准,但被打包工具广泛支持)。
{
"main": "./dist/index.cjs",
"module": "./dist/index.mjs"
}
指定 .js 文件的模块类型。
// ES Module(推荐新项目使用)
{
"type": "module"
}
// CommonJS(默认值)
{
"type": "commonjs"
}
文件扩展名与模块类型对照:
| type 值 | .js 文件解析为 | .mjs 文件 | .cjs 文件 |
|---|---|---|---|
| “module” | ES Module | ES Module | CommonJS |
| “commonjs” | CommonJS | ES Module | CommonJS |
| 未设置 | CommonJS | ES Module | CommonJS |
exports 是现代包的推荐入口点配置方式,提供了比 main 更强大的功能:
基本用法:
{
"exports": "./dist/index.js"
}
多入口点:
{
"exports": {
".": "./dist/index.js",
"./utils": "./dist/utils.js",
"./helpers/*": "./dist/helpers/*.js"
}
}
条件导出:
{
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.cjs",
"default": "./dist/index.js"
}
}
}
完整的条件导出配置:
{
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.cjs",
"default": "./dist/index.js"
},
"./utils": {
"types": "./dist/utils.d.ts",
"import": "./dist/utils.mjs",
"require": "./dist/utils.cjs"
},
"./package.json": "./package.json"
}
}
条件导出优先级(从上到下):
| 条件 | 说明 |
|---|---|
| types | TypeScript 类型定义(应放首位) |
| import | ES Module 环境 |
| require | CommonJS 环境 |
| node | Node.js 环境 |
| browser | 浏览器环境 |
| development | 开发环境 |
| production | 生产环境 |
| default | 默认回退 |
定义包内部的导入映射,使用 # 前缀。
{
"imports": {
"#utils": "./src/utils/index.js",
"#config": "./src/config.js",
"#lib/*": "./src/lib/*.js"
}
}
在代码中使用:
// 不再需要写相对路径
import { helper } from "#utils";
import config from "#config";
条件导入:
{
"imports": {
"#platform": {
"node": "./src/platform-node.js",
"browser": "./src/platform-browser.js",
"default": "./src/platform-default.js"
}
}
}
指定发布到 npm 时包含的文件。
{
"files": ["dist", "lib", "es", "README.md", "LICENSE"]
}
始终包含的文件(无需列出):
始终排除的文件:
定义可执行命令。
// 单个命令(命令名与包名相同)
{
"name": "my-cli",
"bin": "./bin/cli.js"
}
// 多个命令
{
"bin": {
"mycli": "./bin/cli.js",
"mycli-init": "./bin/init.js"
}
}
可执行文件要求:
#!/usr/bin/env node
// bin/cli.js 的第一行必须是 shebang
console.log("Hello from CLI!");
指定 man 手册页。
// 单个手册
{
"man": "./man/doc.1"
}
// 多个手册
{
"man": [
"./man/myapp.1",
"./man/myapp-config.5"
]
}
指定项目目录结构(主要用于文档目的)。
{
"directories": {
"bin": "./bin",
"lib": "./lib",
"man": "./man",
"doc": "./doc",
"example": "./examples",
"test": "./test"
}
}
scripts 字段定义可通过 npm run 执行的命令。
{
"scripts": {
"dev": "vite",
"build": "vite build",
"test": "vitest",
"lint": "eslint src",
"format": "prettier --write src"
}
}
执行脚本:
npm run dev
npm run build
# test/start/stop/restart 可省略 run
npm test
npm start
NPM 在特定操作时自动执行的脚本。
{
"scripts": {
"prepare": "husky install",
"prepublishOnly": "npm run build && npm test",
"preinstall": "node check-environment.js",
"postinstall": "node setup.js"
}
}
主要生命周期脚本:
| 脚本 | 触发时机 |
|---|---|
| preinstall | 安装包之前 |
| install | 安装包时 |
| postinstall | 安装包之后 |
| prepare | 安装后、发布前(常用于构建和设置 Git hooks) |
| prepublishOnly | 仅在 npm publish 之前 |
| prepack | 打包前(tarball 创建前) |
| postpack | 打包后 |
| prepublish | 已废弃,避免使用 |
任何自定义脚本都可以添加 pre 和 post 前缀钩子。
{
"scripts": {
"prebuild": "rm -rf dist",
"build": "tsc",
"postbuild": "node scripts/copy-assets.js",
"pretest": "npm run lint",
"test": "vitest",
"posttest": "echo 'Tests completed!'"
}
}
执行 npm run build 时,执行顺序为:
prebuildbuildpostbuild{
"scripts": {
"dev": "vite",
"build": "vite build",
"test": "vitest",
"test:watch": "vitest --watch",
"test:coverage": "vitest --coverage",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"format": "prettier --write .",
"typecheck": "tsc --noEmit",
"clean": "rm -rf dist node_modules",
"prepare": "husky install"
}
}
命名规范:使用动词开头,变体用冒号分隔(如 test:watch)。
生产环境必需的依赖,会在 npm install --production 时安装。
{
"dependencies": {
"express": "^4.18.2",
"lodash": "^4.17.21"
}
}
仅开发环境需要的依赖,不会在生产环境安装。
{
"devDependencies": {
"typescript": "^5.0.0",
"vitest": "^1.0.0",
"eslint": "^8.0.0",
"@types/node": "^20.0.0"
}
}
dependencies vs devDependencies 选择指南:
| 依赖类型 | 放置位置 | 示例 |
|---|---|---|
| 运行时必需 | dependencies | express, react, lodash |
| 构建工具 | devDependencies | webpack, vite, esbuild |
| 测试框架 | devDependencies | jest, vitest, mocha |
| 代码检查 | devDependencies | eslint, prettier |
| 类型定义 | devDependencies | @types/*, typescript |
| 文档生成 | devDependencies | typedoc, jsdoc |
声明包与宿主项目共享的依赖,避免重复安装。
{
"name": "my-react-component",
"peerDependencies": {
"react": "^17.0.0 || ^18.0.0",
"react-dom": "^17.0.0 || ^18.0.0"
}
}
使用场景:
注意: npm 7+ 默认自动安装 peerDependencies。
为 peerDependencies 提供额外元数据。
{
"peerDependencies": {
"react": "^18.0.0",
"react-native": "^0.70.0"
},
"peerDependenciesMeta": {
"react-native": {
"optional": true
}
}
}
可选依赖,安装失败不会导致整体安装失败。
{
"optionalDependencies": {
"fsevents": "^2.3.2"
}
}
代码中需要处理依赖不存在的情况:
try {
const fsevents = require("fsevents");
// 使用 fsevents
} catch (err) {
// 回退方案
}
指定发布时打包到 tarball 中的依赖。
{
"bundleDependencies": ["internal-package", "legacy-module"]
}
强制指定依赖树中某个包的版本(npm 8.3+)。
{
"overrides": {
// 全局覆盖
"lodash": "4.17.21",
// 特定依赖下的覆盖
"webpack": {
"terser": "5.15.0"
},
// 嵌套覆盖
"react-scripts": {
"webpack": {
"terser": "5.15.0"
}
}
}
}
Yarn 使用 resolutions:
{
"resolutions": {
"lodash": "4.17.21",
"webpack/terser": "5.15.0"
}
}
| 语法 | 示例 | 匹配范围 |
|---|---|---|
| 精确版本 | 1.2.3 |
仅 1.2.3 |
^ |
^1.2.3 |
>=1.2.3 <2.0.0(主版本锁定) |
~ |
~1.2.3 |
>=1.2.3 <1.3.0(次版本锁定) |
>/< |
>1.2.3 |
大于 1.2.3 |
>=/<= |
>=1.2.3 |
大于等于 1.2.3 |
- |
1.2.3 - 2.0.0 |
>=1.2.3 <=2.0.0 |
x/* |
1.2.x |
>=1.2.0 <1.3.0 |
\|\| |
^1.0 \|\| ^2.0 |
1.x 或 2.x |
latest |
latest |
最新发布版本 |
* |
* |
任意版本 |
URL 依赖:
{
"dependencies": {
"local-pkg": "file:../local-pkg",
"git-pkg": "git+https://github.com/user/repo.git",
"github-pkg": "github:user/repo#branch",
"tarball-pkg": "https://example.com/package.tgz"
}
}
指定项目运行所需的 Node.js 和 npm 版本。
{
"engines": {
"node": ">=18.0.0",
"npm": ">=9.0.0"
}
}
严格执行(需要配置):
创建 .npmrc 文件:
engine-strict=true
或配置 package.json:
{
"engines": {
"node": ">=18.0.0"
},
"engineStrict": true
}
限制包运行的操作系统。
// 仅允许特定系统
{
"os": ["darwin", "linux"]
}
// 排除特定系统
{
"os": ["!win32"]
}
可用值: darwin、linux、win32、freebsd、openbsd、sunos、aix
限制包运行的 CPU 架构。
// 仅允许特定架构
{
"cpu": ["x64", "arm64"]
}
// 排除特定架构
{
"cpu": ["!arm", "!mips"]
}
可用值: x64、arm、arm64、ia32、mips、ppc64、s390x
设置为 true 防止包被意外发布。
{
"private": true
}
使用场景:
发布时使用的配置。
{
"publishConfig": {
"registry": "https://npm.company.com/",
"access": "public",
"tag": "next"
}
}
常用配置项:
| 字段 | 说明 |
|---|---|
| registry | 发布目标仓库 |
| access | public 或 restricted |
| tag | 发布时的标签(默认 latest) |
发布作用域包为公开:
{
"name": "@org/my-package",
"publishConfig": {
"access": "public"
}
}
提示:
publishConfig中的配置项也可以在项目的.npmrc文件中设置。例如:registry=https://npm.company.com/ access=public两者的区别在于:
package.json中的publishConfig会随包一起发布,而.npmrc通常用于本地或 CI 环境配置,且可以包含敏感信息(如 token),不建议提交到版本控制。
workspaces 用于配置 Monorepo,管理多个相关包。
{
"name": "my-monorepo",
"private": true,
"workspaces": ["packages/*", "apps/*"]
}
目录结构:
my-monorepo/
├── package.json
├── packages/
│ ├── core/
│ │ └── package.json
│ ├── utils/
│ │ └── package.json
│ └── cli/
│ └── package.json
└── apps/
├── web/
│ └── package.json
└── api/
└── package.json
根 package.json:
{
"name": "my-monorepo",
"private": true,
"workspaces": ["packages/*", "apps/*"],
"scripts": {
"build": "npm run build --workspaces",
"test": "npm run test --workspaces --if-present"
}
}
子包 package.json:
{
"name": "@org/core",
"version": "1.0.0",
"main": "./dist/index.js",
"dependencies": {
"@org/utils": "workspace:*"
}
}
# 在所有工作空间运行脚本
npm run build --workspaces
# 在特定工作空间运行
npm run build -w @org/core
# 添加依赖到特定工作空间
npm install lodash -w @org/utils
# 运行脚本(跳过没有该脚本的包)
npm run test --workspaces --if-present
设置脚本中可用的环境变量。
{
"name": "my-package",
"config": {
"port": "8080",
"host": "localhost"
},
"scripts": {
"start": "node server.js"
}
}
在脚本中访问:
// 通过 npm_package_config_ 前缀访问
const port = process.env.npm_package_config_port; // "8080"
用户可覆盖配置:
npm config set my-package:port 3000
告知打包工具(如 webpack)包是否有副作用,用于 Tree Shaking 优化。
// 无副作用,可以安全 Tree Shake
{
"sideEffects": false
}
// 指定有副作用的文件
{
"sideEffects": [
"*.css",
"*.scss",
"./src/polyfills.js"
]
}
指定 TypeScript 类型定义入口文件。
{
"main": "./dist/index.js",
"types": "./dist/index.d.ts"
}
// 或使用 typings(等效)
{
"typings": "./dist/index.d.ts"
}
配合 exports 使用:
{
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
}
}
}
注意:以下示例仅展示各类项目的核心配置,实际项目可根据需要扩展。
{
"name": "my-web-app",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "vite",
"build": "vite build",
"test": "vitest",
"lint": "eslint src",
"prepare": "husky install"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"vite": "^5.0.0",
"typescript": "^5.0.0"
}
}
关键点:private: true 防止意外发布。
{
"name": "@org/utils",
"version": "1.0.0",
"license": "MIT",
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs"
}
},
"files": ["dist"],
"sideEffects": false,
"scripts": {
"build": "tsup",
"prepublishOnly": "npm run build && npm test"
},
"publishConfig": {
"access": "public"
}
}
关键点:exports 实现条件导出,sideEffects: false 启用 Tree Shaking。
{
"name": "my-cli",
"version": "1.0.0",
"type": "module",
"bin": {
"my-cli": "./dist/cli.js"
},
"files": ["dist"],
"dependencies": {
"commander": "^11.0.0"
},
"engines": {
"node": ">=18.0.0"
}
}
关键点:bin 定义可执行命令,入口文件需添加 #!/usr/bin/env node。
{
"name": "my-monorepo",
"private": true,
"workspaces": ["packages/*", "apps/*"],
"scripts": {
"build": "npm run build --workspaces",
"test": "npm run test --workspaces --if-present"
}
}
关键点:workspaces 定义子包路径,--if-present 跳过无对应脚本的包。
使用版本管理工具
# 使用 npm version
npm version patch # 1.0.0 → 1.0.1
npm version minor # 1.0.0 → 1.1.0
npm version major # 1.0.0 → 2.0.0
# 或使用 changesets(推荐用于 Monorepo)
npx changeset
npm publish --tag beta
npm publish --tag next
定期更新依赖
# 检查过期依赖
npm outdated
# 使用 npm-check-updates
npx npm-check-updates
npx npm-check-updates -u
package-lock.jsonnpm ci 而非 npm installdependenciesdevDependenciespeerDependenciesnpm audit
npm audit fix
使用跨平台工具
{
"scripts": {
// 使用 rimraf 替代 rm -rf
"clean": "rimraf dist",
// 使用 cross-env 设置环境变量
"build": "cross-env NODE_ENV=production webpack"
}
}
build、test、linttest:watch、build:prod{
"scripts": {
"validate": "npm run lint && npm run typecheck && npm run test"
}
}
name 和 version 正确设置description 清晰描述包功能keywords 包含相关关键词license 已指定repository 指向正确地址main/exports 入口点正确files 包含所有必要文件engines 指定支持的 Node.js 版本README.md 完整Q1: 如何解决依赖冲突?
// 使用 overrides(npm 8+)
{
"overrides": {
"problematic-package": "1.2.3"
}
}
# 或使用 --force 安装
npm install --force
# 使用 --legacy-peer-deps 忽略 peerDependencies 冲突
npm install --legacy-peer-deps
Q2: 如何处理 ESM 和 CommonJS 兼容性?
{
"type": "module",
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
}
}
}
Q3: 如何排除文件发布?
创建 .npmignore 文件:
src/
test/
*.test.js
.eslintrc
tsconfig.json
或使用 files 字段白名单方式(推荐):
{
"files": ["dist", "README.md"]
}
Q4: 如何本地测试包发布内容?
# 查看将要发布的文件
npm pack --dry-run
# 创建 tarball 检查内容
npm pack
tar -tzf my-package-1.0.0.tgz
Q5: 如何处理 monorepo 中的内部依赖?
{
"dependencies": {
"@org/utils": "workspace:*"
}
}
Q6: 如何指定不同环境的脚本?
{
"scripts": {
"start": "node index.js",
"start:dev": "nodemon index.js",
"start:prod": "NODE_ENV=production node index.js"
}
}
Q7: exports 和 main 应该同时使用吗?
推荐同时设置,以兼容老版本 Node.js:
{
"main": "./dist/index.js",
"exports": {
".": "./dist/index.js"
}
}
Q8: 如何发布到私有仓库?
{
"publishConfig": {
"registry": "https://npm.your-company.com/"
}
}
或在 .npmrc 中配置:
@your-org:registry=https://npm.your-company.com/
//npm.your-company.com/:_authToken=${NPM_TOKEN}
package.json 是 Node.js 项目的核心配置文件,掌握其配置对于高效开发至关重要。
| 场景 | 关键字段 | 注意事项 |
|---|---|---|
| 包标识 | name, version |
遵循命名规则和语义化版本 |
| 模块入口 | main, exports, type |
优先使用 exports 实现条件导出 |
| 依赖管理 | dependencies, devDependencies, peerDependencies |
正确分类,定期审计 |
| 脚本命令 | scripts |
善用生命周期钩子和命名规范 |
| 发布控制 | private, files, publishConfig |
发布前检查包含文件 |
| Monorepo | workspaces |
使用 workspace:* 协议管理内部依赖 |
main 和 exports 以兼容不同 Node.js 版本files 白名单而非 .npmignore 黑名单npm ci 而非 npm installnpm audit 检查安全漏洞package.jsondependencies 中放置开发工具