深度解析 `package.json` 的 `exports`/`imports`:类型与运行时的双重驾驭
exports(导出条件):Node.js 12+ 引入,用于定义模块在不同环境(ESM/CJS)下的入口点。它取代了mainmodule等字段,实现条件化导出。imports(内部导入):Node.js 12+ 支持,用于在代码中定义路径别名(如),需配合中的imports字段使用。✅关键区别exports导出模块的入口(供外部使用)imports导入内部模块的别名(供代码内使用)(Node.j
你是否曾在TypeScript项目中因库的类型定义缺失而卡住?或在Node.js中反复遭遇require与import的兼容性陷阱?package.json的exports和imports字段是现代JS生态的“瑞士军刀”,能同时解决类型安全与运行时兼容问题。本文将手把手拆解其设计逻辑,让你的库/应用在多环境无缝运行——无需再为环境差异反复调试。
一、基本概念与定义
exports(导出条件):Node.js 12+ 引入,用于定义模块在不同环境(ESM/CJS)下的入口点。它取代了main、module等字段,实现条件化导出。
imports(内部导入):Node.js 12+ 支持,用于在代码中定义路径别名(如import { a } from "#/utils"),需配合package.json中的imports字段使用。
✅ 关键区别:
exports:导出模块的入口(供外部使用)imports:导入内部模块的别名(供代码内使用)
(Node.js 12.17+ 支持两者,浏览器不支持imports)
二、关键属性与最小可运行示例
1. exports 的条件结构
{
"exports": {
".": {
"import": "./dist/index.mjs", // ESM 代码入口
"require": "./dist/index.cjs", // CJS 代码入口
"types": "./dist/index.d.ts" // TypeScript 类型定义
},
"./utils": {
"import": "./dist/utils.mjs",
"require": "./dist/utils.cjs"
}
}
}
输入/输出说明:
- 当外部代码使用
import * as lib from 'my-lib'→ Node.js 优先匹配import字段 → 加载./dist/index.mjs - 当使用
const lib = require('my-lib')→ 匹配require字段 → 加载./dist/index.cjs - TypeScript 编译时 → 通过
types字段找到./dist/index.d.ts
💡 边界情况:若未指定
types,TypeScript 会报错“无法解析模块”;若未指定import/require,Node.js 会回退到main(不推荐)。
2. imports 的别名使用
{
"imports": {
"#/utils": "./dist/utils.mjs" // 定义内部别名
}
}
代码示例:
// 代码中使用别名
import { fetchData } from '#/utils'; // 实际解析为 ./dist/utils.mjs
⚠️ 注意:
imports仅在Node.js中生效,浏览器需用module标签或构建工具处理。
三、工作原理:Node.js 模块解析流程
Node.js 解析模块的优先级顺序(从高到低):
- 检查
package.json中的exports条件 - 根据环境匹配字段:
- ESM 代码 → 优先
import - CJS 代码 → 优先
require
- ESM 代码 → 优先
- 若未匹配 → 回退到
main(若存在) - 若仍失败 → 报错
Cannot find module

(简化版:环境 → 匹配条件 → 加载文件)
📌 关键点:
exports的字段顺序决定优先级。例如:"exports": { ".": { "require": "./cjs", // 优先级高 "import": "./esm" // 优先级低 } }此配置下,CJS 会加载
./cjs,ESM 会加载./esm。
四、实战示例:正确用法 vs 错误用法
场景1:库开发(正确写法)
目标:确保TypeScript类型与Node.js运行时一致。
package.json:
{
"name": "my-lib",
"version": "1.0.0",
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.cjs",
"types": "./dist/index.d.ts"
}
},
"scripts": {
"build": "tsc --outDir dist"
}
}
TypeScript 代码(src/index.ts):
export function add(a: number, b: number): number {
return a + b;
}
构建流程:
npm run build→ 生成dist/index.mjs(ESM)、dist/index.cjs(CJS)、dist/index.d.ts- TypeScript 使用
types字段 → 自动识别类型 - Node.js ESM 代码 → 从
dist/index.mjs加载 - Node.js CJS 代码 → 从
dist/index.cjs加载
✅ 效果:无类型错误,运行时兼容。
场景2:常见错误(类型不匹配)
错误 package.json:
{
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
}
}
}
问题:
- TypeScript 编译时找不到类型定义 → 报错
Could not find a declaration file for module 'my-lib' - 修复:必须添加
"types": "./dist/index.d.ts"
错误 package.json(条件顺序问题):
{
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
}
}
}
✖️ 问题:若外部代码使用
import但Node.js版本<12,会回退到main(可能不存在),导致Cannot find module。
五、应用场景与最佳实践
| 场景 | 推荐方案 | 为什么? |
|---|---|---|
| 新库开发 | 仅用 exports(弃用 main/module) |
避免环境冲突,TypeScript/Node.js统一支持 |
| 类型安全要求高 | 必须指定 types 字段 |
TypeScript 依赖此字段解析类型 |
| 浏览器兼容 | 用 exports 的 browser 条件 |
如 "browser": "./dist/index.browser.mjs" |
| 内部模块别名 | 用 imports 定义路径别名 |
简化代码,避免相对路径冗余 |
✅ 最佳实践:
- 为新库直接使用
exports,不保留main/module。- 所有类型定义必须放在
types字段。- 用
browser条件处理浏览器环境(如React库)。
六、常见坑与排错建议
| 坑点 | 排错方法 |
|---|---|
| TypeScript 类型缺失 | 检查 package.json 是否有 types 字段 |
Node.js 未匹配 import |
用 node -p "require('my-lib').default" 测试 |
| 条件顺序错误 | 按优先级排序:import → require → default |
imports 别名未生效 |
确认Node.js版本≥12.17(node -v) |
💡 防御性写法:
"exports": { ".": { "import": "./dist/index.mjs", "require": "./dist/index.cjs", "types": "./dist/index.d.ts", "default": "./dist/index.cjs" // 作为回退 } }
七、性能与安全注意
-
性能:
exports仅影响模块解析阶段,无额外运行时开销。
(Node.js 会缓存解析结果,无需担心多次加载) -
安全:
exports本身无安全风险(路径解析在沙箱内)。- 避免在
exports中使用../或*通配符(可能引发路径遍历),例如:"exports": { ".": { "import": "./dist/*.mjs" // ❌ 不安全! } }
八、与相关概念的对比
| 概念 | 用途 | 适用场景 | 选型建议 |
|---|---|---|---|
exports |
定义模块导出条件 | 新库开发、多环境兼容 | 优先使用 |
main/module |
旧版入口点(单环境) | 遗留项目迁移 | 逐步替换为 exports |
imports |
代码内路径别名(Node.js专属) | 内部模块简化导入 | 按需使用 |
browser 条件 |
为浏览器环境指定单独入口 | 浏览器库(如React、Vue) | 在 exports 中添加 |
✅ 选型结论:
- 新项目:直接用
exports+types,无需main/module。- 浏览器支持:在
exports中添加"browser"条件。
结语:掌握核心,进阶路径
exports 和 imports 是Node.js 12+ 生态的基石,正确使用能彻底解决类型与运行时的双重矛盾。核心要点:
- 用
exports替代main/module,指定import/require/types。 - 用
imports简化内部导入,但仅限Node.js。 - 必须为TypeScript提供
types字段。
更多推荐


所有评论(0)