Vite 打包优化终极指南:从 30MB 到 800KB 的性能飞跃

Vite 打包优化终极指南:从 30MB 到 800KB 的性能飞跃

📦 涉及组件清单

组件名称 GitHub 链接 Star 数量 功能描述 推荐理由
rollup-plugin-visualizer GitHub 4.2k+ 打包体积可视化分析 Vite/Rollup 场景最常用,定位瓶颈直观
unplugin-vue-components GitHub 5k+ Vue 组件自动按需导入 显著降低 UI 库引入成本,配置简洁
unplugin-auto-import GitHub 4k+ API 自动导入 减少样板 import,增强开发效率
vite-plugin-inspect GitHub 2k+ 查看 Vite 插件变换链路 排查插件冲突和 transform 成本很有帮助
vite-plugin-compression GitHub 2k+ 生成 gzip/br 压缩产物 服务端配合后对传输体积收益明显
vite-plugin-image-optimizer GitHub 500+ 基于 Sharp.js 的图片压缩 更现代,支持缓存,安装友好
vite-plugin-cdn-import GitHub 500+ 外部依赖 CDN 化 快速完成 external + HTML 注入
unplugin-imagemin GitHub 300+ 图片压缩插件 基于 squoosh/sharp,支持多种格式
vite-plugin-perfsee GitHub 600+ 性能综合检测工具 结合 Lighthouse 打分、构建体积分析
speed-measure-vite-plugin GitHub 500+ 构建耗时分析 精准定位哪个插件拖慢构建速度
vite-plugin-remove-console GitHub 200+ 清除 console 语句 专用插件,比 terser 配置更简单
vite-plugin-html GitHub 1k+ HTML 压缩与动态注入 支持 EJS 模板、HTML 压缩、多页面注入

引言

Vite 作为新一代前端构建工具,凭借其基于 ES Module 的开发时快速启动和基于 Rollup 的生产时高效打包能力,已经成为现代前端项目的主流选择。然而,即使使用 Vite,默认配置下的项目仍可能出现以下问题:

  • 构建速度慢:大量依赖解析耗时、编译任务繁重、插件链过长
  • 打包体积大:全量引入大型库、重复模块未合并、无用代码未清除
  • 缓存命中差:分包策略不合理导致浏览器缓存频繁失效
  • 用户体验不佳:FCP、LCP、TTI 等核心指标表现不佳

一、核心概念:Vite 为什么快,又为什么会慢

1.1 Vite 的速度来源

要理解 Vite 的优化方向,首先需要了解 Vite 为什么快。Vite 的快速体验来自以下几个核心机制:

开发阶段:原生 ESM 与按需编译

传统的打包工具(如 Webpack)在开发环境下需要将所有模块打包成 bundle,然后才能启动开发服务器。这个过程在大型项目中可能需要几十秒甚至几分钟。

Vite 则采用了完全不同的策略:利用浏览器原生支持 ES Module 的特性,Vite 在开发环境下不做整体打包,而是让浏览器直接请求源文件。对于 import 语句,Vite 充当一个"按需编译"的服务器------只有当浏览器请求某个文件时,Vite 才会对该文件进行转换和压缩。

这种模式的优点是启动速度极快,因为不需要遍历整个依赖图。启动时间从 O(n) 降到了 O(1),只与当前访问的页面相关,与项目整体规模无关。

依赖预构建:解决 CommonJS 兼容性问题

虽然浏览器原生支持 ESM,但 node_modules 中大量库仍然使用 CommonJS 格式。Vite 通过预构建(Pre-bundling)机制,将这些 CommonJS 依赖转换为 ESM 格式,并整合成少量文件,减少浏览器请求数量。

生产阶段:Rollup 高效打包

Vite 在生产环境使用 Rollup 进行打包。Rollup 相比 Webpack,設計上更专注于 ES Module 的静态分析,能够生成更干净的 bundle,并且天然支持高质量的 Tree Shaking。

1.2 速度瓶颈的常见来源

理解了 Vite 为什么快,我们再来看它为什么会慢。常见的瓶颈来源包括:

依赖层面的问题

  • 大型依赖全量引入(如 lodash、echarts、moment)
  • 依赖解析路径过深(路径别名配置不当)
  • 重复依赖未合并(多个包引用了同一库的不同版本)

配置层面的问题

  • 插件链过长,许多插件在生产构建中不必要
  • sourcemap 生成开启,增加构建时间
  • 未配置依赖预构建或预构建范围不当
  • 开发环境插件未区分,生产环境仍在运行

资源层面的问题

  • 图片等静态资源未经压缩
  • 资源内联阈值设置不当(过大或过小)
  • 静态资源未分类,浏览器难以并行加载

分包层面的问题

  • 所有代码打包成单一 bundle
  • 分包粒度不合理(过细导致请求碎片化,过粗导致缓存失效)
  • 第三方库与业务代码未分离

