说明:标题提到的Vite 4.0在本文写作时(2022年11月14日)尚未正式发布(Vite 4.0于12月发布),本文主要基于Vite 3.0的实战经验,并展望Vite 4.0的改进。

Vite是新一代前端构建工具,以快著称。但项目大了之后,Vite也会变慢。本文分享Vite项目的性能优化实战,包括开发环境优化、构建优化、运行时优化,以及常见问题和解决方案。

一、Vite为什么快

在说优化之前,先说说Vite为什么快。

1. 原生ESM

Vite利用浏览器原生的ES模块,开发时不需要打包。

  • 传统工具(Webpack):开发时要把所有模块打包成一个bundle,项目大了就慢
  • Vite:开发时直接用浏览器的ESM,按需编译,启动快

2. 按需编译

Vite只编译浏览器请求的模块。

  • 你访问哪个页面,就编译哪个页面的模块
  • 没访问的模块,不编译
  • 项目越大,优势越明显

3. 预构建

Vite用esbuild预构建依赖。

  • esbuild用Go写的,比JavaScript快10-100倍
  • 预构建第三方依赖,转换成ESM
  • 缓存预构建结果,下次启动更快

4. HMR

Vite的热更新(HMR)很快。

  • 只更新修改的模块
  • 不重新打包整个应用
  • 状态保持,刷新快

二、开发环境优化

开发环境的优化,主要是提升启动速度和HMR速度。

1. 依赖预构建优化

Vite会预构建依赖,但可以手动配置。

// vite.config.js
export default {
  optimizeDeps: {
    // 强制预构建这些依赖
    include: ['lodash', 'moment'],
    // 排除这些依赖(不要预构建)
    exclude: ['some-esm-package'],
  }
}

优化点:

  • 把常用的依赖加入include,避免运行时再编译
  • 把已经是ESM的依赖加入exclude,避免重复转换
  • 预构建结果会缓存,第一次慢,后面快

2. 减少入口文件

入口文件太多,会影响启动速度。

  • 尽量用一个入口
  • 多页应用,合理配置rollupOptions.input
  • 不要在入口文件里导入太多东西

3. 关闭不必要的功能

开发时,可以关闭一些不必要的功能。

export default {
  server: {
    // 关闭sourcemap(如果不需要)
    sourcemap: false,
  }
}

注意:sourcemap对调试很重要,除非确实不需要,否则不要关。

4. 用更快的依赖

有些依赖比较大或比较慢,可以替换。

  • 用dayjs代替moment(小很多)
  • 用lodash-es代替lodash(支持tree shaking)
  • 按需导入,不要全量导入
// 不好
import _ from 'lodash'

// 好
import { debounce } from 'lodash-es'

5. 合理配置别名

别名可以简化导入,但不要太多。

export default {
  resolve: {
    alias: {
      '@': '/src',
      '@components': '/src/components',
    }
  }
}

别名太多,会影响解析速度。保持简洁。

三、构建优化

构建优化,主要是提升构建速度和减小产物体积。

1. 代码分割

合理配置代码分割,减小首屏体积。

export default {
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          // 把第三方库单独打包
          vendor: ['vue', 'vue-router', 'pinia'],
          // UI库单独打包
          ui: ['element-plus'],
          // 工具库单独打包
          utils: ['lodash-es', 'dayjs'],
        }
      }
    }
  }
}

注意:

  • 不要过度分割,太多小文件反而慢
  • 合理分组,把不常变的放在一起
  • 用分析工具看效果

2. 压缩优化

Vite默认用esbuild压缩,也可以用terser。

export default {
  build: {
    // 用terser压缩(比esbuild压缩率高,但慢)
    minify: 'terser',
    terserOptions: {
      compress: {
        // 移除console
        drop_console: true,
        // 移除debugger
        drop_debugger: true,
      }
    }
  }
}

选择:

  • 追求构建速度:用esbuild(默认)
  • 追求产物体积:用terser

3. 图片优化

图片是产物体积的大头。

  • 用现代格式(WebP、AVIF)
  • 压缩图片
  • 懒加载
  • 用CDN

Vite插件:

import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'

export default {
  plugins: [
    ViteImageOptimizer({
      png: { quality: 80 },
      jpeg: { quality: 80 },
      webp: { quality: 80 },
    })
  ]
}

4. 按需导入

按需导入,减小产物体积。

  • UI库:用按需导入插件
  • 工具库:只导入需要的函数
  • 图标:只导入用到的图标
// 不好
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'

// 好
import { ElButton, ElInput } from 'element-plus'

5. 外部化依赖

如果用CDN,可以把依赖外部化。

export default {
  build: {
    rollupOptions: {
      external: ['vue', 'vue-router'],
    }
  }
}

注意:外部化需要在HTML里用CDN引入,适合有CDN的项目。

四、运行时优化

构建产物小了,运行时也要优化。

1. 路由懒加载

路由懒加载,只加载当前页面的代码。

