Vite 凭借基于 ESM 的开发服务器和 Rollup 驱动的生产构建,已经成为现代前端项目的首选构建工具。然而,随着项目规模增长,默认配置往往无法满足性能需求。本文将从代码分割、Tree Shaking、构建配置、资源加载等多个维度,详细介绍 Vite 生产构建的优化策略。

一、代码分割与动态导入

代码分割(Code Splitting)是首屏加载优化的核心手段。通过动态 import() 语法,Vite 会自动将被导入模块拆分为独立 chunk,实现按需加载。

// 路由级别的懒加载
import { createRouter, createWebHistory } from 'vue-router'

const routes = [
  {
    path: '/',
    component: () => import('../views/Home.vue')
  },
  {
    path: '/dashboard',
    component: () => import('../views/Dashboard.vue')
  },
  {
    path: '/settings',
    component: () => import('../views/Settings.vue')
  }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

每个路由对应的组件会在用户首次访问该路由时才加载,大幅减少首屏 JavaScript 体积。配合 Webpack 风格的魔法注释,还可以自定义 chunk 名称:

// 自定义 chunk 名称
const Chart = () => import(
  /* webpackChunkName: "chart" */
  '../components/HeavyChart.vue'
)

二、手动分包策略

Vite 默认会将 node_modules 中的依赖打包到一个 vendor chunk 中。但对于大型项目,这个 vendor chunk 可能非常臃肿。通过 manualChunks 配置,可以更精细地控制分包策略。

// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  build: {
    rollupOptions: {
      output: {
        manualChunks(id) {
          // 将 React/Vue 等框架核心单独拆包(长期缓存)
          if (id.includes('node_modules/vue/') ||
              id.includes('node_modules/@vue/')) {
            return 'vue-vendor'
          }
          // 将 UI 组件库单独拆包
          if (id.includes('node_modules/element-plus/')) {
            return 'element-plus'
          }
          // 将工具库单独拆包
          if (id.includes('node_modules/lodash-es/') ||
              id.includes('node_modules/dayjs/')) {
            return 'utils'
          }
          // 将图表库单独拆包
          if (id.includes('node_modules/echarts/') ||
              id.includes('node_modules/zrender/')) {
            return 'echarts'
          }
          // 其余第三方依赖
          if (id.includes('node_modules/')) {
            return 'vendor'
          }
        }
      }
    }
  }
})

分包的核心原则是:变化频率不同的模块分开打包。框架代码很少变化,可以设置较长的缓存时间;业务代码频繁更新,单独打包后不会导致框架缓存失效。

三、Tree Shaking 与副作用标记

Vite 在生产模式下基于 Rollup 自动执行 Tree Shaking,移除未被使用的导出代码。但要确保其生效,需要注意以下几点。

// 确保使用 ES Module 导入(而非默认导入整个库)
// 不推荐:导入整个 lodash
import _ from 'lodash'           // Tree Shaking 无效

// 推荐:具名导入具体函数
import { debounce, throttle } from 'lodash-es'

// 或者使用子路径导入
import debounce from 'lodash/debounce'

如果包的 package.json 中没有正确设置 "sideEffects": false,Tree Shaking 可能无法生效。对于自己的库项目,务必在 package.json 中声明副作用:

{
  "name": "my-ui-lib",
  "sideEffects": false,
  "sideEffects": ["*.css", "./src/polyfills.js"]
}

四、Chunk 大小控制与警告处理

当单个 chunk 超过 500KB 时,Vite 构建会输出警告。可以通过 chunkSizeWarningLimit 调整阈值,但更好的方式是真正优化 chunk 大小。

export default defineConfig({
  build: {
    // 调整 chunk 大小警告阈值(单位 KB)
    chunkSizeWarningLimit: 600,

    rollupOptions: {
      output: {
        // 文件命名策略(利用缓存)
        chunkFileNames: 'js/[name]-[hash].js',
        entryFileNames: 'js/[name]-[hash].js',
        assetFileNames: '[ext]/[name]-[hash].[ext]'
      }
    },

    // CSS 代码分割(默认开启)
    cssCodeSplit: true,

    // 资源内联阈值(小于 4KB 的资源转为 base64)
    assetsInlineLimit: 4096,

    // 禁用 CSS 内联到 JS(避免 FOUC)
    cssMinify: 'lightningcss'
  }
})

五、Preload 与 Prefetch 策略

Vite 默认会为动态导入的 chunk 添加 prefetch 链接,在浏览器空闲时预加载。但对于大型应用,过多的 prefetch 可能浪费带宽。可以按需调整这一行为。

// vite.config.js
export default defineConfig({
  build: {
    // 禁用所有 prefetch
    modulePreload: {
      polyfill: false
    }
  }
})

// 在组件中手动控制预加载
// 只对关键路由预加载
const router = createRouter({ /* ... */ })

router.beforeResolve((to) => {
  // 鼠标悬停在链接上时预加载
})

// 或者使用 link 标签手动控制
// <link rel="preload" href="/js/dashboard-[hash].js" as="script">
// <link rel="prefetch" href="/js/settings-[hash].js">

preload 表示当前页面马上需要的资源,优先级高;prefetch 表示未来可能需要的资源,优先级低,浏览器空闲时才加载。合理使用这两种策略可以显著改善页面加载体验。

六、构建分析与持续优化

优化不能凭感觉,需要数据驱动。推荐使用 rollup-plugin-visualizer 生成构建产物分析报告。

import { visualizer } from 'rollup-plugin-visualizer'

export default defineConfig({
  plugins: [
    vue(),
    visualizer({
      filename: './dist/stats.html',
      open: true,
      gzipSize: true,
      brotliSize: true
    })
  ]
})

运行 vite build 后会自动打开可视化报告页面,可以直观地看到每个 chunk 中包含的模块及其大小占比。根据报告结果调整分包策略、排查意外引入的大型依赖。

总结

Vite 构建优化是一个系统工程:代码分割减少首屏加载量,精细分包优化缓存命中率,Tree Shaking 移除无用代码,preload/prefetch 策略优化资源加载时机。建议在项目初期就建立构建产物分析的习惯,每次发版前检查 bundle 大小变化,避免性能退化。配合 CI 中的 bundle 大小限制检查,可以确保应用的加载性能始终保持在合理范围内。