二、打包诊断:优化从了解开始

做任何优化之前,首先要清楚当前项目的打包状态------哪些模块占用了大量体积?哪些依赖是主要性能瓶颈?盲目优化不仅浪费时间,还可能适得其反。

2.1 rollup-plugin-visualizer:打包体积可视化分析

rollup-plugin-visualizer 是 Vite 项目中最经典的打包分析插件,功能对标 Webpack 的 webpack-bundle-analyzer。它可以生成交互式的可视化报告,帮助开发者直观定位体积瓶颈。

核心特性

  • 支持多种视图模式:treemap(矩形层次图)、sunburst(循环层次图)、network(网格图)、flamegraph(火焰图)
  • 显示 gzip 与 brotli 压缩后的体积
  • 支持 JSON、YAML 等格式导出,便于自动化集成

安装与配置

bash 复制代码
npm install rollup-plugin-visualizer -D
typescript 复制代码
// vite.config.ts
import { defineConfig } from 'vite'
import { visualizer } from 'rollup-plugin-visualizer'

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

执行 npm run build 后,会在 dist 目录生成 stats.html 文件。打开后可以清晰看到各个模块的体积占比,通常 node_modules 中的第三方库会占据最大比例。

2.2 vite-plugin-inspect:插件链路分析

vite-plugin-inspect 用于查看 Vite 插件的执行流程和模块依赖关系,排查构建卡顿环节。当构建过程异常缓慢时,这个工具能帮你精准定位是哪个插件在"拖后腿"。

安装与配置

bash 复制代码
npm install vite-plugin-inspect -D
typescript 复制代码
// vite.config.ts
import Inspect from 'vite-plugin-inspect'

export default defineConfig({
  plugins: [Inspect()]
})

启动开发服务器后,访问 http://localhost:5173/__inspect/,可以查看插件的 transform 顺序、每个插件的执行耗时,以及模块的依赖关系图。

2.3 speed-measure-vite-plugin:构建耗时分析

speed-measure-vite-plugin 专门用于测量每个插件的执行耗时。当构建时间过长时,使用此插件可以精准定位问题环节。

安装与配置

bash 复制代码
npm install speed-measure-vite-plugin -D
typescript 复制代码
// vite.config.ts
import SpeedMeasurePlugin from 'speed-measure-vite-plugin'

const smp = new SpeedMeasurePlugin()

export default defineConfig({
  plugins: smp.wrap({
    vue()
  })
})

执行构建后,终端会输出每个插件的耗时信息,帮助你识别慢插件。

2.4 vite-plugin-perfsee:综合性能检测

vite-plugin-perfsee 是 Perfsee 团队出品的性能分析工具,结合了 Lighthouse 打分、构建体积分析和热更新性能分析。适合进行构建性能的综合检测,也可在提交 PR 时做性能回归检测。

安装与配置

bash 复制代码
npm install vite-plugin-perfsee -D
typescript 复制代码
// vite.config.ts
import { perfsee } from 'vite-plugin-perfsee'

export default defineConfig({
  plugins: [
    perfsee({
      // 可配置项
    })
  ]
})

2.5 建立性能基线

在使用诊断工具之前,建议先建立性能基线,记录以下关键指标:

指标类型 具体指标 测量方法
产物体积 dist 总大小、最大 JS 文件大小 du -sh dist/
构建性能 冷启动时间、热更新时间 CLI 输出或手动计时
加载性能 FCP、LCP、TTI Lighthouse 或 Chrome DevTools
网络性能 请求数量、资源大小 Network 面板

示例记录

diff 复制代码
优化前基线:
- dist 总大小:30 MB
- 最大 JS 文件:7 MB
- 构建耗时:45s
- 首屏加载:8.5s
- 资源请求数:156 个

有了基线数据,后续的优化效果才能量化对比。


三、构建速度优化:让打包快起来

Vite 构建速度慢的核心原因是「依赖解析耗时久、编译任务繁重、资源处理低效」。以下 10 个技巧从依赖、编译、缓存、配置四个层面帮助提升构建速度。

3.1 依赖预构建优化

原理解析 :Vite 开发环境会预构建依赖(将 CommonJS 依赖转为 ES Module),但预构建的范围默认只包含一小部分常用依赖。通过 optimizeDeps 精细控制预构建范围,可以减少开发服务器启动时的重复解析工作。

配置

typescript 复制代码
// vite.config.ts
export default defineConfig({
  optimizeDeps: {
    include: [
      'vue',
      'vue-router',
      'pinia',
      'axios',
      'lodash-es'
    ],
    exclude: ['some-large-library'],
    cacheDir: '.vite'
  }
})

效果 :冷启动速度可提升 30%-50%。对于大型项目,建议将所有高频使用的依赖都加入 include 列表。

