打包umd_cjs, umd, esm or iife?

本文探讨2020年后的JavaScript库开发,解析CJS、AMD、UMD、IIFE和ESM模块的区别与应用场景,并给出在不同环境下的最佳实践建议,包括如何利用rollup和esbuild进行打包优化。

d0b2e757040cd763340a0481790df8e4.png

2020 年了,nodejs 和浏览器基本都支持了原生 esm,那么现在 js 库该怎么写?本文先解释他们分别是什么,再结合最新环境支持给出建议和实践。

他们分别是什么?

你可以用 rollup 的在线 repl 来查看各种模块写法

它们是在 JS 里用来实现“模块”的不同规则。

CJS

CommonJS,只能在 NodeJS 上运行,使用 require("module") 读取并加载模块。

缺点:不支持浏览器,执行后才能拿到依赖信息,由于用户可以动态 require(例如 react 根据开发和生产环境导出不同代码 的写法),无法做到提前分析依赖以及 Tree-Shaking 。

AMD

Asynchronous Module Definition,可以看作 CJS 的异步版本,制定了一套规则使模块可以被异步 require 进来并在回调函数里继续使用,然后 require.js 等前端库也可以利用这个规则加载代码了,目前已经是时代的眼泪了。

UMD

Universal Module Definition,同时兼容 CJS 和 AMD,并且支持直接在前端用 <script src="lib.umd.js"></script> 的方式加载。现在还在广泛使用,不过可以想象 ESM 和 IIFE 逐渐代替它。

IIFE

Immediately Invoked Function Expression,只是一种写法,可以隐藏一些局部变量,前端人要是不懂这个可能学的是假前端。可以用来代替 UMD 作为纯粹给前端使用的写法。

ESM

ECMAScript Module,现在使用的模块方案,使用 import export 来管理依赖。由于它们只能写在所有表达式外面,所以打包器可以轻易做到分析依赖以及 Tree-Shaking。当然他也支持动态加载(import())。

浏览器直接通过 <script type="module"> 即可使用该写法。NodeJS 可以通过使用 mjs 后缀或者在 package.json 添加 "type": "module" 来使用,注意他还有一些 实验性的功能 没有正式开启。考虑到大量 cjs 库没有支持,如果要发布 esm 版的库还是通过 rollup 打包一下比较好(同时相关依赖可以放到 devDependencies 里)。

其他

SystemJS

库该怎么写?

其实很简单,要看你需要支持哪些平台:

只支持 NodeJS 的 require 写法

package.json:"main": "index.js"

