随着前端工程复杂度不断提升,模块化成为项目结构设计中的基础能力。模块化不仅提升了代码的复用性、可维护性,也奠定了现代构建工具(如 Webpack、Vite)的技术基石。
本篇文章将系统梳理前端主流模块化规范,包括 ESM(ES Modules)、CommonJS、AMD,以及它们的运行机制、对比差异、适用场景及在项目中的最佳实践。
模块系统 | 适用场景 | 加载方式 | 是否同步 | 是否运行时可变 |
---|---|---|---|---|
ESM(ES Modules) | 浏览器、现代构建工具 | 静态导入 | 异步 | 否 |
CommonJS | Node.js | 动态 require |
同步 | 是 |
AMD | 浏览器异步加载 | define |
异步 | 否 |
UMD | 兼容浏览器 + Node.js | 同时支持 | 同步/异步 | 是 |
ESM 是 ECMAScript 官方标准模块系统:
// utils/math.js
export function add(a, b) {
return a + b;
}
// main.js
import { add } from './utils/math.js';
console.log(add(1, 2));
特点:
.js
)工程化优点:
CommonJS 是 Node.js 的标准模块系统,支持同步加载模块:
// math.js
module.exports = {
add: (a, b) => a + b
};
// main.js
const { add } = require('./math');
console.log(add(2, 3));
特点:
问题:
AMD 是为了解决浏览器端异步模块加载而生的模块系统:
define(['math'], function(math) {
console.log(math.add(1, 2));
});
特点:
现状:
UMD 是为了解决兼容多种模块系统而提出的规范:
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define(factory); // AMD
} else if (typeof module === 'object' && module.exports) {
module.exports = factory(); // CommonJS
} else {
root.myModule = factory(); // 全局变量
}
}(this, function () {
return {
sayHello: () => console.log('Hello')
};
}));
适用场景:
由于两种模块系统的加载机制不同,存在诸多不兼容问题。
Node.js 中不能直接通过 require()
导入 .mjs
模块,需使用 import()
动态导入。
// ESM 文件
import * as fs from 'fs'; // 成功:Node.js 提供 CommonJS 接口封装
.mjs
+ "type": "module"
全面使用 ESM现代工具如 Webpack、Vite 会自动处理不同模块:
配置建议:
// package.json
{
"type": "module", // 启用 ESM 支持
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
}
}
}
使用场景 | 推荐模块系统 |
---|---|
浏览器项目(现代构建工具) | ESM |
Node.js >= 14 项目 | ESM |
需要兼容旧工具链或发布 npm 包 | UMD + CommonJS |
不使用构建工具的原始 HTML 项目 | AMD / 原生 ESM |
微前端架构系统 | ESM(便于模块独立加载) |
浏览器支持 来加载模块,该机制具备以下特性:
'use strict'
)<script type="module">
import { initApp } from './app.js';
initApp();
script>
浏览器会自动递归解析 import
,构建 模块依赖图,每个模块仅初始化一次。
require()
是同步读取文件// A.js
console.log('A模块被执行');
module.exports = { name: 'module A' };
// B.js
require('./A'); // 控制台打印 'A模块被执行'
require('./A'); // 不再打印,走缓存
import
是异步加载require()
时执行,执行结果缓存在 require.cache
副作用是指导入模块时立即执行的代码,如注册全局变量、改写原型、操作 DOM。
建议:
sideEffects: false
提示构建工具可清除无用代码(适用于 Tree-shaking)// package.json
{
"sideEffects": false
}
在微前端架构中,模块化的意义更为突出:
SystemJS
可加载格式或 UMD 模块import()
或 System.import()
动态加载远程模块libraryTarget: 'umd'
或 system
externals
避免重复打包 React/Vue// 子应用暴露接口
export function mount(container) {
// 渲染逻辑
}
export function unmount() {
// 卸载逻辑
}
Tree-shaking 是构建工具中用于删除未使用代码的优化技术。
import
,构建模块依赖图// util.js
export function used() { }
export function unused() { } // 将被剔除
// index.js
import { used } from './util';
import.meta.url
定位当前模块路径--trace-module
启动参数观察模块加载过程module.createRequire()
模拟 CommonJS require()
行为.cjs
与 .mjs
文件技术选型场景 | 推荐模块体系 |
---|---|
现代前端项目(React/Vue) | ESM |
微前端架构 | ESM + SystemJS/UMD |
后端 Node 项目 | 纯 ESM(推荐)或 CommonJS(兼容旧项目) |
npm 组件开发 | UMD + ESM + CJS 打包 |
统一模块系统 = 提升团队协作效率 + 提高打包性能 + 减少模块混乱问题。