3.2 开启构建缓存

原理解析:Vite 支持构建缓存机制,将编译后的结果缓存到本地文件系统。下次打包时,Vite 会比较文件时间戳和内容哈希,只重新编译有变化的部分。

配置

typescript 复制代码
// vite.config.ts
export default defineConfig({
  build: {
    cache: {
      type: 'filesystem',
      key: (config) => `${config.mode}-${process.env.npm_package_version}`
    }
  }
})

CI/CD 优化建议

bash 复制代码
# 在 CI/CD 中缓存 .vite 和 node_modules
pnpm install --frozen-lockfile
pnpm build
# 缓存 node_modules/.vite 目录

效果:二次构建速度可提升 50%-70%。

3.3 精简插件配置

原理解析:过多的 Vite 插件会增加构建耗时,尤其是一些仅在开发环境有用的插件。生产环境应该禁用与构建无关的插件。

区分开发/生产环境插件

typescript 复制代码
// vite.config.ts
import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import visualizer from 'rollup-plugin-visualizer'
import eslintPlugin from 'vite-plugin-eslint'

export default defineConfig(({ mode }) => {
  const env = loadEnv(mode, process.cwd())
  const isProduction = mode === 'production'

  return {
    plugins: [
      vue(),
      !isProduction && eslintPlugin(),
      isProduction && visualizer({
        open: true,
        gzipSize: true,
        brotliSize: true
      })
    ].filter(Boolean)
  }
})

建议在生产环境禁用的插件

  • vite-plugin-eslint(代码检查,开发时已运行)
  • vite-plugin-stylelint(样式检查,开发时已运行)
  • vite-plugin-vue-inspector(Vue 调试工具)

效果:构建速度可提升 10%-20%。

3.4 优化 HMR 热更新

原理解析 :热更新(HMR)延迟会严重影响开发效率。在 Windows 或 Docker 环境下,文件系统监听可能不灵敏,需要通过配置 usePolling: true 来使用轮询方式。

配置

typescript 复制代码
// vite.config.ts
export default defineConfig({
  server: {
    watch: {
      usePolling: true,
      ignored: ['**/node_modules/**', '**/.git/**', '**/dist/**'],
      interval: 100
    },
    open: true,
    port: 3000,
    strictPort: true
  }
})

效果:热更新延迟从 3-5s 降至 1s 以内。

3.5 关闭生产环境 sourceMap

原理解析sourceMap 用于代码调试,生产环境无需生成。关闭 sourceMap 可以同时减少打包体积和构建时间。

配置

typescript 复制代码
// vite.config.ts
export default defineConfig({
  build: {
    sourcemap: false
  }
})

如果需要在生产环境保留错误追踪能力,可以使用 'hidden' 模式,这样会生成 sourceMap 但不在产物中引用:

typescript 复制代码
sourcemap: 'hidden'

效果:构建速度提升 20%-30%,产物体积减少 50%+。

3.6 路径别名优化

原理解析:配置路径别名不仅方便开发,还能减少 Vite 的路径查找时间。合理的别名配置可以避免相对路径的多次解析。

配置

typescript 复制代码
// vite.config.ts
import { defineConfig } from 'vite'
import { resolve } from 'path'

export default defineConfig({
  resolve: {
    alias: {
      '@': resolve(__dirname, 'src'),
      '~': resolve(__dirname, 'node_modules')
    }
  }
})

3.7 多页面应用(MPA)优化

原理解析:多页面应用默认会构建所有页面。通过配置多入口,可以让每个页面独立构建,未访问的页面不参与当前构建。

配置

typescript 复制代码
// vite.config.ts
import { resolve } from 'path'

export default defineConfig({
  build: {
    rollupOptions: {
      input: {
        main: resolve(__dirname, 'index.html'),
        admin: resolve(__dirname, 'admin.html'),
        mobile: resolve(__dirname, 'mobile.html')
      }
    }
  }
})

效果:大型 MPA 项目的冷启动速度可提升 50% 以上。

3.8 合理设置 chunk 大小警告阈值

原理解析:Vite 默认的 chunk 大小警告阈值是 500KB。对于大型应用或特定场景(如管理后台),这个阈值可能过于严格。适当调整可以减少无意义的警告干扰。

配置

typescript 复制代码
// vite.config.ts
export default defineConfig({
  build: {
    chunkSizeWarningLimit: 1500
  }
})

3.9 减少 resolve 解析

原理解析 :每次 import 时 Vite 都需要解析模块路径。减少 resolve.extensions 列表长度、避免过多自定义 alias 可以加快解析速度。

配置建议

typescript 复制代码
// vite.config.ts
export default defineConfig({
  resolve: {
    extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json'],
    mainFields: ['module', 'jsnext:main', 'jsnext']
  }
})

3.10 虚拟模块实现真正的按需打包

