package.json 深入与前端工程化配置
package.json 全字段详解:exports 出口、engines、peerDependencies、包管理器脚本与 lint/format 工具链。
0. 一句话理解
package.json 不只是”依赖清单”:
exports决定包的公共 API 边界,engines锁运行时版本,peerDependencies声明宿主契约,scripts串联生命周期,packageManager固定工具链——它是 Node 生态的工程契约文件。
1. 双角色:应用清单 vs 包清单
同一份文件在两类项目里关注点完全不同:
- 应用(网站、服务):
dependencies/devDependencies、scripts、engines、packageManager是主角; - 库(发布到 npm):
name/version/exports/files/peerDependencies/sideEffects是主角,对外暴露的每个字段都构成公共 API。
2. 依赖字段辨析
{
"dependencies": { "express": "^5.1.0" },
"devDependencies": { "vitest": "^3.0.0" },
"peerDependencies": { "react": ">=19" },
"optionalDependencies": { "fsevents": "^2.3.3" },
"overrides": { "semver": "^7.6.0" }
}
| 字段 | 语义 | 常见误用 |
|---|---|---|
| dependencies | 运行时必需 | 把构建工具放进来 |
| devDependencies | 开发/构建/测试用 | 库作者把真正运行时依赖错放这里 |
| peerDependencies | 声明”宿主必须提供”,插件/组件库用 | 把工具库当 peer 装给应用 |
| optionalDependencies | 装不上不报错 | 把核心依赖写成 optional |
| overrides(npm)/ pnpm.overrides | 强制统一传递依赖版本 | 滥用掩盖版本冲突的真实原因 |
版本范围:^5.1.0 允许 5.x;~5.1.0 允许 5.1.x;精确版本 + lockfile 是应用项目的稳态;库项目避免锁死精确版本。
3. exports:现代包的公共 API 边界
{
"name": "@lib/core",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.cjs",
"default": "./dist/index.mjs"
},
"./utils": "./dist/utils.mjs",
"./package.json": "./package.json"
}
}
要点:
exports存在时,未列出的子路径一律禁止导入——这是 API 收敛的最强手段;- 条件顺序有意义:
types必须在最前(TypeScript 按moduleResolution匹配),import/require分别对应 ESM/CJS 消费方; - 旧字段
main/module仍被部分工具读取,发布库时建议双写过渡; typesVersions/publishConfig用于发布前改写,避免仓库内路径与发布产物不一致。
4. engines 与 packageManager:锁定运行链路
{
"engines": { "node": ">=24", "pnpm": ">=10" },
"packageManager": "pnpm@10.14.0",
"type": "module"
}
engines声明运行时门槛;npm 默认仅警告(engine-strict=true转硬错误),pnpm 默认强校验;packageManager被 corepack 消费:团队成员corepack enable后自动使用同一版本包管理器,杜绝”我这里能装你那里不行”;"type": "module"让.js按 ESM 解析;纯 CJS 遗留包写"type": "commonjs",混排场景用.mjs/.cjs后缀精确控制。
5. scripts 与生命周期钩子
{
"scripts": {
"predev": "node scripts/check-env.mjs",
"dev": "vite",
"build": "vite build",
"postbuild": "node scripts/verify-dist.mjs",
"test": "vitest run",
"lint": "eslint . --max-warnings 0",
"format": "prettier --write ."
}
}
pre/post前缀自动串联(npm run dev前先跑predev);- npm 注入的
npm_package_*环境变量、prepack/postinstall等发布/安装钩子是 CI 常用抓手;postinstall脚本是供应链攻击的常见入口,第三方包的安装脚本要审(--ignore-scripts可禁用); - 跨平台写法:避免直接写
&&与 Unix 命令,用node scripts/xx.mjs或cross-env保证 Windows CI 可跑。
6. files、sideEffects 与发布卫生
{
"files": ["dist", "README.md"],
"sideEffects": ["*.css"],
"publishConfig": { "registry": "https://registry.npmjs.org/" }
}
files白名单决定发布内容(npm pack --dry-run预览),配合.npmignore双保险;sideEffects: false告诉打包器”本包可安全 Tree Shaking”,含副作用的文件(CSS、polyfill)单独列出(原理见javascript/390-ModuleDynamicImportCodeSplitting相关章节);- 发布前动作清单:
npm pack --dry-run查体积与内容 →npm publish --dry-run→ 带 2FA 正式发布;monorepo 用 Changesets 管版本(见vite/210-ChangesetsRelease)。
7. lint 与 format:代码质量工具链
7.1 ESLint(flat config)
// eslint.config.js(扁平配置,ESLint 9+ 默认)
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
export default tseslint.config(
js.configs.recommended,
...tseslint.configs.recommended,
{
rules: {
'no-console': ['warn', { allow: ['warn', 'error'] }],
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
},
},
);
7.2 Prettier 与 Biome
{
"scripts": {
"lint": "biome check .",
"format": "biome format --write ."
},
"devDependencies": { "@biomejs/biome": "^2.0.0" }
}
- 传统组合:ESLint(规则)+ Prettier(格式),需处理两者规则冲突(eslint-config-prettier);
- 新选择:Biome 一体化(Rust 实现,lint + format 单二进制,速度快一个量级),新项目可直接起步;
- 提交门禁:husky/lefthook 挂 pre-commit 跑 lint-staged,只检查暂存文件。
8. 安全与供应链
npm audit/pnpm audit结合 CI 门禁,新增高危依赖阻断合并;overrides修传递依赖的安全版本(例如强制semver无漏洞版本),修完删除临时 override;- 锁文件(
package-lock.json/pnpm-lock.yaml)必须入库;应用项目建议npm ci/pnpm install --frozen-lockfile安装; - lockfile 是审计与可复现构建的基石——它记录的是”解析后的确切依赖图”。
9. 动手试试
- 给一个库项目配置
exports(双格式 + types 条件),并验证require与import两条消费路径; - 用
npm pack --dry-run检查发布内容,把测试文件从包里剔除; - 在 monorepo 根配置
packageManager与 corepack,让两个包管理器版本并存的问题消失; - 把项目的 ESLint 迁移到 flat config,或用 Biome 替代并对比单次全量 lint 耗时。
10. 一句话记住
package.json 是工程契约:exports 圈定公共 API、engines/packageManager 锁工具链、peerDependencies 立宿主契约、sideEffects 换 Tree Shaking;质量门禁交给 ESLint/Biome,锁文件永远入库。