Vite 为什么比 Webpack 快?

Vite 为什么比 Webpack 快?

一、时代背景决定架构差异

要理解 Vite 为什么快,先得明白 Webpack 为什么会出现

Webpack 并不是为了“打包而打包”,它解决的是那个年代浏览器能力不够的问题。

早些年前端开发没有现在这么舒服,浏览器不支持 ES Module,像下面这种写法根本跑不起来:

import Vue from 'vue'
import App from './App.vue'

浏览器既不认识 import,也不认识 .vue.scss.ts 这些文件。

所以那个时候必须依赖 Webpack 这类工具,把开发代码提前处理成浏览器能识别的代码。

Webpack 主要做的事情其实就这些:

  • 分析依赖关系

  • 把多个模块打成一个或多个 bundle.js

  • 把 ES6+ 转成低版本 JS

  • 处理 css、less、scss、图片等资源

  • 压缩代码

  • 生成 html 并自动注入 js 文件

也就是说,当时浏览器做不到的事,Webpack 全包了。

所以 Webpack 在那个时代是刚需,而且是非常先进的方案。


后来环境变了。

现代浏览器已经支持:

<script type="module">

也支持:

import './main.js'

这意味着浏览器自己已经具备模块加载能力了。

于是 Vite 的思路就出来了:

既然浏览器自己能加载模块,那开发环境为什么还要先把整个项目打包一遍?

所以 Vite 直接把“打包”这一步从开发阶段拿掉了。

浏览器请求哪个模块,我就处理哪个模块。

这就是它快的根本原因。


二、Webpack 为什么慢(开发环境)

假设一个项目有 2000 个模块。

执行:

npm run dev

Webpack 一般要走下面这套流程:

扫描文件
→ 建立依赖图
→ loader 转换
→ plugin 执行
→ chunk 拆分
→ bundle 输出到内存
→ 启动服务

很多人以为启动慢是 server 慢,其实不是。

真正耗时的是前面那一大串编译流程。

也就是说,哪怕你只是打开首页,Webpack 也得先把整个项目准备得差不多了,才肯启动服务。

所以大型项目经常出现这种情况:

启动 20 秒
改个文件热更新 3~5 秒

这很常见。


1、建立依赖图,很多项目卡在这里

Webpack 启动后,会从入口文件开始找依赖,比如:

src/main.js

然后解析里面的内容:

import App from './App.vue'
import router from './router'
import store from './store'

接着继续往下找:

main.js
├─ App.vue
│  ├─ Header.vue
│  └─ Home.vue
├─ router.js
└─ store.js

它会一直递归扫描,最后把整个项目整理成一张模块关系图。

这一步看起来简单,实际上挺重的。

因为不是看看文件名就完事了,它要做很多事:

  • 读取磁盘文件

  • 解析 import 路径

  • 处理 alias(比如 @/components)

  • 识别动态 import

  • 递归找子依赖

  • 去重

  • 缓存结果

如果项目文件很多,目录层级深,再加上 Windows 磁盘 IO 本身一般,这一步就容易卡住。

很多人见过终端一直停在:

building modules...

大概率就是这里。


2、loader 转换,最吃 CPU 的地方

Webpack 本身只认识 JS 和 JSON。

像这些文件它默认都不认识:

.vue
.ts
.scss
less
jsx
png
svg

所以必须交给 loader 处理。

比如 Vue 项目里一个 .vue 文件,通常要经过:

vue-loader
↓
ts-loader
↓
sass-loader
↓
postcss-loader
↓
css-loader

也就是说,一个文件可能被处理好几轮。

最终才变成浏览器能执行的 JS + CSS。

如果项目里有几百个组件,这个计算量就很大了。

尤其是:

  • TypeScript 编译

  • Sass 编译

  • Babel 转译

  • SourceMap 生成

这些都比较吃 CPU。

很多时候不是 Webpack 慢,是 loader 配太重了。


3、plugin 执行,最难控制的部分

loader 负责单文件处理。

plugin 负责整个构建生命周期。

Webpack 编译过程里有很多阶段:

initialize
compile
make
emit
done

插件可以插在任意阶段执行逻辑。

常见插件比如:

  • HtmlWebpackPlugin:生成 html

  • CopyWebpackPlugin:复制静态资源

  • DefinePlugin:注入环境变量

  • MiniCssExtractPlugin:抽离 css

问题在于,插件能做的事情太自由了。

它可能:

  • 扫描目录

  • 操作文件系统

  • 压缩资源

  • 重新遍历模块图

  • 生成分析报告

  • 做额外校验

所以插件一多,构建速度就很难预测。

很多老项目配置文件几百上千行,插件堆一堆,最后谁慢都说不清。


4、bundle 输出到内存

开发环境下,Webpack 一般不会真的生成 dist 文件夹。

比如理论上会输出:

dist/app.js
dist/index.html

但实际是放在内存里。

这样做的好处是避免频繁写磁盘,速度更快。

但要注意一点:

输出到内存之前,前面的活已经全干完了。

也就是说你看到服务迟迟起不来,不是写文件慢,而是前面这些流程慢:

  • 依赖图分析

  • loader 编译

  • plugin 执行

  • chunk 计算

  • hash 生成

  • runtime 注入