对于"同仓多发行版本"的场景(如 full/member/lite 版本),可以通过虚拟模块在构建时决定路由入口,实现真正的编译期裁剪。

步骤 1:创建独立的路由配置文件

typescript 复制代码
// src/router/routes.full.ts
export const getRoutes = async () => {
  const [
    { homeRoutes },
    { mineRoutes },
    { orderRoutes }
  ] = await Promise.all([
    import('@/router/modules/home'),
    import('@/router/modules/mine'),
    import('@/router/modules/order')
  ])

  return [
    ...homeRoutes,
    ...mineRoutes,
    ...orderRoutes
  ]
}

步骤 2:创建虚拟模块插件

typescript 复制代码
// vite.config.ts
import path from 'path'
import fs from 'fs'

const virtualRoutesPlugin = (routeType) => {
  const virtualModuleId = 'virtual:routes'
  const resolvedVirtualModuleId = '\0' + virtualModuleId
  const routesFile = path.resolve(__dirname, `src/router/routes.${routeType}.ts`)

  return {
    name: 'virtual-routes',
    enforce: 'pre',
    resolveId(id) {
      if (id === virtualModuleId) {
        return resolvedVirtualModuleId
      }
    },
    load(id) {
      if (id === resolvedVirtualModuleId) {
        return fs.readFileSync(routesFile, 'utf-8')
      }
    }
  }
}

步骤 3:配置打包脚本

bash 复制代码
# Member 专属版(只打包 member 相关路由)
VITE_ROUTE_TYPE=member npm run build

# 完整版(打包所有路由)
npm run build

效果对比

指标 完整版 Member 版
JS 文件数量 265 个 20 个
打包时间 31.49s 11.85s
核心 JS 大小 2.2MB ~2.0MB

四、打包体积优化:让产物小下去

相比构建速度,打包体积优化更能直接影响用户体验。以下从代码分割、资源压缩、按需引入、CDN 加速四个维度展开。

4.1 代码分割:合理拆分管好缓存

代码分割(Code Splitting)是优化首屏加载的关键策略。通过 manualChunks 将大体积依赖和业务代码分离,可以实现更好的缓存控制和按需加载。

策略一:按依赖类型基础分包
typescript 复制代码
// vite.config.ts
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          'vue-vendor': ['vue', 'vue-router', 'pinia'],
          'ui-vendor': ['element-plus', 'ant-design-vue'],
          'utils-vendor': ['lodash-es', 'dayjs', 'axios']
        }
      }
    }
  }
})
策略二:逐库精细分包

每个 node_modules 下的包单独打包成独立文件,缓存控制更精细:

typescript 复制代码
// vite.config.ts
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks(id) {
          if (id.includes('node_modules')) {
            return id.toString()
              .split('node_modules/')[1]
              .split('/')[0]
              .toString()
          }
        }
      }
    }
  }
})

注意:这种方式会产生大量小文件,增加 HTTP 请求数,建议仅在 HTTP/2 环境下使用。

策略三:针对大体积库的详细分包

对于 ant-design-vue 这类大型 UI 库,可以进一步细分:

typescript 复制代码
// vite.config.ts
const antdSplitArr = [
  'node_modules/ant-design-vue/es/table',
  'node_modules/ant-design-vue/es/select',
  'node_modules/ant-design-vue/es/form',
  'node_modules/ant-design-vue/es/menu',
  'node_modules/ant-design-vue/es/vc-picker',
  'node_modules/ant-design-vue/es/_util'
]

export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks(id) {
          for (const path of antdSplitArr) {
            if (id.includes(path)) {
              return 'antd-split'
            }
          }
          if (id.includes('node_modules/echarts')) return 'echarts'
          if (id.includes('node_modules/ant-design-vue/es/')) return 'antd'
          if (id.includes('node_modules/vue') ||
              id.includes('node_modules/pinia') ||
              id.includes('node_modules/vue-router')) return 'vue'
        }
      }
    }
  }
})

⚠️ 注意事项:id 分包方式可能导致依赖引用异常,建议先在开发环境测试,确认无报错再使用。

静态资源分类输出

将 JS、CSS、图片等资源分类到不同文件夹:

typescript 复制代码
// vite.config.ts
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        chunkFileNames: 'static/js/[name]-[hash].js',
        entryFileNames: 'static/js/[name]-[hash].js',
        assetFileNames: 'static/[ext]/[name]-[hash].[ext]'
      }
    }
  }
})

输出效果

perl 复制代码
dist/
├── static/
│   ├── js/
│   │   ├── vue-vendor-a1b2c3d4.js
│   │   └── index-e5f6g7h8.js
│   ├── css/
│   │   └── index-i9j0k1l2.css
│   └── img/
│       └── logo-m3n4o5p6.png

4.2 Tree Shaking:自动清除无用代码

