ESM 与 CJS 双格式包输出
生成 ES 模块和 CommonJS 构建版本,并正确配置 package.json 的 exports 字段
ESM 与 CJS 双格式包输出 是 CoddyKit 上的免费 React Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 React Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 React Academy 课程共包含 4 节课。
什么是 ESM
ESM(ECMAScript 模块)是官方的 JavaScript 模块标准,使用 import 和 export 语法。ESM 可以进行静态分析——打包工具能够在构建时准确确定使用了哪些导出内容,从而实现摇树优化。现代浏览器和 Node.js 都原生支持 ESM。
什么是 CJS
CJS(CommonJS)使用 require() 和 module.exports 语法。它是 Node.js 最初的模块系统,目前仍然用于兼容较旧的 Node.js 环境、Jest(历史上使用 CJS)以及使用 require() 的代码。CJS 会动态求值,因此更难进行摇树优化。
双格式包:同时发布两种格式
现代 npm 包应同时发布 ESM 和 CJS,以最大限度地提高兼容性。ESM 使用者(Vite、Next.js、现代打包工具)可以使用支持摇树优化的导入方式。CJS 使用者(较旧的 Node.js 脚本、未配置的 Jest)可以获得 require() 兼容性。package.json 的 exports 字段会告诉 Node 和打包工具应使用哪种格式。
exports 字段
package.json 中的 exports 字段是定义条件入口点的现代方式。例如:{ '.': { 'import': './dist/esm/index.js', 'require': './dist/cjs/index.js', 'types': './dist/esm/index.d.ts' } }。打包工具和 Node.js 12+ 会读取 exports,以自动选择正确的格式。
旧版 main 和 module 字段
较旧的工具无法理解 exports 字段。为确保兼容性,还应设置:指向 CJS 输出的 main(旧版 Node require 的回退入口),以及指向 ESM 输出的 module(供 webpack/Rollup 使用的提示,虽非标准但得到广泛支持)。现代工具优先使用 exports,旧版工具则回退到 main/module。
type: module 的影响
在 package.json 中设置 "type": "module" 后,包中的所有 .js 文件都会被视为 ESM。如果发布双格式包,就需要使用明确的扩展名:当 type 为 module 时,使用 .mjs 表示 ESM 文件,使用 .cjs 表示 CJS 文件,反之亦然。tsup 会自动处理这一点。
.mjs 和 .cjs 扩展名
显式使用 .mjs(ESM)和 .cjs(CJS)文件扩展名,无论 type 字段如何设置,都能明确标记格式。这样可以避免歧义。当 format 为 ['esm', 'cjs'] 且未设置 type 字段时,tsup 可以输出:index.js(ESM)和 index.cjs(CJS),这符合最常见的约定。
双格式包陷阱
当一个包同时提供 ESM 和 CJS 格式时,使用者的打包工具可能会在同一进程中加载两个版本——例如主应用加载 ESM 版本,而 Jest 测试加载 CJS 版本。如果包包含模块级状态(例如 React 上下文),两个实例会分别拥有独立状态。这就是双格式包陷阱。
缓解双格式包陷阱
要缓解这一陷阱:不要在库中保存模块级状态(不使用单例模式);精确使用 exports 条件,确保只加载一种格式;并说明测试应配置其打包工具使用 ESM。这个陷阱主要影响包含共享单例的库。
测试双格式输出
构建后请验证两种格式都能正常工作。测试 CJS:node -e "const lib = require('./dist/cjs/index.js'); console.log(lib)"。测试 ESM:node --input-type=module --eval "import { Component } from './dist/esm/index.js'; console.log(Component)"。发布前,两种格式都应能正常解析且不产生错误。
多个入口点的 exports
exports 字段支持多个入口点:{ '.': { import: './dist/esm/index.js', require: './dist/cjs/index.js' }, './utils': { import: './dist/esm/utils.js', require: './dist/cjs/utils.js' } }。这样,使用者可以从 'your-lib' 或 'your-lib/utils' 导入,并获得正确的格式。
package.json 的 exports 字段
库的 package.json 中的 exports 字段主要有什么作用?
课程回顾:双格式包输出
ESM 使用 import/export,并支持摇树优化。CJS 使用 require() 以兼容 Node.js。通过 package.json 的 exports 字段,并配合 import/require 条件,同时发布两种格式。旧版回退方式:main(CJS)和 module(ESM)。使用 .mjs/.cjs 扩展名或 type: module 明确标记格式。构建后使用 Node CLI 测试两种格式。请注意包含单例状态时的双格式包陷阱。
常见问题解答
「ESM 与 CJS 双格式包输出」课时是免费的吗?
是的 — 「ESM 与 CJS 双格式包输出」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 React Academy 课程的其余内容,请升级到 CoddyKit PRO。 React Academy 课程共包含 4 节课。
「ESM 与 CJS 双格式包输出」这节课中我会学到什么?
生成 ES 模块和 CommonJS 构建版本,并正确配置 package.json 的 exports 字段 你通过在浏览器中直接运行的动手代码来练习 React Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 React Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 React Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。
「ESM 与 CJS 双格式包输出」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 React Academy 课中编写并运行代码吗?
能。每节 React Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- 使用 Rollup 和 tsup 为库打包
- ESM 与 CJS 双格式包输出
- 对等依赖与树摇
- 发布到 npm 与语义化版本控制