内存输出只是最后一步。


5、启动服务为什么反而最轻

很多人觉得 webpack-dev-server 启动慢。

其实启动 server 本身并不重。

流程通常是:

webpack compile 完成
↓
资源放入内存
↓
启动 express / websocket 服务
↓
监听端口
↓
浏览器访问

真正耗时的是 compile。

server 只是等前面全部干完再开门营业。


三、为什么 Vite 秒开

Vite 的思路完全不同。

启动时它不先扫描整个项目,也不先打完整 bundle。

它只做几件事:

  • 启动本地服务

  • 预构建 node_modules(通常用 esbuild)

  • 等待浏览器请求模块

当浏览器访问页面:

import App from './App.vue'

浏览器请求 App.vue,Vite 才去编译这个文件。

再 import Header.vue,再处理 Header.vue

谁被访问,处理谁。

所以启动非常快。

Vite 的启动流程大致可以理解为:

启动本地服务
→ 预构建 node_modules
→ 浏览器请求页面
→ 按需加载源码模块
→ 即时编译文件
→ HMR 精准更新

和 Webpack 最大区别就是:

Webpack:先全部准备好,再启动
Vite:先启动,用到什么再处理什么

1、启动本地服务(秒开)

执行:

npm run dev

Vite 首先做的事情很轻:

  • 启动本地 HTTP 服务

  • 创建文件监听器

  • 读取配置文件

  • 初始化插件系统

  • 准备模块转换能力

它不会像 Webpack 那样,一启动就扫描整个项目依赖图。

所以即使项目再大,命令执行后也能很快看到地址:

http://localhost:5173

这就是为什么很多人第一次用 Vite,会觉得它像没编译一样。

实际上它确实还没开始真正的大规模编译。

2、预构建 node_modules

在启动服务后,会发现并不是瞬间启动,一般会看到:

Optimizing dependencies...

这一步就是预构建依赖。

为什么要做这一步?

因为业务代码可以按需加载,但 node_modules 往往不是为浏览器直接运行准备的。

例如:

import _ from 'lodash'
import dayjs from 'dayjs'
import { ElButton } from 'element-plus'

这些包可能存在问题:

  • CommonJS 格式(浏览器不认识 require

  • 一个包拆成大量小文件

  • 多层嵌套依赖

  • 重复请求数量过多

所以 Vite 会先把依赖统一处理一次。

通常使用:

esbuild

把这些第三方依赖转换成浏览器友好的 ESM 格式,并做一定程度合并缓存。

比如:

lodash -> 单个可缓存模块
commonjs -> esm
多文件依赖 -> 减少请求数

这一步完成后,下次启动通常直接走缓存,就非常快。

3、浏览器请求页面

当在浏览器访问:

http://localhost:5173

浏览器先拿到 index.html

这点和 Webpack 也很不一样。

Vite 把 index.html 当作入口文件,而不是隐藏在内部配置里。

浏览器解析到:

<script type="module" src="/src/main.js"></script>

然后开始请求:

/src/main.js

这时候 Vite 才开始处理 main.js

注意这个时间点很关键:

不是启动时处理
而是浏览器请求时处理

这就是按需编译思想。

4、按需加载模块

如果 main.js 里写着:

import App from './App.vue'
import router from './router'

浏览器继续请求:

/src/App.vue
/src/router/index.js

Vite 收到请求后,再逐个处理这些文件。

如果当前页面没有访问:

UserCenter.vue
BigScreen.vue
Report.vue

那这些文件此时根本不会编译。

这和 Webpack 的区别非常大:

Webpack:项目文件先处理一遍
Vite:当前页面需要什么就处理什么

所以项目规模越大,这种优势越明显。

5、即时编译源码文件

浏览器请求到 .vue 文件时,浏览器自己并不认识。

例如:

<template>
  <div>Hello</div>
</template>

<script setup>
const msg = 'hi'
</script>

Vite 会临时调用对应插件处理。

比如 Vue 项目通常通过:

@vitejs/plugin-vue

.vue 文件转换成 JS 模块返回给浏览器。

TypeScript、JSX、CSS Modules 也是类似思路。

重点是:

请求一个文件,转换一个文件

不是全项目统一编译。

这就是为什么它响应很快。

6、HMR 为什么比 Webpack 更丝滑

修改一个组件:

src/components/Table.vue

Vite 会做这些事:

检测文件变化
→ 只重新编译当前文件
→ 判断影响边界
→ websocket 通知浏览器
→ 局部替换模块

如果只是改了样式,可能连页面状态都不会丢。如果改的是组件逻辑,也只更新当前组件链路,不像传统全量重新构建那样成本高。

所以体验就是:保存后瞬间生效。这样就导致项目越大,Vite 优势越明显。

小项目:Webpack 启动 3 秒,而Vite 启动 1 秒。

差距一般。

大型后台系统:Webpack 启动 30 秒,而Vite 启动仅需要 2 秒。


九、总结

Webpack 是旧时代的完整解决方案。

Vite 是新时代利用浏览器原生能力的轻量方案。

Webpack 慢,是因为它启动时先把事情做完。

Vite 快,是因为它把事情延后再做。

驾驶舱适配进阶:解决超宽屏留白问题 2026-04-16

评论区