原理解析:Tree Shaking 是 Rollup 提供的特性,通过静态分析 ES Module 的导入导出关系,自动剔除未使用的代码。Tree Shaking 有效的前提是模块必须使用 ES Module 格式。

esbuild vs terser 压缩对比
特性 esbuild terser
压缩速度 快 20-40 倍 较慢
压缩率 略低 1-2% 更高
配置复杂度 简单 较复杂
Tree Shaking 基础 更彻底
兼容性 现代浏览器 所有浏览器
esbuild 压缩配置
typescript 复制代码
// vite.config.ts
export default defineConfig({
  esbuild: {
    pure: ['console.log', 'console.info', 'console.debug'],
    drop: ['debugger']
  }
})
terser 压缩配置
bash 复制代码
npm install terser -D
typescript 复制代码
// vite.config.ts
export default defineConfig({
  build: {
    minify: 'terser',
    terserOptions: {
      compress: {
        drop_console: true,
        drop_debugger: true,
        pure_funcs: ['console.log', 'console.info']
      }
    }
  }
})
lodash 的 Tree Shaking 问题

lodash 存在一个著名的 Tree Shaking 问题:完整版的 lodash 是 CommonJS 格式,无法被 Tree Shaking。

typescript 复制代码
// ❌ 错误:全量引入,约 70KB
import _ from 'lodash'
const result = _.cloneDeep(obj)

// ✅ 正确:使用 lodash-es,约 13KB
import { cloneDeep } from 'lodash-es'
const result = cloneDeep(obj)

体积对比

完整版体积 按需引入体积 节省比例
lodash 78.64 KB 13.23 KB 83%
moment.js 200KB+ 10KB (dayjs) 95%
echarts 600KB+ 100KB 83%

4.3 按需引入:不打包未使用的代码

UI 组件按需引入

使用 unplugin-vue-components 自动按需导入 Vue 组件:

bash 复制代码
npm install unplugin-vue-components -D
typescript 复制代码
// vite.config.ts
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver, AntDesignVueResolver } from 'unplugin-vue-components/resolvers'

export default defineConfig({
  plugins: [
    Components({
      dirs: ['src/components'],
      extensions: ['vue', 'jsx'],
      dts: 'src/components.d.ts',
      resolvers: [ElementPlusResolver()]
    })
  ]
})

支持的 UI 库解析器:Element Plus、Ant Design Vue、Vant、Naive UI、Prime Vue、Quasar、TDesign、View UI、Arco Design Vue 等 20+ 主流 UI 库。

效果对比

全量引入 按需引入 节省比例
Element Plus 300KB+ 30KB 90%
Ant Design Vue 500KB+ 50KB 90%
API 自动导入

使用 unplugin-auto-import 自动导入 Vue、Vue Router 等 API,无需手动编写 import 语句:

bash 复制代码
npm install unplugin-auto-import -D
typescript 复制代码
// vite.config.ts
import AutoImport from 'unplugin-auto-import/vite'

export default defineConfig({
  plugins: [
    AutoImport({
      imports: ['vue', 'vue-router', 'pinia', '@vueuse/core'],
      dts: 'src/auto-import.d.ts',
      vueTemplate: true
    })
  ]
})

使用效果

typescript 复制代码
// ❌ 引入前
import { ref, computed, watch } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useStore } from 'pinia'

const count = ref(0)
const doubled = computed(() => count.value * 2)

// ✅ 引入后(无需手动 import)
const count = ref(0)
const doubled = computed(() => count.value * 2)
ECharts 按需引入

ECharts 完整引入体积巨大,通过按需引入可以显著减少体积:

typescript 复制代码
// src/utils/echarts.ts
import * as echarts from 'echarts/core'
import { BarChart, LineChart, PieChart, GaugeChart } from 'echarts/charts'
import {
  TitleComponent,
  TooltipComponent,
  GridComponent,
  LegendComponent,
  ToolboxComponent
} from 'echarts/components'
import { LabelLayout, UniversalTransition } from 'echarts/features'
import { CanvasRenderer } from 'echarts/renderers'

echarts.use([
  TitleComponent,
  TooltipComponent,
  GridComponent,
  LegendComponent,
  ToolboxComponent,
  BarChart,
  LineChart,
  PieChart,
  GaugeChart,
  LabelLayout,
  UniversalTransition,
  CanvasRenderer
])

export default echarts

4.4 路由与组件懒加载

路由级懒加载
typescript 复制代码
// router/index.ts
const routes = [
  {
    path: '/home',
    name: 'Home',
    component: () => import('@/views/Home.vue')
  },
  {
    path: '/about',
    name: 'About',
    component: () => import('@/views/About.vue')
  }
]
组件级懒加载

对于体积较大的组件(如富文本编辑器、复杂图表),可使用 defineAsyncComponent 实现懒加载:

