前端模块化
CommonJS
- 使用
require
,export
进行导入导出 CommonJS
是Node
的模块化方案,只能在Node
端运行,不能在浏览器端,除非使用一些构建工具进行编译(Webpack
、Rollup
)- 特定的第三方库只支持
CommonJS
,比如下载量很高的ms CommonJS
属于动态加载,可以直接require
一个变量:require(`./${a}`);
1
2
3
4// sum.js
exports.sum = (x, y) => x + y;
// index.js
const { sum } = require("./sum.js");
ESModule
Esmodule
是tc39对于ESMAScript
的模块化规范,正因是语言层规范,因此在 Node 及 浏览器中均支持。1
2
3
4// sum.js
export const sum = (x, y) => x + y;
// index.js
import { sum } from "./sum";- 使用
import
,export
进行模块的导入导出 Esmodule
为静态导入,正因如此,可在编译期进行Tree Shaking
,减少 js 体积。- 如果需要动态导入,tc39 为动态加载模块定义了 API:
import(module)
。 cjs
模块输出的是一个值的拷贝,esm
输出的是值的引用- Node环境下的
cjs
模块是运行时加载,Webpack环境下的esm
模块化是编译时加载
本质上是多了个文件IO
UMD
一种兼容cjs
与amd
的模块化方案,既可以在node/webpack
环境中被require
引用,也可以在浏览器中直接用 CDN 被script.src
引入。
1 | (function (root, factory) { |
总结
特性 | ESM(ES6 模块) | UMD(通用模块定义) | CJS(CommonJS) |
---|---|---|---|
语法 | import/export | 自执行函数,兼容多种环境 | require/exports/module.exports |
兼容性 | 现代浏览器、Node.js(需配置) | 所有环境(浏览器、Node.js) | Node.js(默认) |
加载时机 | 编译时静态分析(静态结构) | 运行时动态加载 | 运行时动态加载 |
依赖解析 | 编译阶段确定依赖关系 | 运行时判断环境并选择加载方式 | 运行时执行 require 语句 |
Tree Shaking | 支持 ✅(静态结构可分析) | ❌(动态加载) | ❌(动态加载) |
典型应用场景 | 现代前端项目(Webpack/Vite) | 通用库(需兼容所有环境) | Node.js 后端项目 |
作用域 | 每个模块有独立作用域 | 封装在 IIFE 中,避免全局污染 | 每个文件是独立模块 |
导出机制 | 实时绑定(引用共享) | 对象拷贝 | 对象拷贝 |
常见问题
1、第三方npm包基于commonjs开发,使用到webpack的前端项目中,能否通过esmodule语法import引入
可以通过import
引入CommonJS
模块,Webpack
会自动处理兼容性,比如引入下载量很高的ms
- 直接导入:在Webpack环境下可直接使用
import ms from 'ms'
导入,Webpack会自动处理 - 要注意的是
Tree Shaking
限制:Webpack
只会对Esmodule
模块进行TreeShaking
。
2、如何判断引入的第三方包是否支持 Tree Shaking
- 只有
ESM
支持Tree Shaking
- 要根据第三方依赖的产物来判断(是否打成
ESM
的格式),而不是根据第三方依赖的源码判断
3、CJS的运行时加载和ESM的编译时加载有什么区别
前端Webpack环境编译时加载模块的执行过程
源代码(含import语句) → 打包工具(如Webpack) → 分析import依赖 → 生成合并后的代码 → 浏览器直接执行
关键步骤:- 静态分析:Webpack 在打包时扫描所有 import 语句,构建依赖图。
- 代码合并:将所有依赖的模块打包到一个或多个文件中(如 bundle.js)。
- 运行时执行:浏览器直接执行打包后的代码,无需再加载其他文件(依赖已包含在主文件中)。
Node环境运行时加载模块的执行过程
代码执行 → 遇到require()语句 → 查找模块文件 → 读取文件内容 → 执行模块代码 → 返回导出值
关键步骤:- 执行到 require():代码运行到该语句时,触发模块加载逻辑。
- 路径解析:根据 require() 的参数(如 ./math.js),查找对应的文件。
- 文件读取与执行:读取模块文件内容,创建新的模块上下文并执行代码。
- 缓存结果:将模块的 exports 对象存入缓存,下次引用时直接返回缓存。
4、成熟的第三方npm包是怎么去做构建的(以Lodash为例)
Lodash 的源码采用定义全局对象,「无模块化」设计,核心是通过构建工具将函数集合打包为多种格式:
源码组织:所有工具函数(如 map、filter)定义在全局对象上。
构建工具:使用定制的构建系统,将源码转换为:
CJS 格式:lodash/index.js
ESM 格式:lodash-es/index.js
UMD 格式:dist/lodash.js(可通过 CDN 引入)
5、如何把开发的NPM包打成不同的格式(ESM,CJS,UMD)
详见:打包示例
js代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14export function capitalize(str) {
if (!str) return '';
return str.charAt(0).toUpperCase() + str.slice(1);
}
export function truncate(str, maxLength = 10, suffix = '...') {
if (str.length <= maxLength) return str;
return str.slice(0, maxLength) + suffix;
}
export default {
capitalize,
truncate
};
webpack配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
// 公共配置
const commonConfig = {
mode: 'production',
entry: './src/index.js',
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
// use: {
// loader: 'babel-loader',
// options: {
// presets: ['@babel/preset-env']
// }
// }
}
]
},
plugins: [
// new CleanWebpackPlugin() // 清理dist目录
],
resolve: {
extensions: ['.js']
}
};
// ESM 配置
const esmConfig = {
...commonConfig,
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'string-utils.esm.js',
library: {
type: 'module' // 输出ESM格式
}
},
experiments: {
outputModule: true // 启用ESM输出
},
optimization: {
minimize: false // 开发环境不压缩
}
};
// CJS 配置
const cjsConfig = {
...commonConfig,
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'string-utils.cjs.js',
library: {
type: 'commonjs2' // 输出CJS格式
}
},
target: 'node', // 针对Node.js环境
optimization: {
minimize: false
}
};
// UMD 配置(开发环境)
const umdDevConfig = {
...commonConfig,
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'string-utils.umd.js',
library: {
name: 'StringUtils', // 全局变量名
type: 'umd' // 输出UMD格式
},
globalObject: 'this' // 兼容浏览器和Node.js
},
optimization: {
minimize: false
}
};
module.exports = [esmConfig, cjsConfig, umdDevConfig, umdProdConfig];