人人都会AI编程

Tree Shaking、代码分割、打包优化

更新时间:2026-07-11

无论使用 Webpack 还是 Vite,打包工具的最终目标都是:产出一个尽可能小、加载尽可能快的生产包。这需要开发者理解并善用三个核心优化手段:Tree Shaking(摇树优化)、代码分割(Code Splitting)与各类打包体积优化策略。本节以 Webpack 为主视角,同时标注 Vite 中的对应方案,因为原理相通。

Tree Shaking:剔除“你写了但没用到的代码”

Tree Shaking 依赖于 ES Modules 的静态导入导出特性。在编译阶段,打包工具可以分析出哪些导出的函数、变量、组件并未被任何模块引用,从而安全地删除它们。最终构建产物里不会包含任何“死代码”。

在 Webpack 中启用 Tree Shaking:

  1. 使用 ES Modules:确保项目代码使用 import/export 语法,而非 CommonJS 的 require/module.exports
  2. 生产模式自动开启mode: 'production' 会默认启用 TerserPlugin 进行代码压缩,同时 Webpack 会自动标记未使用的导出并将其移除。
  3. 配置 sideEffects:在 package.json 中声明哪些文件有副作用(如全局 CSS、polyfill),否则 Webpack 可能误删。
   {
     "sideEffects": ["*.css", "*.scss"]
   }
   

对于 Vue 单文件组件,vue-loader 会自动处理,无需额外标记。

实际效果

  • 引入 lodash 时,如果只用了 debounce,以前整个库都会打包。改用 lodash-es 并配合 Tree Shaking,只会保留 debounce 及其依赖的几个函数。
  • Vue 3 的模块设计就是 Tree Shaking 友好的:像 v-model<Transition> 这类可选功能,如果组件中没用到,最终包体并不会包含它们。

实用建议

  • 永远使用 import { xxx } from 'lib' 的方式引入工具函数,避免整体导入。
  • 组件库(如 Element Plus) 使用 unplugin-vue-components 实现按需自动导入,既能享受 Tree Shaking,也无需手动注册。
  • 检查第三方库是否提供 ES Module 版本,否则 Tree Shaking 无法生效。

代码分割:按需加载,而不是一次性全给

代码分割是将打包产物拆分成多个 chunk(代码块),让浏览器按需加载并行加载,避免首屏加载一个巨大的 bundle。

Webpack 代码分割的三种常用方式:

  1. 动态导入(Dynamic Import):使用 import() 语法,返回一个 Promise。Webpack 会自动将动态导入的模块拆分成独立的 chunk,并在需要时发起网络请求。这是实现路由懒加载的基础。
   // 路由配置中
   const UserProfile = () => import('./views/UserProfile.vue')
   

Vue Router 官方推荐在路由定义中使用这种写法,每个页面组件都会被拆成单独的 chunk。

  1. SplitChunksPlugin 配置:Webpack 内置的 optimization.splitChunks 可以提取公共依赖,避免多个 chunk 重复包含相同的库。
   // vue.config.js 或 webpack.config.js 中的典型配置
   module.exports = {
     configureWebpack: {
       optimization: {
         splitChunks: {
           chunks: 'all',
           cacheGroups: {
             vendor: {
               test: /[\\/]node_modules[\\/]/,
               name: 'vendors',
               chunks: 'all',
             },
             common: {
               minChunks: 2,
               priority: -20,
               reuseExistingChunk: true,
             },
           },
         },
       },
     },
   }
   

这样会产生三个核心文件:vendors.js(第三方依赖)、common.js(业务公共模块)以及各页面自己的 chunk。浏览器可以长期缓存 vendors.js,因为业务代码更新不会改动它。

  1. Magic Comments:在动态导入时可以添加注释来指定 chunk 名称或预加载策略。
   import(/* webpackChunkName: "my-component" */ './MyComponent.vue')
   

在 Vite 中:代码分割基于 Rollup,动态导入天然支持,无需额外配置。生产构建时 Rollup 会自动拆分 chunk,也可以手动配置 build.rollupOptions.output.manualChunks

打包优化:从配置到线上的全链路瘦身

除了 Tree Shaking 和代码分割,还有一系列“组合拳”能显著提升加载性能。

1. 压缩与混淆

  • JavaScript:Webpack 5 内置 TerserWebpackPlugin,生产模式自动开启。Vite 使用 esbuild 压缩,速度极快。
  • CSS:使用 css-minimizer-webpack-plugin(Webpack)或 Vite 的 css.minify 配置。
  • HTML:可借助 html-webpack-pluginminify 选项。

2. 静态资源处理

  • 图片/字体:Webpack 5 内置 Asset Modules,可配置小于 8KB 的资源转为 base64 内联,减少 HTTP 请求。超出限制的输出为文件并添加哈希以利于缓存。
  module: {
    rules: [{
      test: /\.(png|jpe?g|gif|svg)$/i,
      type: 'asset',
      parser: { dataUrlCondition: { maxSize: 8 * 1024 } },
      generator: { filename: 'images/[name].[hash:8][ext]' },
    }],
  }
  
  • 雪碧图:可以通过 PostCSS 插件自动合成,减少图标类图片的请求次数。

3. 外部化与 CDN
对于大型第三方库(如 Vue、Vue Router、Pinia),可以通过 externals 配置将它们排除在构建产物之外,然后通过 CDN 的 <script> 标签引入。这样既加快了构建速度,又利用了公共 CDN 的缓存优势。

// webpack externals 配置
externals: {
  vue: 'Vue',
  'vue-router': 'VueRouter',
  pinia: 'Pinia',
}

4. Gzip / Brotli 压缩
打包后的文件通常还需要在服务端开启压缩传输。可以用 compression-webpack-plugin 生成预压缩的 .gz 文件,但更推荐在 Nginx/Caddy 等反向代理层动态压缩,灵活且节省体积。

5. 可视化分析与持续监控

  • Webpack:使用 webpack-bundle-analyzer 生成可交互的树状图,一眼看出哪些模块占据了最大的体积。
  • Vite:有对应的 rollup-plugin-visualizer
  • 将分析步骤集成到 CI 中,当包体积超过阈值时报警,防止不知不觉间包体膨胀。

6. 持久化缓存(Webpack 5)
通过设置 cache.type: 'filesystem',Webpack 会将构建缓存写入磁盘,第二次构建时只重新编译变更模块,显著提升开发增量编译和重复生产构建的速度。

最终检查清单

  • 所有路由都用了动态 import() 了吗?
  • 组件库是按需引入的吗?
  • package.json 中正确设置了 sideEffects 吗?
  • 打包后的 chunk 是否合理,公共依赖是否被提取了?
  • 静态资源大小阈值和内联策略合适吗?
  • 是否产出了构建分析报告并检查过大块依赖?

打包优化不是一次性工作,而应作为项目持续集成的一部分。保持在每个迭代中观察包体变化,才能让应用始终“轻量高效”。