出处:掘金

原作者:前端微白


在开发过程中节省的每一秒,都是对开发体验的重大提升 —— 尤雨溪(Vite 作者)

速度对比:直观的性能差异

先看一组实际项目的真实数据:

指标Webpack 5 (dev-server)Vite 5差距倍数
冷启动时间12.8s0.8s16x
CSS 更新延迟350±50ms20±5ms17x
JS 模块热替换延迟1800±300ms45±10ms40x
内存占用峰值1.2GB320MB4x
热更新网络传输量整个 chunk (≈300KB)单个模块(≈5KB)60x

在大型项目中,这种差异会更加明显,Vite 的冷启动速度可以达到 Webpack 的 50-100 倍,HMR 更新快 50-300 倍!

经典面试题:与理论不同,我的项目中为什么 Vite 反而比 Webpack 慢?

答:Vite 会发起很多 HTTP 请求,HTTP/1.1 有队头阻塞问题,升级到 HTTP/2 解决

详见 HTTP 各版本差异

核心原理

Webpack 的打包方式

graph LR
A(入口文件) --> B(解析依赖)
B --> C(构建完整依赖图)
C --> D(打包整个应用)
D --> E(启动开发服务器)
E --> F(浏览器加载整个 bundle)

这种全部打包再服务的模式导致:

  • 启动时:必须处理所有模块才能提供服务
  • 更新时:即使微小改动也要重新构建大部分模块

Vite 的按需编译模式

graph TB
A(浏览器请求) --> B(Vite服务器)
B --> C{是否已编译?}
C --是--> D(返回缓存)
C --否--> E(按需编译)
E --> F(返回单文件)
F --> G(浏览器执行 ESM)

这种模式的创新之处在于:

  1. 启动时:仅启动开发服务器,零打包
  2. 请求时:按需编译单个文件
  3. 更新时:仅编译修改的文件及直接依赖

性能优化对比:Webpack VS. Vite

冷启动过程分析

Webpack

sequenceDiagram
participant 开发者
participant Webpack
participant 浏览器
开发者 ->> Webpack: 运行 dev 命令
Webpack ->> Webpack: 构建完整依赖图
Webpack ->> Webpack: 打包全部模块
Webpack ->> Webpack: 创建 bundle 文件
Webpack ->> 浏览器: 发送完整 bundle
浏览器 ->> 浏览器: 解析执行 bundle
浏览器 -->> 开发者: 页面加载完成

Vite

sequenceDiagram
participant 开发者
participant Vite
participant 浏览器
开发者 ->> Vite: 运行 dev 命令
Vite ->> 浏览器: 立即返回 HTML 骨架
浏览器 ->> Vite: 请求 /src/main.js
Vite ->> Vite: 编译 main.js
Vite ->> 浏览器: 返回编译后 main.js
浏览器 ->> Vite: 请求依赖模块
Vite ->> Vite: 按需编译依赖
Vite ->> 浏览器: 返回按需编译模块
浏览器 -->> 开发者: 页面逐步加载

热更新 (HMR) 对比

Webpack

基于 Bundle 的级联更新

graph TB
A(文件修改) --> B(Webpack 检测变更)
B --> C(重建依赖图谱)
C --> D(增量编译模块)
D --> E(生成补丁文件)
E --> F(通过 WebSocket 推送)
F --> G(客户端执行 HMR 运行时)
G --> H(动态替换模块)

性能瓶颈:依赖图谱越大,重建依赖图谱 → 增量编译模块 → 生成补丁文件阶段耗时指数级增长(实测 1000 模块项目平均耗时 1.8s)

// 典型 webpack HMR 处理流程
compiler.hooks.done.tap('HMRPlugin', (stats) => {
  const changedModules = Array.from(stats.compilation.modifiedModules);
  const chunks = changedModules.map(module => 
    Array.from(module.chunks).map(chunk => chunk.id)
  );
  server.sendMessage(client, { type: 'update', chunks });
});

核心缺陷:修改一个文件需重新计算整个依赖链

Vite

基于 ESM 的按需编译

  • ES Module 是编译时静态加载(import/export),支持 Tree-shaking
  • CommonJS 是运行时动态加载(require/module.exports),无法静态优化
graph TB
A(文件修改) --> B(Vite 拦截请求)
B --> C{判断文件类型}
C --源文件--> D(单文件编译)
D --> E(通过 WebSocket 推送更新)
E --> F(浏览器重新发起模块请求)
F --> G(返回新编译结果)
C --依赖文件--> H(返回预构建缓存)

性能密钥:跳过依赖图谱遍历,单文件编译速度比 Webpack 快 10x(实测 1000 模块项目耗时 <100ms)

// Vite 的 HMR 边界处理(伪代码)
function handleHotUpdate({ modules }) {
  const updates = modules.map(mod => ({
    type: 'js-update',
    path: mod.url,
    timestamp: Date.now()
  }));
  ws.send(updates);
}
 
