import() 本身非 tree shaking 直接手段,但与代码分割协同可实现分支级深度摇树:为动态导入生成独立 chunk 及依赖图,使每个异步模块拥有独立摇树域,并需配合精细化 sideeffects 配置与纯 esm 实践。

按需加载 import() 本身不是 Tree Shaking 的直接手段,但它和代码分割(Code Splitting)协同工作后,能从**执行路径层面**大幅提升摇树效果——本质是让 Tree Shaking 不再只作用于“静态导入的模块集合”,而是作用于“实际运行时才会进入的分支模块子图”。这种配合实现了真正意义上的分支级深度摇树优化。
动态导入创造独立的摇树上下文
Webpack/Vite 对 import('./module.js') 会生成一个全新的 chunk,并为其构建独立的依赖图。这个 chunk 内部的 ES 模块仍支持完整 Tree Shaking:未被该 chunk 内任何代码引用的导出,会被彻底移除,哪怕它在主包里被其他路径间接依赖过。这意味着每个异步加载的模块都拥有自己的“摇树域”。
- 路由组件懒加载是最典型场景:用户没访问 /admin,
Admin.vue及其所有依赖(包括它内部import { useUser } from '@/composables')都不会参与主包摇树,而是在单独 chunk 中二次摇树 - 条件性功能模块:比如仅在 IE11 环境下才
import('./polyfill-corejs'),该 polyfill 的全部副作用和未用导出,在现代浏览器构建中完全不存在 - 插件式架构:第三方插件通过
import(pluginPath)加载,其内部未使用的工具函数、冗余 UI 组件,在该插件 chunk 内被精准剔除
配合命名导出 + 按需引入库,触发两级摇树
单纯 import() 只解决模块粒度;要深入到函数/组件级,需叠加库本身的模块化设计:
- 使用支持 ESM + 细粒度导出的库(如 lodash-es、date-fns、VueUse),避免
import { debounce } from 'lodash'这类全量引入 - 写成
import('lodash-es/debounce').then(({ default: debounce }) => {...}),此时 Webpack 不仅把 debounce 单独拆包,还会在该 chunk 内摇掉 lodash-es 其他所有未被引用的导出 - 更进一步:结合 babel 插件(如
babel-plugin-import或unplugin-auto-import),将import { ElButton } from 'element-plus'自动转为import ElButton from 'element-plus/es/components/button',再由import()加载,实现组件级隔离与摇树
sideEffects 配置必须按 chunk 精细化声明
Tree Shaking 安全移除代码的前提是确认无副作用。而副作用往往只存在于特定模块中(比如某个 polyfill 会改写全局 Promise):
- 主包
package.json设"sideEffects": false是基础,但对异步加载的第三方库不生效 - 应要求或手动为每个动态加载的库配置
sideEffects字段(例如date-fns/package.json已设为false,可放心摇) - 自定义模块中,若某文件含副作用(如
initAnalytics()),需显式标记/*#__PURE__*/或在sideEffects数组中列出该文件路径,避免误删
避免破坏摇树链的常见陷阱
即使用了 import(),以下写法仍会让整个 chunk 失去摇树能力:
- 在动态模块内使用 CommonJS 导入(
require('./utils')),ESM 静态分析中断 - 通过字符串拼接动态构造模块路径:
import(`./locales/${lang}.js`),Webpack 无法静态分析,会打包所有匹配文件 - 模块内存在非纯副作用:如直接执行
console.log、修改全局变量、调用localStorage.setItem,且未用/*#__PURE__*/标记 - 使用
export * from './xxx'全量 re-export,阻断对下游模块的精确引用分析











