Prompts-MCP 686 skills · ai / design / design-pattern / framework / fundamentals / habit / lang / tech-selection /mcp/sse
fundamentals fundamentals/frontend/module-systems.md low

fundamentals-frontend-module-systems

模块化体系 CJS/ESM/UMD 的工程选型与互操作 — 新代码用 ESM,CJS 仅遗留 Node 工具,UMD 仅浏览器 script 库。Use when 选模块格式 / CJS 与 ESM 互操作报错 / 配 package.json type 与 exports 时。

esmcommonjsumd模块化importexports 字段esmoduleinteroprequire is not defined
paths
  • frontend/src/**/*.ts
  • frontend/src/**/*.tsx
  • frontend/src/**/*.vue

Fundamentals · 模块化体系 CJS/ESM/UMD 选型

决策点:写新代码、引依赖、发布库时该选哪种模块格式,以及混用时的互操作坑。 一句话:新代码一律 ESM;CJS 仅遗留与 Node 传统工具;UMD 仅给浏览器 <script> 标签的发布库。

规则

场景 为什么
前端业务新代码 ESM import/export 静态可分析→tree-shake,浏览器/打包器原生
库要被打包器消费 ESM(保留 import 让下游 tree-shake,体积最小
Node CLI / 旧脚手架 / 老 config CJS require/module.exports 动态加载、Node 传统生态
发布库给浏览器 <script src> 直接用 UMD 同时兼容 AMD/CJS/全局变量
同时服务打包器与裸 <script> ESM + UMD 双产物 exports 走 ESM,<script> 走 UMD

要点:

  • ESM 是静态的(顶层 import 在解析期确定),所以能 tree-shake、能并行加载;动态用 import()
  • CJS 是运行时的(require 是函数调用,可放在条件里),无法静态 tree-shake。
  • UMD 是一段自适应包装,检测环境后挂到 AMD/CJS/window——仅当目标是裸浏览器引入时才需要。
  • 纯 ESM 写法约束见 ../../lang/typescript/module/esm-only.md

反例 · 正例

// ❌ 新前端代码用 CommonJS:打包器无法 tree-shake,且 Vite 下报 "require is not defined"
const { fetchArticles } = require("@/api/articles");
module.exports = { listView };

// ✅ ESM:静态导入,可摇树
import { fetchArticles } from "@/api/articles";
export { listView };

CJS↔ESM 互操作的 default 差异(最常见坑):

// CJS 模块 module.exports = fn 在 ESM 里默认整体是 default
// ✅ esModuleInterop 开启后
import dayjs from "dayjs";          // CJS 默认导出
import { Buffer } from "node:buffer"; // Node 内置具名

// ❌ 关掉 interop / 误用:拿到的是带 __esModule 标记的命名空间对象
import * as dayjs from "dayjs";
dayjs();                            // TypeError: dayjs is not a function

package.json 字段决定整包模块系统与入口解析:

{
  "type": "module",                 // .js 文件按 ESM 解析不写则按 CJS
  "exports": {                      // 条件导出优先级高于 main/module
    ".": {
      "import": "./dist/index.mjs", // 打包器/ESM 走这里
      "require": "./dist/index.cjs" // require() 走这里
    }
  }
}
// 坑:type:module 下写 require → "require is not defined in ES module scope"
// 坑:CJS 文件里写顶层 import → "Cannot use import statement outside a module"
// 解法:保持单一格式;要混用就靠扩展名 .mjs(ESM) / .cjs(CJS) 或 exports 条件分流

构建产物格式与双包配置见 ./build-tooling.md

自检

  • [ ] 新代码全用 import/export,无 require/module.exports
  • [ ] 引 CJS 库用默认导入而非 import * asesModuleInterop: true)?
  • [ ] package.jsontype 与文件扩展名/写法一致,无 import/require 混用报错?
  • [ ] 发布库:打包器入口走 exports.import,裸 <script> 才出 UMD?
  • [ ] 用 UMD 仅因目标是浏览器直接引入,而非默认选项?

相关