Vite 原理与性能优势

Vite 的工作原理及为什么比 Webpack 启动快

问题

Vite 的工作原理是什么?为什么它的开发服务器启动速度比 Webpack 快很多?

解答

Webpack 的问题

Webpack 在开发模式下需要先打包整个应用,再启动开发服务器:

入口文件 → 分析依赖 → 打包所有模块 → 启动服务器 → 可访问

项目越大,启动越慢。

Vite 的做法

Vite 利用浏览器原生 ESM 支持,跳过打包步骤:

启动服务器 → 可访问 → 按需编译请求的模块
// 浏览器直接请求 ESM 模块
// index.html
<script type="module" src="/src/main.js"></script>

// main.js - 浏览器发起请求
import { createApp } from 'vue'  // 请求 /node_modules/.vite/vue.js
import App from './App.vue'       // 请求 /src/App.vue

createApp(App).mount('#app')

三个关键技术

1. 原生 ESM

Vite 让浏览器自己处理模块加载:

// Vite 开发服务器返回的代码
// 浏览器看到 import 语句会自动发起新请求
import { ref } from '/@modules/vue'
import Comp from '/src/components/Comp.vue?t=123456'

2. 按需编译

只编译当前页面用到的模块:

// Vite 中间件伪代码
async function transformMiddleware(req, res) {
  const url = req.url
  
  // 只有请求到达时才编译
  if (url.endsWith('.vue')) {
    const code = await fs.readFile(url)
    const result = await compileSFC(code)  // 编译 Vue 单文件组件
    res.send(result)
  }
  
  if (url.endsWith('.ts')) {
    const code = await fs.readFile(url)
    const result = await esbuild.transform(code, { loader: 'ts' })
    res.send(result.code)
  }
}

3. Esbuild 预构建

用 Go 编写的 Esbuild 处理 node_modules,速度是 JavaScript 打包工具的 10-100 倍:

// vite.config.js
export default {
  optimizeDeps: {
    // 预构建配置
    include: ['lodash-es', 'axios'],  // 强制预构建
    exclude: ['my-local-package']      // 排除预构建
  }
}

预构建做两件事:

// 1. 将 CommonJS 转为 ESM
// node_modules/lodash/index.js (CommonJS)
module.exports = { debounce, throttle }

// 转换后 → .vite/lodash.js (ESM)
export { debounce, throttle }

// 2. 合并小模块,减少请求数
// lodash-es 有几百个文件,预构建合并成一个
import { debounce } from 'lodash-es'
// 只需要一个请求,而不是几百个

对比示意

Webpack 开发模式:
┌─────────────────────────────────────────────────┐
│  启动时打包全部模块 (可能几十秒)                    │
│  entry → moduleA → moduleB → ... → bundle.js    │
└─────────────────────────────────────────────────┘

Vite 开发模式:
┌──────────────┐
│  启动服务器    │  (几百毫秒)
└──────────────┘

┌──────────────┐
│  请求 main.js │ → 编译 main.js
└──────────────┘

┌──────────────┐
│  请求 App.vue │ → 编译 App.vue
└──────────────┘

      ...按需继续

HMR 也更快

// Webpack HMR: 重新打包受影响的模块链
// 改动 utils.js → 重新打包 utils.js + 所有依赖它的模块

// Vite HMR: 只更新改动的模块
// 改动 utils.js → 只发送 utils.js 的新内容
// 浏览器通过 ESM 动态替换

关键点

  • 原生 ESM:利用浏览器原生模块系统,无需打包,直接请求源文件
  • 按需编译:只编译当前页面请求的模块,未访问的代码不处理
  • Esbuild 预构建:用 Go 编写,将 node_modules 转换为 ESM 并合并,速度极快
  • 启动时间与项目大小无关:不管项目多大,启动都是毫秒级
  • 生产环境仍用 Rollup:开发用 ESM,生产打包保证兼容性