typescript 复制代码
// 组件中使用
<template>
  <div>
    <button @click="showEditor = true">显示编辑器</button>
    <RichEditor v-if="showEditor" />
  </div>
</template>

<script setup>
import { ref, defineAsyncComponent } from 'vue'

const showEditor = ref(false)
const RichEditor = defineAsyncComponent(() => import('@/components/RichEditor.vue'))
</script>

4.5 资源压缩:减少传输体积

Gzip/Brotli 压缩原理

代码压缩后,传输体积可减少 60%-80%。当请求静态资源时,服务器识别到 .gz.br 后缀的文件,自动返回压缩版本,浏览器解压后使用。

压缩率对比

压缩方式 压缩率 说明
无压缩 0% 原始体积
Gzip 60%-70% 通用性强,所有浏览器支持
Brotli 70%-80% 更高压缩率,需要服务器支持
vite-plugin-compression 配置
bash 复制代码
npm install vite-plugin-compression -D
typescript 复制代码
// vite.config.ts
import viteCompression from 'vite-plugin-compression'

export default defineConfig({
  plugins: [
    viteCompression({
      verbose: true,
      threshold: 10240,
      algorithm: 'brotliCompress',
      ext: '.br'
    }),
    viteCompression({
      verbose: true,
      threshold: 10240,
      algorithm: 'gzip',
      ext: '.gz'
    })
  ]
})
Nginx 配置(配合压缩使用)
nginx 复制代码
server {
    gzip on;
    gzip_types text/plain text/css application/javascript application/json text/xml;
    gzip_comp_level 6;
    gzip_min_length 1024;
    gzip_vary on;

    brotli on;
    brotli_types text/plain text/css application/javascript application/json text/xml;
    brotli_comp_level 6;
}
图片压缩

方案一:unplugin-imagemin(推荐)

bash 复制代码
npm install unplugin-imagemin -D
typescript 复制代码
// vite.config.ts
import imagemin from 'unplugin-imagemin/vite'

export default defineConfig({
  plugins: [
    imagemin({
      mode: 'sharp',
      compress: {
        jpeg: { quality: 25 },
        png: { quality: 25 },
        webp: { quality: 25 }
      },
      conversion: [
        { from: 'png', to: 'webp' },
        { from: 'jpeg', to: 'png' }
      ]
    })
  ]
})

方案二:vite-plugin-image-optimizer

bash 复制代码
npm install vite-plugin-image-optimizer -D
npm install sharp svgo -D
typescript 复制代码
// vite.config.ts
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'

export default defineConfig({
  plugins: [
    ViteImageOptimizer({
      logStats: true,
      test: /\.(jpe?g|png|gif|tiff|webp|svg|avif)$/i,
      svg: {
        multipass: true,
        plugins: [
          { name: 'preset-default' },
          'sort-attrs'
        ]
      },
      png: { quality: 80 },
      jpeg: { quality: 80 },
      webp: { lossless: true },
      cache: true
    })
  ]
})

4.6 CDN 加速:减少打包体积

原理解析:将大体积依赖通过 CDN 加载,利用浏览器缓存实现加速。同时,主包体积减小,首屏加载速度提升。

CDN 服务商对比
服务商 特点 访问速度 生态丰富度
jsDelivr 免费、开源、GitHub/CDN 全球 CDN 非常丰富
unpkg NPM 包直接访问 全球 CDN 丰富
cdnjs 社区维护 全球 CDN 一般
vite-plugin-cdn-import 配置
bash 复制代码
npm install vite-plugin-cdn-import -D
typescript 复制代码
// vite.config.ts
import { Plugin as importToCDN, autoComplete } from 'vite-plugin-cdn-import'

export default defineConfig({
  plugins: [
    importToCDN({
      modules: [
        autoComplete('vue'),
        autoComplete('vue-router'),
        autoComplete('pinia'),
        autoComplete('axios'),
        {
          name: 'echarts',
          var: 'echarts',
          path: 'https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js'
        }
      ]
    })
  ]
})
CDN 注意事项
注意点 说明
可靠性 依赖第三方 CDN 可用性,建议使用知名 CDN
版本控制 锁定具体版本号,避免升级导致兼容问题
安全风险 引入外部脚本存在 XSS 风险,需评估来源可信度
调试困难 生产环境问题定位难度增加

实际案例 :某管理后台项目通过 CDN + 分包 + 按需引入,包体积从 1.7MB 优化至 899KB,减少 47%


五、完整配置示例

将以上所有优化策略整合为一个完整的配置:

typescript 复制代码
// vite.config.ts
import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import AutoImport from 'unplugin-auto-import/vite'
import viteCompression from 'vite-plugin-compression'
import { visualizer } from 'rollup-plugin-visualizer'
import { Plugin as importToCDN, autoComplete } from 'vite-plugin-cdn-import'
import viteImagemin from 'vite-plugin-imagemin'
import removeConsole from 'vite-plugin-remove-console'

