一、时代背景决定架构差异
要理解 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 devWebpack 一般要走下面这套流程:
扫描文件
→ 建立依赖图
→ 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 devVite 首先做的事情很轻:
启动本地 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.jsVite 收到请求后,再逐个处理这些文件。
如果当前页面没有访问:
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.vueVite 会做这些事:
检测文件变化
→ 只重新编译当前文件
→ 判断影响边界
→ websocket 通知浏览器
→ 局部替换模块如果只是改了样式,可能连页面状态都不会丢。如果改的是组件逻辑,也只更新当前组件链路,不像传统全量重新构建那样成本高。
所以体验就是:保存后瞬间生效。这样就导致项目越大,Vite 优势越明显。
小项目:Webpack 启动 3 秒,而Vite 启动 1 秒。
差距一般。
大型后台系统:Webpack 启动 30 秒,而Vite 启动仅需要 2 秒。
九、总结
Webpack 是旧时代的完整解决方案。
Vite 是新时代利用浏览器原生能力的轻量方案。
Webpack 慢,是因为它启动时先把事情做完。
Vite 快,是因为它把事情延后再做。