ESM、CJS、UMD、IIFE、MF 产物矩阵
为什么要逐个看 format
format 不是输出文件后缀的别名。它决定一组互相关联的产物语义:
- Rspack parser 如何理解模块。
- Rspack output 是否启用 module。
- library type 是什么。
- chunk loading 用
import、require还是关闭。 - externalsType 如何生成外部依赖引用。
- splitChunks 和 runtimeChunk 是否允许。
- target 默认是 node 还是 web。
- 是否能 bundleless。
- 是否适合 dts auto extension。
如果只把 format 理解成 esm -> .mjs、cjs -> .cjs,维护时很容易改坏运行时语义。
总览表
ESM
ESM 是默认 format。Rslib 为 ESM 设置:
output.module = truelibrary.type = "modern-module"chunkLoading = "import"workerChunkLoading = "import"chunkFormat = falseavoidEntryIife = truesplitChunks.chunks = "async"runtimeChunk根据 bundle 和 entry 数量决定externalsType = "module-import"
为什么 ESM external 特别敏感
ESM 产物的 external 不是简单字符串替换。module-import 会生成 ESM import 形态。若源码中从 CommonJS issuer 发起 request,可能出现语义不符合预期的 external。
因此 Rslib 有 composeExternalsWarnConfig,专门在 ESM 下探测 externalized commonjs request 并警告用户显式指定 external type。
ESM 和 autoExtension
ESM 在 Node 中对扩展名很敏感。Node ESM loader 不做扩展名搜索:
不会自动找 Button.js。所以 bundleless ESM 通常需要把输出 import 改成:
如果 package type 或用户 filename 让 ESM 输出变成 .mjs,内部 import 也要跟着变。
ESM 的维护风险
- 不要把 CJS external type 用到 ESM。
- 不要生成无扩展名相对 import。
- 不要让 runtime chunk 名在多 environment 下冲突。
- 不要让 CJS shim 默认进入 ESM,除非用户显式开启。
CJS
CJS 产物设置:
output.module = falselibrary.type = "commonjs-static"chunkLoading = "require"workerChunkLoading = "async-node"chunkFormat = "commonjs"splitChunks.chunks = "async"externalsType = "commonjs-import"
为什么需要 commonjs-static
库产物希望保留比较静态的导出结构,便于下游工具和 Node 解析。commonjs-static 比动态挂载 exports 更适合库输出。
CJS 和 import.meta.*
很多现代源码会写:
或使用 import.meta.dirname、import.meta.filename。CJS 没有这些语义。Rslib 默认在 CJS 下启用对应 shims,避免源码从 ESM 转 CJS 后直接坏掉。
CJS 的维护风险
- shim 注入必须尊重 shebang 和
"use strict"。 requirechunk loading 不应混入 ESM import chunk。.cjs/.js扩展名要和 package type 对齐。- external helper、Node builtins、target node 的组合要保持可运行。
UMD
UMD 是最终 bundle 格式,不支持 bundleless。Rslib 会在 bundle: false 时直接报错。
UMD 设置:
library.type = "umd"- 可选
library.name = umdName asyncChunks = falsesplitChunks = falsenodeEnv = process.env.NODE_ENVexternalsType = "umd"
为什么 UMD 必须 bundle
UMD 的目标是一个可直接通过 script、AMD 或 CJS 使用的 bundle。它不适合保留多文件源码结构。如果 UMD bundleless,产物会同时要求 UMD wrapper 和相对模块 import,这两种模型冲突。
UMD 的维护风险
- external 映射必须适合 UMD 消费方式。
- 多入口可能产生多个 UMD wrapper,要确认用户预期。
- 不要打开 splitChunks,否则不是单一分发 bundle。
umdName对浏览器 global 消费非常关键。
IIFE
IIFE 是浏览器直接执行的 bundle,也不支持 bundleless。
设置包括:
output.iife = truelibrary.type = "module"asyncChunks = falsesplitChunks = falsechunkFormat = falsechunkLoading = "import"globalObject = "globalThis"externalsType = "global"
为什么 IIFE external 用 global
如果 external type 用 var,像 @scope/pkg 这种包名会生成非法变量引用。如果用 umd,环境中 define 等变量可能影响判断。Rslib 对 IIFE 使用 global,更适合浏览器和多环境下的 global lookup。
IIFE 的维护风险
- 不能产生 async chunks。
- external 名称要能在 globalThis 上找到。
- 不要混入 Node target 假设。
- minify 和 annotations 需要保证库入口仍可用。
MF
MF format 是最特殊的。它更像应用 remote,不是普通 library format。
设置包括:
- 必须
bundle: true - 默认 target 为 web
dev.writeToDisk = true- output
uniqueName = pkgJson.name nodeEnv在 development/production 中设置具体字符串- 必须有 Module Federation 插件
- 不使用普通 RslibPlugin 的所有 library 假设
为什么 MF 不是普通 library
Module Federation 需要 runtime 来加载 remote、shared modules 和 chunks。它的输出语义不是“把库发布到 npm 后被 import”,而是“运行时通过 federation 协议加载 remote”。
因此很多 library 默认值不能直接套到 MF:
nodeEnv = false不适合 shared module 构建。- target 通常是 web。
- dev server 有意义。
- remoteEntry 需要写盘。
- minify 默认更积极,因为网络资源可能不再由项目侧压缩。
MF 的维护风险
- 不要把 MF 当 ESM/CJS 的一个变体。
- 不要让 Node target 默认渗入 MF。
- 不要移除 MF plugin 检查。
- 不要把 ordinary library 的 runtime chunk 策略直接套给 MF。
format 与测试
修改 format 行为时,至少检查:
tests/integration/formattests/integration/umdtests/integration/iifetests/integration/externalstests/integration/auto-externaltests/e2e/module-federationtests/e2e/react-component中 UMD 消费
如果改动影响 bundleless,还要加 bundle-false 和 redirect。
修改 format 的审查问题
- 是否改变外部依赖生成方式?
- 是否改变 chunk loading?
- 是否改变 output.module?
- 是否改变 target 默认值?
- 是否改变 autoExternal 默认值?
- 是否影响 dts extension?
- 是否影响 CSS/asset redirect?
- 是否影响
--lib多 environment 构建?
format 是 Rslib 的主轴之一。任何 format 行为变化都应被视为高风险改动。