export default defineConfig(({ mode }) => {
  const env = loadEnv(mode, process.cwd())
  const isProduction = mode === 'production'

  return {
    resolve: {
      alias: {
        '@': resolve(__dirname, 'src')
      }
    },

    optimizeDeps: {
      include: ['vue', 'vue-router', 'pinia', 'axios', 'lodash-es'],
      exclude: ['echarts']
    },

    server: {
      host: '0.0.0.0',
      port: 3000,
      open: true,
      watch: {
        usePolling: true,
        ignored: ['**/node_modules/**', '**/.git/**', '**/dist/**']
      }
    },

    plugins: [
      vue(),

      AutoImport({
        imports: ['vue', 'vue-router', 'pinia', '@vueuse/core'],
        dts: 'src/auto-imports.d.ts'
      }),

      Components({
        dirs: ['src/components'],
        extensions: ['vue', 'jsx'],
        dts: 'src/components.d.ts',
        resolvers: [ElementPlusResolver({ importStyle: 'sass' })]
      }),

      isProduction && visualizer({
        open: true,
        gzipSize: true,
        brotliSize: true,
        filename: 'dist/stats.html'
      }),

      isProduction && viteCompression({
        algorithm: 'brotliCompress',
        threshold: 10240,
        ext: '.br'
      }),

      isProduction && viteCompression({
        algorithm: 'gzip',
        threshold: 10240,
        ext: '.gz'
      }),

      isProduction && importToCDN({
        modules: [
          autoComplete('vue'),
          autoComplete('vue-router'),
          autoComplete('pinia'),
          autoComplete('axios'),
          autoComplete('lodash')
        ]
      }),

      isProduction && viteImagemin({
        gifsicle: { optimizationLevel: 7 },
        optipng: { optimizationLevel: 7 },
        mozjpeg: { quality: 20 },
        pngquant: { quality: [0.8, 0.9] }
      }),

      isProduction && removeConsole()
    ].filter(Boolean),

    build: {
      minify: 'terser',
      sourcemap: false,
      chunkSizeWarningLimit: 1500,
      assetsInlineLimit: 4096,

      terserOptions: {
        compress: {
          drop_console: true,
          drop_debugger: true
        }
      },

      rollupOptions: {
        output: {
          chunkFileNames: 'static/js/[name]-[hash].js',
          entryFileNames: 'static/js/[name]-[hash].js',
          assetFileNames: 'static/[ext]/[name]-[hash].[ext]',

          manualChunks: {
            vue: ['vue', 'vue-router', 'pinia'],
            elementPlus: ['element-plus'],
            utils: ['lodash-es', 'dayjs', 'axios']
          },

          experimentalMinChunkSize: 20 * 1024
        }
      }
    }
  }
})

六、方案对比与选型建议

6.1 优化策略对比

优化策略 收益 复杂度 风险点 建议优先级
打包分析定位瓶颈 ⭐⭐⭐ 必做
依赖预构建优化 ⭐⭐⭐ 必做
构建缓存 CI 配置 ⭐⭐⭐ 必做
关闭 sourcemap ⭐⭐ 推荐
精简插件配置 可能遗漏检查 ⭐⭐ 推荐
manualChunks 分包 拆分不当影响缓存 ⭐⭐⭐ 必做
UI/工具按需引入 少量样式漏引 ⭐⭐⭐ 必做
Gzip/Brotli 压缩 服务端未配则无效 ⭐⭐⭐ 必做
CDN external 中-高 版本与可用性风险 ⭐⭐ 按需
图片压缩 质量损失 ⭐⭐ 推荐
路由懒加载 ⭐⭐⭐ 必做
虚拟模块按需构建 构建链路复杂 ⭐ 按需

6.2 项目规模选型建议

项目规模 核心优化项 可选优化项
小型项目 (< 100KB) 按需引入、路由懒加载、terser 缓存配置
中型项目 (100KB - 1MB) + 分包策略、Gzip 压缩 CDN、图片压缩
大型项目 (> 1MB) + CDN external、虚拟模块 详细分包、CI 集成

6.3 优化效果预期

优化策略 预期效果
打包分析定位瓶颈 精准识别优化方向
合理分包 减少首屏加载体积,提高缓存命中率
清除 console/debugger 减小包体积约 5-10%
Gzip/Brotli 压缩 减少传输体积约 60%-80%
UI 组件按需引入 Element Plus 从 300KB+ 降至 30KB
ECharts 按需引入 从 600KB 降至 100KB 左右
图片压缩 根据图片数量可减少 30%-50%
CDN 加速 减少主包体积,利用浏览器缓存
路由懒加载 按需加载,减少首屏 JS 体积
Tree Shaking 配合按需引入可减少 80%+ 无用代码

