Tree Shaking 与动态 import 分包
本节目标
- 理解 Tree Shaking 如何「删掉没用到的导出」
- 会用「动态 import」做代码分割(按需加载)
- 知道什么写法会「抖不掉」(副作用)
Tree Shaking:打包器分析「哪些导出真的被 import 了」,没用到的整段删除,缩小体积。
// 运行环境:Node.js 16+(演示 import 拆分,实际 Tree Shaking 看打包结果)
// utils.mjs
export const used = () => '用了'
export const unused = () => '没用' // 如果没人 import unused,打包会被删掉
// 导入时只取 used,unused 会被 Tree Shaking 移除(前提是 ESM 静态、无副作用)
import { used } from './utils.mjs'
console.log(used())
// 动态 import:按需加载,生成单独 chunk(分包)
// 适合「点击后才需要的重型模块」
async function onClick() {
const heavy = await import('./heavy.mjs') // 运行时才加载这个文件
heavy.run()
}会抖不掉的情况:模块顶层有「副作用」(如 console.log、改全局、import './css'),打包器不敢删,怕你本来就要它执行。用 /*#__PURE__*/ 标注纯函数可帮助摇树。
名词解释
Tree Shaking(摇树):打包时把「没被任何地方 import 的导出代码」像摇树掉枯叶一样删掉,减小产物体积。前提是 ESM 静态分析(能确定谁用到谁)。它是前端性能优化的大头——一个库导出 100 个函数你只用一个,摇完可能只剩那一个。
动态 import(Dynamic Import):import('./x.mjs') 写在函数里、返回 Promise,运行时才加载该模块。它让「不常用的模块」不进首屏包,而是「用到时再下载」,即「代码分割 / 按需加载」。和静态 import 不同,它是异步、可条件触发的。
课后练习
练习 1:为什么 Tree Shaking 要求「ESM + 无副作用」?
答案:摇树靠「静态分析确定某导出无人用」然后删除;CJS 的
module.exports是运行时对象,无法静态判断;若模块顶层有副作用(如连库、打日志),删了会改变程序行为,打包器只好保留。所以「纯 ESM + 纯模块」才摇得干净。
练习 2:动态 import 和静态 import,在产物上有何不同?
答案:静态 import 的模块会被打进「主包」(首屏一起加载);动态 import 的模块会被单独切成一个 chunk,只有执行到那行(如用户点击)才下载。所以动态 import 是「首屏瘦身」的标准手段。
总结
Tree Shaking 和动态 import 是现代前端「体积小、首屏快」的两大支柱。我的观点:写库/写模块时,默认用 ESM 命名导出、保持模块纯净(顶层无副作用),就是给 Tree Shaking 铺路——你不经意写的 console.log 或顶层副作用,可能让整模块抖不掉。动态 import 则要用在「重且不一定用」的地方(图表库、富文本、二级页面),别滥用到首屏核心逻辑上(反而增加请求数)。一句话:摇树管「删没用的」,分包管「晚加载重的」,两者配合,性能才有保障。