javaScript模块化无唯一主流方案:浏览器端优先ES Module(需type=”module”和.js后缀),node.js v14+默认支持但需配置”type”:”module”或.mjs,CommonJS仍广泛用于工具链动态加载,混用时存在兼容限制。

javascript 模块化开发没有“唯一主流方案”,只有不同场景下更合适的选择:浏览器端优先用 ES Module(原生支持、静态分析强),Node.js 从 v14+ 起也默认支持 ES Module,但仍有大量旧项目依赖 CommonJS;构建工具链中 Rollup 偏爱 ESM 输出,webpack 则两者都兼容但默认处理 CommonJS 更成熟。
浏览器里直接用 import/export 就行,但要注意 .js 后缀和 type=”module”
现代浏览器(chrome 61+、firefox 60+、safari 11.1+)原生支持 ES Module,但必须满足两个硬性条件:
-
标签要加type="module"属性,否则会忽略import/export -
import路径必须是完整 URL 或带后缀的相对路径,比如import { foo } from './utils.js'—— 缺少.js会报404,不支持 Node 风格的自动解析 - 顶层
this是undefined,且模块作用域天然严格模式,无法用var声明全局变量
示例:
node.js 里用 ES Module 要注意 package.json 的 “type” 字段
Node.js 默认把 .js 文件当 CommonJS 处理。启用 ES Module 有两种方式:
立即学习“Java免费学习笔记(深入)”;
- 在
package.json中声明"type": "module",此后所有.js文件按 ESM 解析(包括require()会报错) - 或用
.mjs扩展名,无需改配置,Node 会强制按 ESM 加载 -
import路径不能省略扩展名(./utils不行,得写./utils.js或./utils.mjs),也不能用 Node 内置模块简写(import fs from 'fs'会失败,得用import * as fs from 'fs')
CommonJS 还没淘汰,尤其在 require() 动态加载和工具脚本里很常见
CommonJS(module.exports/require())仍是很多 Node 工具链的底层选择,比如 webpack.config.js、jest.config.js 默认是 CJS 模块,因为支持动态路径拼接和运行时条件判断:
const env = process.env.NODE_ENV; const config = require(`./webpack.${env}.js`); // ✅ CommonJS 支持
而 ES Module 的 import 必须是静态字符串,上面这种写法会报 Cannot use import statement outside a module 错误。另外,CJS 模块的 require.resolve() 和 require.cache 在插件开发中仍不可替代。
混用 ESM 和 CommonJS 时,__dirname、__filename 和 require 不可用
一旦用了 type="module" 或 .mjs,Node 就禁用 __dirname、__filename 和全局 require。想获取当前目录需改用:
import { fileURLToPath } from 'url';const __filename = fileURLToPath(import.meta.url);const __dirname = path.dirname(__filename);
想在 ESM 中加载 CJS 模块(如 lodash),可以用 import _ from 'lodash'(Node 自动适配),但反过来——在 CJS 里 require() 一个 ESM 文件——会直接报错:ERR_REQUIRE_ESM。这时只能靠构建工具(如 esbuild)提前转译,或改用 dynamic import()(返回 promise)。
真正容易被忽略的是:模块解析规则在不同环境间不一致——浏览器不认 node_modules 自动查找,Node 的 exports 字段又可能屏蔽 ESM 入口,而 Webpack/vite 的别名配置又各自为政。选方案前,先确认你的运行时环境、依赖包的导出方式(看它的 package.json 中 exports 或 main/module 字段),比盲目套教程更重要。