// 浏览器的动态加载
import(`/src/component.js?t=${Date.now()}`).then(newModule => {
  newModule.render.applyUpdate();
});

突破点:每个模块是独立网络请求,无级联更新

关键技术深度解析

原生 ES 模块 (ESM) 的运用

Vite 直接利用浏览器原生支持的 ES 模块系统:

<!-- index.html -->
<script type="module" src="/src/main.js"></script>
<!-- main.js -->
import { createApp } from 'vue'
import App from './App.vue' // 浏览器直接请求 App.vue 文件
 
createApp(App).mount('#app')

优势:

  • 零打包启动:开发服务器即时启动
  • 按需加载:仅编译当前屏幕需要的模块
  • 高效缓存:浏览器缓存未更改的模块

闪电般的依赖预构建

Vite 使用 Go 编写的 esbuild 处理依赖预构建:

// vite.config.js
export default {
  optimizeDeps: {
    // 需要预构建的依赖项
    include: ['react', 'react-dom', 'lodash-es']
  }
}

esbuild 的优势:

  • 用 Go 编写,直接编译为本地机器码
  • 并行处理,利用多核 CPU
  • 比 JavaScript 构建工具快 10-100 倍
工具处理速度语言并发支持
esbuild极快Go
Babel中等JS有限
TerserJS有限

高效的热模块更新 (HMR)

Webpack 的 HMR 瓶颈:

  • 需要重建整个模块图
  • 更新速度随项目增长而下降

Vite 的 HMR 优化:

// Vite 的 HMR API
import.meta.hot.accept(['./dep.js'], ([newDep]) => {
  // 当 dep.js 更新时执行
  updateComponent(newDep);
});

核心优化:

  • 基于 ESM:精确的边界更新
  • 只使修改的模块失效
  • 利用浏览器缓存未更改模块
  • 更新传播时间与项目大小无关

基于路由的异步拆分

生产构建时,Vite 使用 Rollup 进行优化:

// 自动代码拆分示例
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        // 自动代码分割策略
        manualChunks(id) {
          if (id.includes('node_modules')) {
            return 'vendor';
          }
        }
      }
    }
  }
})

构建优化特点:

  • 预配置优化的 Rollup 构建
  • 更快的源映射生成
  • 更智能的代码拆分策略
  • 支持 WebAssembly 和 Web Workers

何时选择 Vite 或 Webpack?

场景Vite 优势Webpack 优势
新项目启动✅ 极快⚠ 慢
大型项目开发体验✅ HMR极快⚠ HMR变慢
复杂自定义构建⚠ 有限制✅ 高度灵活
需要兼容旧浏览器⚠ 需插件✅ 开箱即用
微前端架构✅ 原生支持⚠ 复杂配置
需要大量 loader/plugin⚠ 生态年轻✅ 成熟生态

Vite 优化配置实战

加速依赖预构建

// vite.config.js
export default {
  optimizeDeps: {
    // 强制提前预构建
    include: ['lodash-es', 'axios'],
    
    // 排除不需要预构建的
    exclude: ['@monorepo/shared'],
    
    // 启用性能监听
    plugins: [visualizer()]
  },
  build: {
    // 使用更快的压缩工具
    minify: 'esbuild'
  }
}

提升 HMR 性能

// 自定义 HMR 处理
if (import.meta.hot) {
  import.meta.hot.accept('./math.js', (newModule) => {
    console.log('Math module updated:', newModule);
    // 执行精确更新逻辑
  });
}

优化生产构建

export default {
  build: {
    // 更快的打包输出
    target: 'esnext',
    
    // 开启 gzip 压缩
    brotliSize: true,
    
    // 移除 console
    terserOptions: {
      compress: { drop_console: true }
    }
  }
}

Vite 的进化方向

  1. Rust 编译工具链替代:Rolldown(Rust 版 Rollup)HMR 速度再提 50%
  2. Lightning CSS 集成:替代 PostCSS 的 Rust 实现
  3. 全局 CSS 优化:改进 CSS 代码分割
  4. 服务端渲染增强:更快的 SSR 构建
  5. Wasm 优化:原生 WebAssembly 支持
  6. 插件标准化:统一 Rollup 和 Vite 插件

小结

Vite 的高性能源于几个根本性创新:

  1. 拥抱浏览器标准:直接使用 ESM
  2. 按需编译:不打包不编译不需要的代码
  3. 原生性能工具:利用 esbuild 等非 JS 工具突破性能瓶颈
  4. 精确更新策略:HMR 只需处理变更影响的最小范围

“Webpack 是打包优先的,而 Vite 是服务器优先的。这就是性能差异的根本原因”

迁移建议:

  • 新项目首选 Vite
  • 大型现有项目逐步迁移
  • 依赖特殊 Webpack 插件的暂缓

Vite 代表了前端工具链的未来方向 - 通过利用现代浏览器特性和原生编译工具,实现了从量变到质变的开发体验跃升