const router = createRouter({
  routes: [
    {
      path: '/home',
      component: () => import('./views/Home.vue')
    },
    {
      path: '/about',
      component: () => import('./views/About.vue')
    }
  ]
})

2. 组件懒加载

大组件也可以懒加载。

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

const BigChart = defineAsyncComponent(() => import('./BigChart.vue'))
</script>

<template>
  <BigChart v-if="showChart" />
</template>

3. 虚拟列表

长列表用虚拟列表,只渲染可见区域。

  • 用vue-virtual-scroller
  • 或自己实现
  • 大量数据时,性能提升明显

4. 防抖节流

频繁触发的事件,用防抖节流。

import { debounce, throttle } from 'lodash-es'

// 防抖:搜索输入
const onSearch = debounce((value) => {
  // 搜索逻辑
}, 300)

// 节流:滚动事件
const onScroll = throttle(() => {
  // 滚动逻辑
}, 100)

5. 避免不必要的渲染

  • 用v-show代替v-if(频繁切换时)
  • 用computed缓存计算结果
  • 合理使用watch,不要过度监听
  • 大列表用key,且key稳定

五、分析工具

优化之前,先分析,找到瓶颈。

1. rollup-plugin-visualizer

分析产物体积。

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

export default {
  plugins: [
    visualizer({
      open: true,
      filename: 'stats.html',
    })
  ]
}

运行构建后,会打开一个可视化页面,看到每个模块的体积。

2. Vite的debug模式

看Vite的详细日志。

vite --debug

可以看到:

  • 预构建了哪些依赖
  • 每个请求的耗时
  • HMR的耗时

3. 浏览器开发者工具

用浏览器的Performance面板分析运行时性能。

  • 看加载时间
  • 看渲染时间
  • 看脚本执行时间
  • 找到瓶颈

六、常见问题

1. 启动慢

可能的原因:

  • 依赖太多,预构建慢
  • 入口文件太大
  • 别名太多
  • 插件太多

解决方案:

  • 优化依赖预构建
  • 减少入口
  • 精简别名和插件
  • 用更快的依赖

2. HMR慢

可能的原因:

  • 修改的文件影响范围大
  • 组件太大
  • 样式文件太多
  • 插件影响HMR

解决方案:

  • 拆分大组件
  • 合理组织代码
  • 检查插件是否影响HMR
  • 用Vite的debug模式看耗时

3. 构建慢

可能的原因:

  • 项目太大
  • 用了terser压缩
  • 图片太多太大
  • 插件太多

解决方案:

  • 用esbuild压缩(默认)
  • 优化图片
  • 精简插件
  • 升级硬件或用CI构建

4. 产物体积大

可能的原因:

  • 全量导入依赖
  • 图片没压缩
  • 没有代码分割
  • 没有按需导入

解决方案:

  • 按需导入
  • 压缩图片
  • 代码分割
  • 外部化依赖

七、Vite 4.0展望

Vite 4.0预计2022年12月发布,主要改进:

1. Rollup 3

Vite 4.0升级到Rollup 3。

  • 构建更快
  • 更好的tree shaking
  • 更好的代码分割
  • 修复了一些bug

2. 更快的冷启动

Vite 4.0优化了冷启动。

  • 更快的依赖扫描
  • 更好的缓存策略
  • 减少不必要的工作

3. 更好的HMR

HMR更稳定、更快。

  • 修复了一些HMR的bug
  • 更好的CSS HMR
  • 更快的更新

4. 其他改进

  • 更好的SSR支持
  • 更好的环境变量处理
  • 更多的配置选项
  • 文档改进

建议:Vite 4.0发布后,可以升级体验。

八、优化实战案例

分享一个我做过的优化案例。

项目情况:

  • 中后台系统,Vue 3 + Vite 3
  • 50多个页面,200多个组件
  • 启动时间:15秒
  • 构建时间:3分钟
  • 首屏体积:2MB

优化措施:

  1. 依赖预构建:把常用依赖加入include
  2. 按需导入:Element Plus按需导入
  3. 代码分割:按路由和依赖分组
  4. 图片优化:压缩图片,用WebP
  5. 路由懒加载:所有路由懒加载
  6. 大组件懒加载:图表组件懒加载
  7. 用esbuild压缩

优化结果:

  • 启动时间:15秒 → 5秒
  • 构建时间:3分钟 → 1分钟
  • 首屏体积:2MB → 500KB
  • 用户体验明显提升

九、写在最后

Vite很快,但项目大了也需要优化。

优化的思路:

  • 先分析,找到瓶颈
  • 开发环境:优化启动和HMR
  • 构建:优化速度和产物体积
  • 运行时:优化加载和渲染
  • 持续优化,不要一劳永逸

Vite 4.0即将发布,会带来更多改进。但不管工具怎么变,优化的思路是一样的:找到瓶颈,对症下药。

最后,用一句话总结:"Vite很快,但优化无止境。分析瓶颈,对症下药,让你的项目从慢到快。"

愿你的Vite项目,又快又稳。