规范现状:CommonJS 与 ESM

本节目标

// CommonJS(Node 默认,运行时加载,语法是 require/exports)
const fs = require('fs')
module.exports = function () {}

// ES Module(浏览器 + Node 都支持,静态加载,语法是 import/export)
import fs from 'fs'
export default function () {}

// UMD:上面说过,全兼容,但已过时,新项目别用

核心区别:

现状:前端工程(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 编译期。