规范现状:CommonJS 与 ESM
本节目标
- 分清 CommonJS(CJS)和 ES Module(ESM)的适用场景
- 理解「CJS 是运行时加载、ESM 是编译期静态」
- 知道 Node 和浏览器各自怎么支持
// CommonJS(Node 默认,运行时加载,语法是 require/exports)
const fs = require('fs')
module.exports = function () {}
// ES Module(浏览器 + Node 都支持,静态加载,语法是 import/export)
import fs from 'fs'
export default function () {}
// UMD:上面说过,全兼容,但已过时,新项目别用核心区别:
- CJS
require是运行时才执行、能写进if里动态决定;ESMimport是静态的,必须在顶层、编译期就确定依赖(便于打包器提前分析、Tree Shaking)。 - ESM 有「实时绑定(live binding)」,导入的值随导出方变化;CJS 导出的是值的快照拷贝。
现状:前端工程(Vite/Webpack)里你写 ESM,打包器把它转成浏览器能跑的;Node 后端默认还是 CJS,但加 "type":"module" 也能写 ESM。
名词解释
CommonJS(CJS):Node.js 采用的模块规范,用 require 引入、module.exports 导出。特点是「运行时加载」——代码执行到 require 才去加载模块,因此能写条件加载。它是服务端模块的王者,但静态分析能力弱。
ES Module(ESM):JS 语言标准的模块系统,用 import/export。特点是「静态可分析」——依赖在代码顶层写死、编译期可知,于是打包器能做 Tree Shaking、IDE 能精准跳转。它是浏览器和现代前端的统一标准。
课后练习
练习 1:为什么「if (x) require('a') 合法,但 if (x) import 'a' 不合法」?
答案:CJS 的
require是普通函数调用,运行时执行,当然能写进if;ESM 的import是静态语法,引擎要求在编译期就固定依赖图,不能出现在条件/函数里。这正是「静态 vs 运行时」的区别落地。
练习 2:Node 里如何让一个 .js 文件按 ESM 解析?
答案:两种:① 文件后缀用
.mjs;② 在package.json写"type": "module",则该目录下.js都按 ESM 解析。否则 Node 默认按 CJS 解析.js。
总结
我的观点:CJS 和 ESM 不是「谁取代谁」的敌对关系,而是「各自主场」。服务端脚本、CLI 工具用 CJS 很顺手(动态 require 灵活);前端工程、库源码用 ESM(能被打包器优化、写法统一)。关键是别混用出 default 陷阱(见后面章节)。新项目我一律推荐 ESM 作为源码格式,哪怕最终跑在 Node 上——它的静态特性让工具链更聪明,长期收益明显。记住区分一句话:CJS 运行时、ESM 编译期。