七、常见问题与解决方案

问题 1:rollupOptions 配置不生效

排查步骤

  1. 删除 node_modules/.vite 缓存目录后重新构建
  2. 检查配置是否被多份 defineConfig 覆盖
  3. 确认当前命令使用的 mode 与条件判断一致
  4. 检查 Vite 版本,确保配置语法正确

问题 2:CDN 引入后出现全局变量未定义

排查步骤

  1. 核对 external 映射变量名(如 vue -> Vue
  2. 确认 HTML 注入顺序(先依赖后业务)
  3. 检查依赖的二级依赖(如 vue-demi 也需要 CDN 引入)
typescript 复制代码
// vue-demi 也需要 CDN
{
  name: 'vue-demi',
  var: 'VueDemi',
  path: 'https://cdn.jsdelivr.net/npm/vue-demi@0.14.6/lib/index.iife.js'
}

问题 3:按需引入后样式丢失

解决方案:部分 UI 库的样式需要单独引入:

bash 复制代码
npm install vite-plugin-style-import -D
typescript 复制代码
// vite.config.ts
import { createStyleImportPlugin, ElementPlusResolve } from 'vite-plugin-style-import'

export default defineConfig({
  plugins: [
    createStyleImportPlugin({
      resolves: [ElementPlusResolve()]
    })
  ]
})

问题 4:vite-plugin-imagemin 安装失败

国内解决方案

bash 复制代码
npm install vite-plugin-imagemin -D --registry=https://registry.npmmirror.com

或在 package.json 中添加:

json 复制代码
{
  "resolutions": {
    "bin-wrapper": "npm:bin-wrapper-china"
  }
}

问题 5:分包后报初始化顺序错误

解决方案

  • 避免过于激进的 manualChunks(id) 字符串规则
  • 先使用对象分组法(更稳定)
  • 对高耦合库做"整组同包"

问题 6:构建后 echarts 样式异常

解决方案

  1. 使用完整版 echarts.js 而非按需引入版本
  2. 确保 echarts 实例不重复初始化
typescript 复制代码
onMounted(() => {
  if (!myChart) {
    myChart = echarts.init(container.value)
  }
  myChart.setOption(option)
})

onUnmounted(() => {
  myChart?.dispose()
  myChart = null
})

八、优化落地路线图

以下是一套可直接执行的优化路径,建议按顺序实施:

第一阶段:诊断与基线(1-2 天)

  1. 加入 rollup-plugin-visualizer,完成瓶颈定位并记录基线
  2. 使用 vite-plugin-inspect 检查插件链路

第二阶段:基础优化(2-3 天)

  1. 上线 manualChunks 分域策略 + 路由懒加载
  2. 落地 unplugin-vue-componentslodash-es 按需引入
  3. 启用 sourcemap: false + drop_console + gzip/br 压缩

第三阶段:进阶优化(3-5 天)

  1. 针对图片密集页引入压缩流程
  2. 评估是否需要 CDN external
  3. 配置构建缓存和依赖预构建

第四阶段:持续保障

  1. 把构建体积检查接入 CI,防止性能回退
  2. 定期使用 visualizer 检查新引入的依赖

总结

Vite 打包优化是一个系统工程,需要从多个维度综合考虑。本文介绍了三大优化方向:

构建速度优化 :依赖预构建、缓存配置、插件精简、HMR 优化等 10 个技巧,可使构建速度提升 30%-70%。 打包体积优化 :代码分割、Tree Shaking、按需引入等核心策略,可使包体积减少 60%-90%。 资源传输优化:Gzip/Brotli 压缩、图片压缩、CDN 加速等,可使传输体积进一步减少 50%-80%。

相关推荐
Highcharts.js2 小时前
在 React 中使用 useState 和 @highcharts/react 构建动态图表
开发语言·前端·javascript·react.js·信息可视化·前端框架·highcharts
梓言2 小时前
解决 Element Plus 中 Tooltip 样式影响全局菜单(Menu)及宽度控制失效的完美方案
前端·css·element
小蜜蜂dry2 小时前
css变量
前端·css
|晴 天|3 小时前
Vue 3 实战:打造可拖拽歌词、播放列表的嵌入式音乐播放器
前端·javascript·vue.js
Liu.7743 小时前
Vue 3 开发中遇到的报错(2)
前端·javascript·vue.js
jerrywus3 小时前
把 Obsidian 知识库接进 Claude Code:400 行代码实现 AI 长期记忆
前端·agent·claude
小t说说3 小时前
2026年PPT生成工具评测及使用体验
大数据·前端·人工智能
雨季mo浅忆3 小时前
第五项目梳理
前端·项目梳理
hERS EOUS3 小时前
WebSpoon9.0(KETTLE的WEB版本)编译 + tomcatdocker部署 + 远程调试教程
前端