其中 index.js 使用 cjs 写法(module.exports = xxx;

只支持 NodeJS 的 import 写法

package.json:"main": "index.mjs""type": "module", "main": "index.js"

其中 index.mjs 或 index.js 使用 esm 写法(export default xxx

同时支持 NodeJS 的 require 和 import 写法

利用 条件 export,直接看文档里面有例子。

支持浏览器直接通过 <script> 引入的写法

package.json:"browser": "index.global.js",然后 jsDelivr 等 cdn 会自动使用这个文件,具体到 cdn 上还有 "jsdelivr": "index.jsdelivr.js" 等配置写法,权重更高。

这里可以试试 esbuild 输出 iife 格式的包,比 webpack/babel 更快,除了对 cjs 的库不太友好(可以配合下面 rollup/commonjs 插件使用)。

浏览器直接支持 type="module" 引入 esm 写法的文件,但是这对于 cdn 来说并不友好:cdn 看到 import "xxx" 并不知道如何找到 xxx 模块,所以这种写法建议只在本地使用。另外也可以通过 vite,让它使用 rollup 和 esbuild 帮你引入这些外部模块。

支持现代打包器 rollup 和 webpack2+ 通过 import 引入的写法

package.json:"module": "index.esm.js"

如果某些库没有写这个选项,那么可以借助 rollup 的 commonjs 插件转译到可用,具体做法可以参考 rollup 文档。

如何实践?

完全使用 ESM,如果有不同环境的需求,通过 rollup 打包成支持目标环境的其他格式(esm 给其他打包器使用, iife 给浏览器, cjs 给 nodejs 玩家),可以参考 vue3。

**ESM** 是 **ECMAScript Module** 的缩写,它是 JavaScript 的官方标准模块系统,自 ES6(ECMAScript 2015)起被引入,用于在浏览器和现代 JavaScript 运行时中组织和管理代码模块。 --- ### ✅ 简单定义: > **ESM(ECMAScript Modules)** 是 JavaScript 内置的模块化规范,允许你使用 `import` 和 `export` 来拆分代码为多个文件(模块),实现复用、封装和依赖管理。 这与早期的 CommonJS(Node.js 使用的 `require()` 和 `module.exports`)不同,ESM 是语言层面的标准,更加现代化、静态可分析,并支持 tree-shaking(打包优化)。 --- ## 🔧 ESM 基本语法示例 ### 1. 导出模块(`export`) ```js // mathUtils.js export const add = (a, b) => a + b; export const multiply = (a, b) => a * b; // 默认导出(每个模块只能有一个) export default function greet(name) { return `Hello, ${name}!`; } ``` ### 2. 导入模块(`import`) ```js // main.js import greet, { add, multiply } from './mathUtils.js'; console.log(greet("Alice")); // Hello, Alice! console.log(add(2, 3)); // 5 console.log(multiply(4, 5)); // 20 ``` > ⚠️ 注意:文件扩展名 `.js` 在某些环境中是必需的(尤其是在 ESM 模式下的 Node.js)。 --- ## 🌍 ESM 的应用场景 | 环境 | 支持情况 | |------|----------| | **浏览器** | ✅ 原生支持(通过 `<script type="module">`) | | **Node.js** | ✅ 支持(需设置 `"type": "module"` 或使用 `.mjs` 扩展名) | | **前端构建工具(Vite、Webpack、Rollup)** | ✅ 默认推荐使用 ESM | | **现代框架(React、Vue、Svelte)** | ✅ 全面采用 ESM | ### 浏览器中使用 ESM 示例: ```html <script type="module"> import { add } from './mathUtils.js'; console.log(add(1, 2)); // 输出: 3 </script> ``` --- ## 🆚 ESM vs CommonJS(CJS) | 特性 | ESM(`import/export`) | CommonJS(`require/module.exports`) | |------|------------------------|-------------------------------------| | 标准来源 | ES6+(标准化) | Node.js 自创(非标准) | | 加载方式 | 静态解析(编译时) | 动态加载(运行时) | | 是否支持 Tree Shaking | ✅ 是 | ❌ 否 | | 浏览器原生支持 | ✅ 是 | ❌ 否 | | 文件扩展名要求 | 有时需要 `.js` | 不需要 | | 循环引用处理 | 更安全 | 易出错 | | Node.js 中启用方式 | `"type": "module"` in `package.json` | 默认模式 | ### 示例对比: #### ✅ ESM: ```js // utils.mjs 或 package.json 中 type: "module" export const PI = 3.14; ``` ```js import { PI } from './utils.js'; ``` #### 🟡 CommonJS: ```js // utils.js exports.PI = 3.14; ``` ```js const { PI } = require('./utils'); ``` --- ## ⚙️ 在 Node.js 中启用 ESM 要让 Node.js 使用 ESM,有两种方法: ### 方法 1:在 `package.json` 中声明 ```json { "name": "my-app", "type": "module", "scripts": { "start": "node index.js" } } ``` 然后就可以使用 `.js` 文件中的 `import/export`。 ### 方法 2:使用 `.mjs` 扩展名 即使没有 `"type": "module"`,也可以将文件命名为 `index.mjs`,Node.js 会自动按 ESM 解析。 --- ## 💡 常见问题与注意事项 1. **为什么导入时必须写 `.js` 而不是 `.ts/.jsx`?** 因为浏览器或 Node.js 实际加载的是编译后的 `.js` 文件,路径必须指向真实存在的资源。 2. **ESM 是异步的吗?** 是的,在浏览器中,`<script type="module">` 是延迟执行的,不会阻塞 HTML 解析,且自动以 `defer` 方式加载。 3. **可以动态导入吗?** 可以!使用 `import()` 函数(返回 Promise): ```js const module = await import('./mathUtils.js'); console.log(module.add(2, 3)); ``` 4. **如何兼容旧环境?** 使用打包工具如 **Vite / Webpack / Rollup / Babel** 将 ESM 编译为 UMD/CJS/IIFE 格式。 --- ## 🛠️ 实际用途举例 - **前端框架**(React/Vue)组件按需加载 - **库开发**:发布支持 ESM 的 NPM 包(通过 `exports` 字段) - **Tree-shaking**:只打包用到的代码(如 Lodash 按需引入) - **微前端架构**:通过 ESM 动态加载远程模块 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值