AMD 和 ESModule 有什么区别?为什么 ESModule 能做 Tree-Shaking?
核心区别:AMD 是运行时加载,ESModule 是编译时静态分析。这个根本差异决定了 ESModule 能做 Tree-Shaking,AMD 做不了。
AMD 用 define(['dep1', 'dep2'], callback) 声明模块,依赖列表虽然写死了,但 callback 里的逻辑是运行时才执行的——你完全可以在回调里根据条件 require 不同的模块。ESModule 的 import/export 必须写在模块顶层,引擎在解析代码时(还没执行)就能确定整个依赖图。Tree-Shaking 就靠这个:Webpack/Rollup 静态扫描 import 语句,标记哪些导出被引用了,没被引用的直接从产物中删除。
实际效果很直观:import { debounce } from 'lodash-es',打包后只有 debounce 和它的依赖。换成 AMD 的 define(['lodash'], callback),整个 lodash 全量加载,因为工具无法判断 callback 里到底用了 lodash 的哪些方法。
追问
CommonJS 和 AMD 有什么区别?
CommonJS 是同步加载(require 阻塞执行),给 Node.js 设计的,文件都在本地所以同步没问题。AMD 是异步加载(define + 回调),给浏览器设计的,网络请求不能阻塞。两者都不能做 Tree-Shaking——require() 是运行时调用,静态分析搞不定。
ESModule 的静态结构除了 Tree-Shaking 还有什么用?
- Scope Hoisting:Webpack 把多个模块的变量合并到同一个作用域,减少闭包和函数调用开销
- 动态 import() 做代码分割:
import()虽然是动态的,但语法上是个特殊的表达式,构建工具能识别并单独分包 - 浏览器原生支持:
<script type="module">不需要打包就能跑,Vite 开发模式就靠这个实现秒级热更新
现在项目还需要关心 AMD 吗?
基本不用了。现代浏览器和 Node.js 都原生支持 ESModule,新项目直接用 ESM。AMD 只在维护老 RequireJS 项目时遇到。面试问这个,考的是你对模块化演进的理解——从全局变量 → IIFE → CommonJS/AMD → ESModule,每个阶段解决什么问题。
怎么验证 Tree-Shaking 是否生效?
用 Webpack 的 webpack-bundle-analyzer 或 Rollup 的可视化插件看产物体积。常见翻车场景:Babel 把 import 转成了 require(@babel/preset-env 的 modules 选项没关),或者 package.json 没配 "sideEffects": false,这两种情况 Tree-Shaking 都会失效。