Vite Or Webpack
KongHou

🔍 深入对比 Vite 与 Webpack:从底层机制看两种构建思路的分水岭

一、引言

前端构建工具的进化史,就是在提升开发体验与构建性能之间不断取舍的过程。
Webpack 曾经统治了前端工程化的整个时代,而 Vite 的出现,则几乎在一夜之间成为现代前端开发的新默认。

很多开发者知道:

  • Webpack 慢在打包,Vite 快在启动。
    但从底层机制上,为什么会慢、为什么能快
    这篇文章将从模块系统、依赖构建、HMR 原理等角度,带你看到两者的根本分歧。

二、核心理念的不同

对比维度 Webpack Vite
核心理念 一切皆模块(Bundle First) 原生 ESM 优先(No Bundle During Dev)
构建时机 启动时即打包所有模块 按需加载,首次访问才编译
开发服务器 自己实现的 HMR 系统 基于原生 ESM + HTTP
编译语言 Node + Loader + AST Node(预构建) + 浏览器原生 ES Module
HMR 实现 通过 WebSocket + 模块替换 + 全量依赖图 通过模块边界的精确更新

三、模块解析机制:打包与原生 ESM 的分歧

🧱 Webpack 的模块系统

Webpack 的设计出发点是:浏览器不支持模块化
因此,它需要:

  1. 解析依赖关系(基于 AST 分析 import / require);
  2. 打包所有依赖为一个或多个 bundle;
  3. 在浏览器运行时使用自定义模块系统__webpack_require__)模拟模块加载。

示意伪代码:

1
2
3
4
5
6
7
8
9
10
11
// Webpack 打包后生成的 runtime 代码(简化)
function __webpack_require__(moduleId) {
const module = { exports: {} };
modules[moduleId].call(
module.exports,
module,
module.exports,
__webpack_require__
);
return module.exports;
}

👉 缺点:启动前必须打包完所有依赖,即便只修改了一个文件,也要重新打包依赖链。


⚡ Vite 的模块系统

Vite 则利用了现代浏览器的原生能力:
浏览器已支持 **ES Module (ESM)**,可通过 <script type="module"> 直接导入模块。

因此,Vite 不再打包,而是:

  1. 直接将源文件路径转换为 URL
  2. 当浏览器请求某个模块时,Vite Dev Server 拦截请求
  3. 将源码通过 esbuild 实时转换为可执行的 ESM。

示例:

1
2
3
4
5
// 源码
import { add } from './utils.js';

// 浏览器请求 → Vite 返回转译结果
import { add } from '/src/utils.js?t=1693567890000';

👉 优势:启动无需打包,冷启动仅需毫秒级,访问时才按需编译。


四、依赖预构建机制:esbuild vs AST Bundling

在开发阶段,第三方依赖(例如 React、Lodash)通常是大量小模块,会带来成千上万次 ESM 请求
为此,Vite 使用了 依赖预构建(Dependency Pre-Bundling)

Vite 的预构建过程

  1. 检测裸模块导入(如 import React from 'react');
  2. 使用 esbuild(Go 实现)将其打包成单个 ESM;
  3. 缓存结果到 .vite/deps
  4. 后续请求直接走缓存。

这就是为什么首次启动时会看到 “pre-bundling dependencies” 的提示。

对比 Webpack

Webpack 则通过 loader + plugin + AST 打包机制 实现所有依赖的静态打包。
这种做法虽强大(支持 CommonJS、动态导入、polyfill 等),但在开发模式下极大拖慢了启动速度。


五、HMR(热模块替换)机制对比:模块热替换的两种哲学

🔥 Webpack HMR 流程

Webpack HMR 是在构建产物之上“热替换”模块内容,流程较复杂:

1
2
3
4
5
6
7
8
9
10
11
12
┌────────────┐
│ Webpack Dev Server │
└──────┬──────┘
│ WebSocket 通信

┌────────────┐
│ 浏览器端 HMR runtime │
│ (__webpack_hot__) │
└────────────┘


检测变更 → 请求更新 → 更新依赖图 → 替换模块 → 执行 accept 回调

示意代码(浏览器端):

1
2
3
4
5
if (module.hot) {
module.hot.accept('./App', () => {
render(App);
});
}
  • Webpack HMR 的本质是在打包产物内维护一张模块依赖图
  • 每次更新都要根据该依赖图重新构建 affected modules;
  • 最终通过 WebSocket 将增量更新推送到客户端。

⚠️ 代价:

  • 依赖图复杂、增量编译耗时;
  • 改动模块多时,HMR 会越来越慢。

⚡ Vite HMR 流程(基于原生 ESM)

Vite 的 HMR 不依赖打包产物,而是基于浏览器的 ESM 模块图

流程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
┌──────────────────────────────┐
│ Vite Dev Server │
│ (监控文件变化 + WS 通知) │
└─────────────┬────────────────┘


文件变动触发 → 发送 HMR 消息


┌──────────────────────────────┐
│ 浏览器端 HMR Runtime (ESM) │
│ 通过 import.meta.hot 接受更新 │
└─────────────┬────────────────┘


精确定位依赖边界 → 局部热替换模块 → 无需全量重编译

示例:

1
2
3
4
5
if (import.meta.hot) {
import.meta.hot.accept((newModule) => {
render(newModule);
});
}

执行过程:

  1. 文件变化 → Vite server 触发 ws.send({ type: 'update', path })
  2. 浏览器 runtime 收到消息;
  3. 判断该模块是否声明了 import.meta.hot.accept
  4. 若有 → 动态 import 新模块 → 执行回调;
  5. 若没有 → 向上冒泡 → 若父模块未接受 → 全局刷新。

👉 优势
Vite 不需要重新构建 bundle,仅重新请求变动模块的 URL。
这让它的 HMR 响应时间始终维持在毫秒级。


六、生产构建阶段:Vite 仍需回到 Rollup

虽然开发时 Vite 不打包,但在生产模式中,它依然会使用 Rollup 进行完整的打包优化:

  • Tree-shaking;
  • Lazy loading;
  • Code splitting;
  • 资源哈希化。

而 Webpack 的构建产物优化通过 TerserPlugin、SplitChunks、CSSMinimizerPlugin 等完成。
二者性能上各有千秋,但 Rollup 的输出更贴近 ESM 规范,体积通常更优。


七、总结:谁更强?

场景 推荐工具 原因
开发调试 Vite 极速启动、按需编译、HMR 轻盈
大型复杂项目(如微前端) ⚙️ Webpack 插件体系更成熟、生态广泛
生产构建 ⚖️ 持平 Vite 用 Rollup,Webpack 优化后性能接近
自定义构建流程 🧩 Webpack Plugin/Loader 机制灵活强大

八、结语

Webpack 是工程化时代的基石,而 Vite 是现代化开发体验的象征。
从底层实现上,Vite 的革命在于:

它不再与浏览器对抗,而是与浏览器协作。

Vite 通过利用原生 ES 模块、esbuild、HTTP 服务器与文件系统的天然配合,实现了几乎“零打包”的开发体验。
而 Webpack 的历史使命,则是为现代构建生态打下了所有的基础设施与抽象模型。

未来,两者仍会长期共存。
但对于多数现代前端项目而言,Vite 正在成为新的默认选项。

Powered by Hexo & Theme Keep
Total words 23.5k