这是一个非常好的问题!你在 package.json 中看到的这种写法:
"exports": {"./package.json": "./package.json"
}
并不是“一般会”这样写,而是有明确目的的特殊配置。让我为你详细解释它的作用、为什么需要它,以及不加会有什么问题。
🔍 核心原因:Node.js 的模块解析规则变更
从 Node.js 12.0+(ESM 模式)和 Node.js 14.13+(CommonJS) 开始,当一个包定义了 "exports" 字段后,Node.js 会严格限制外部能访问的文件 —— 只有在 exports 中显式声明的路径才能被导入!
📌 这叫 "Exports Enforcement"(导出强制),目的是:
- 防止用户访问内部未公开的文件(如
dist/internal.js)- 让包作者完全控制公共 API
⚠️ 如果不加 "./package.json": "./package.json"
场景:用户想读取你的包版本号
// 用户代码
import pkg from 'your-package/package.json';
console.log(pkg.version); // 期望输出 "1.0.0"
结果:
Error [ERR_PACKAGE_PATH_NOT_EXPORTED]:
Package subpath './package.json' is not defined by "exports"
💥 即使
package.json文件真实存在,也会报错!
因为 Node.js 看到你的 package.json 有 "exports" 字段,就禁止访问任何未声明的路径,包括 package.json 本身!
✅ 为什么需要显式导出 package.json?
常见使用场景:
| 场景 | 说明 |
|---|---|
| 读取版本号 | import { version } from 'my-lib/package.json' |
| 工具链检测 | Webpack/Vite/Babel 会读取 package.json 的 sideEffects、type 等字段 |
| 运行时配置 | 应用启动时读取包的元信息 |
🌰 例如:Vite 在解析依赖时会检查
package.json的exports和type字段。
🧩 完整示例
假设你开发了一个库 my-utils:
// my-utils/package.json
{"name": "my-utils","version": "1.2.3","type": "module","exports": {".": "./dist/index.js","./package.json": "./package.json" // ← 关键!}
}
现在用户可以安全地:
// ✅ 成功!
import { version } from 'my-utils/package.json';
console.log(version); // "1.2.3"
如果去掉那行:
// ❌ 失败!
import pkg from 'my-utils/package.json'; // 报错 ERR_PACKAGE_PATH_NOT_EXPORTED
📦 其他需要显式导出的常见文件
除了 package.json,以下文件也常需要显式导出:
"exports": {".": "./dist/index.js","./package.json": "./package.json","./styles.css": "./dist/styles.css", // CSS 文件"./types": "./dist/types/index.d.ts", // 类型定义"./utils/*": "./dist/utils/*.js" // 通配符导出
}
⚖️ 是否“一般都会”这样写?
| 项目类型 | 是否需要 |
|---|---|
| 纯 ESM 库(现代工具链) | ✅ 强烈建议添加 |
| 仅用于浏览器的库 | ⚠️ 可能不需要(但 Vite/webpack 仍会读取) |
| 旧版 CommonJS 库 | ❌ 不需要(但建议升级) |
| TypeScript 库 | ✅ 需要(tsc 可能读取 package.json) |
📊 现实情况:
主流开源库(如 Vue、React、Lodash ESM 版)都显式导出了package.json。
🔧 最佳实践建议
1. 始终导出 package.json
{"exports": {".": "./dist/index.js","./package.json": "./package.json"}
}
2. TypeScript 用户额外注意
如果你用 tsc 编译,可能还需要在 types 字段中处理:
{"types": "./dist/index.d.ts","exports": {".": {"import": "./dist/index.js","types": "./dist/index.d.ts"},"./package.json": "./package.json"}
}
3. 不要导出整个目录
❌ 错误做法:
"exports": {"./*": "./*" // 危险!暴露所有内部文件
}
✅ 正确做法:只导出明确需要的文件
💡 有趣事实:Node.js 官方推荐
Node.js 官方文档明确建议:
"If your package.json is meant to be available to consumers, it should be exported."
(如果你的 package.json 需要被使用者访问,就应该导出它)
来源:Node.js Package Entry Points
✅ 总结
| 问题 | 答案 |
|---|---|
为什么导出 ./package.json? |
因为 Node.js 的 exports 强制策略会阻止访问未声明的文件 |
| 不加会怎样? | 用户无法通过 import 'xxx/package.json' 读取版本等信息 |
| 是否必须加? | ✅ 现代 JavaScript 库强烈建议添加 |
| 这是“一般做法”吗? | 是的,在 ESM 时代已成为标准实践 |
🎯 一句话记住:
只要你的package.json有"exports"字段,就必须显式导出./package.json,否则用户会遇到奇怪的导入错误!