Vite生产构建优化实战:手动分块策略与产物体积压缩方案

Vite构建优化背景

Vite基于原生ESM和esbuild实现极速开发服务器启动,但生产构建使用Rollup打包,大型项目中构建耗时和产物体积仍是痛点。一个包含200个页面的中后台项目,冷构建耗时可能超过60秒,产物chunk过大导致首屏加载缓慢。通过手动配置Rollup参数、代码分割策略和Tree Shaking优化,构建时间可压缩一半以上,首屏加载体积减少40%。

依赖预构建与外部化

Vite的依赖预构建(optimizeDeps)将CommonJS/UMD依赖转换为ESM格式并缓存。大型依赖预构建耗时较长,可通过手动声明include列表锁定预构建范围,避免开发服务器冷启动时动态发现依赖导致的延迟。

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

export default defineConfig({
  optimizeDeps: {
    include: [
      'vue',
      'vue-router',
      'pinia',
      'element-plus',
      'echarts',
      'lodash-es',
      'dayjs',
      'axios'
    ],
    exclude: ['@vueuse/core']  // 按需引入,不预构建
  }
})

某些大型库(如xlsx、pdf-lib)仅在特定功能中使用,可完全外部化,通过CDN加载或动态import,避免打入主bundle:

export default defineConfig({
  build: {
    rollupOptions: {
      external: ['xlsx'],
      output: {
        globals: { xlsx: 'XLSX' }
      }
    }
  }
})

手动分块策略优化chunk体积

Vite默认按动态import自动分割chunk,但分割粒度不够精细,容易产生超大vendor chunk。通过manualChunks手动控制分块逻辑,将第三方依赖按功能分组:

export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks(id) {
          if (id.includes('node_modules')) {
            if (id.includes('vue') || id.includes('pinia')) {
              return 'vue-vendor'
            }
            if (id.includes('element-plus')) {
              return 'element-vendor'
            }
            if (id.includes('echarts')) {
              return 'echarts-vendor'
            }
            if (id.includes('@vueuse') || id.includes('lodash-es')) {
              return 'utils-vendor'
            }
            return 'vendor'
          }
        }
      }
    }
  }
})

这套分块策略将vue核心、UI库、图表库、工具库分离为独立chunk。路由级别的业务代码按需加载,只有访问对应页面时才请求相应chunk。通过Webpack Bundle Analyzer或rollup-plugin-visualizer可视化分析chunk构成:

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

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

第三方库按需引入减少Tree Shaking残留

Element Plus等组件库支持自动按需导入,但一些工具库默认全量引入导致产物膨胀。lodash是典型反面案例——全量引入约70KB,按需引入可降至10KB以内:

// 差: 全量引入
import _ from 'lodash'
_.debounce(fn, 300)

// 好: 按需引入
import debounce from 'lodash-es/debounce'
debounce(fn, 300)

// 或使用babel-plugin自动转换
// .babelrc
{
  "plugins": [
    ["lodash", { "id": ["lodash-es"] }]
  ]
}

ECharts按需引入效果更为显著,全量引入约1MB,只引入折线图和柱状图可压缩至200KB:

import * as echarts from 'echarts/core'
import { LineChart, BarChart } from 'echarts/charts'
import { GridComponent, TooltipComponent, LegendComponent } from 'echarts/components'
import { CanvasRenderer } from 'echarts/renderers'

echarts.use([
  LineChart, BarChart,
  GridComponent, TooltipComponent, LegendComponent,
  CanvasRenderer
])

// 创建图表实例
const chart = echarts.init(document.getElementById('chart'))
chart.setOption(option)

CSS代码分割与PurgeCSS

Element Plus等UI库的CSS文件体积较大,全量引入约300KB。Vite默认对CSS进行代码分割,但未使用组件的样式仍会打包。配合unplugin-auto-import和unplugin-vue-components实现组件和样式的自动按需引入:

import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

export default defineConfig({
  plugins: [
    vue(),
    AutoImport({
      resolvers: [ElementPlusResolver()]
    }),
    Components({
      resolvers: [ElementPlusResolver()]
    })
  ]
})

配置后,模板中直接使用el-table、el-button等组件会自动引入对应样式,未使用的组件样式不会打包。

Gzip与Brotli预压缩

Vite构建默认生成未压缩文件,由Nginx在运行时Gzip压缩。预压缩可减少服务端CPU占用并加速传输。Brotli压缩率比Gzip高15%-20%,现代浏览器均支持。通过vite-plugin-compression生成预压缩文件:

import viteCompression from 'vite-plugin-compression'

export default defineConfig({
  plugins: [
    vue(),
    viteCompression({
      algorithm: 'gzip',
      ext: '.gz',
      threshold: 10240  // 仅压缩大于10KB的文件
    }),
    viteCompression({
      algorithm: 'brotliCompress',
      ext: '.br',
      threshold: 10240
    })
  ]
})

Nginx配置优先使用Brotli预压缩文件:

# nginx.conf
brotli_static on;
gzip_static on;
gzip on;
gzip_types text/plain text/css application/javascript application/json;

构建缓存与增量编译

Vite 5+版本支持Rollup的持久化缓存,二次构建速度显著提升。配合cacheDir和fileAgingQueue可加速重建。对于Monorepo项目,通过tsconfig的paths映射和Vite的resolve.alias避免重复编译:

export default defineConfig({
  resolve: {
    alias: {
      '@': '/src',
      '@components': '/src/components',
      '@utils': '/src/utils'
    },
    dedupe: ['vue', 'vue-router', 'pinia']  // 确保单例
  },
  build: {
    cacheOptions: {
      cacheDir: 'node_modules/.vite-cache'
    }
  }
})

CI/CD环境中利用pnpm store缓存和Vite构建缓存,二次构建时间可从60秒压缩至20秒以内。确保CI runner的缓存目录正确挂载:

# GitHub Actions缓存配置
- name: Cache Vite Build
  uses: actions/cache@v3
  with:
    path: |
      node_modules/.vite-cache
      node_modules/.vite
    key: ${{ runner.os }}-vite-${{ hashFiles('**/pnpm-lock.yaml') }}
    restore-keys: |
      ${{ runner.os }}-vite-

资源内联与Base64阈值

小体积图片和SVG内联为Base64可减少HTTP请求数,但Base64编码会使体积增加33%。assetsInlineLimit控制内联阈值,默认4096字节。根据HTTP/2多路复用特性,建议将阈值降低至2048字节:

export default defineConfig({
  build: {
    assetsInlineLimit: 2048,
    rollupOptions: {
      output: {
        assetFileNames: (assetInfo) => {
          if (assetInfo.name.endsWith('.png')) {
            return 'assets/img/[name].[hash][extname]'
          }
          return 'assets/[name].[hash][extname]'
        },
        chunkFileNames: 'assets/js/[name].[hash].js',
        entryFileNames: 'assets/js/[name].[hash].js'
      }
    }
  }
})

大图片使用vite-plugin-imagemin进行无损压缩,SVG使用vite-plugin-svgr转换为Vue组件按需引入。通过这套组合策略,200页面级别的中后台项目首屏JS体积可从2.5MB降至1.2MB,LCP(最大内容渲染时间)从4.2秒优化至1.8秒。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/vite-sheng-chan-gou-jian-you-hua-shi-zhan-shou-dong-fen/

(0)
小编小编
上一篇 1天前
下一篇 1天前

相关推荐