面试题 1:Webpack 和 Vite 的核心差异是什么?
核心思路(一句话)
Webpack 开发模式以“构建模块依赖图 + Bundle”为核心;Vite 开发模式以“原生 ESM 按需加载 + 依赖预构建”为核心。
Webpack Dev 源码 ↓ 解析依赖 ↓ 构建 Module Graph ↓ Loader ↓ Plugin ↓ Bundle ↓ 浏览器Vite Dev 源码 ↓ 浏览器请求入口 ↓ 原生 ESM ↓ Vite 按需转换模块 ↓ 浏览器继续请求依赖所以真正的区别是:
开发阶段两者采用了不同的模块交付模型。
Vite生产构建仍然需要构建和优化产物。
面试题 2:Vite 的依赖预构建到底做了什么?
这是最重要的追问之一。
核心思路(一句话)
Vite 会在开发启动阶段识别node_modules中的依赖,对需要处理的依赖进行预构建,主要解决 CommonJS/UMD 兼容和大量模块请求问题,并缓存结果。
流程
node_modules ↓ 扫描依赖 ↓ 识别需要预构建的依赖 ↓ esbuild ↓ CommonJS / UMD 等 ↓ 转换 / 打包成浏览器可消费的 ESM ↓ 缓存 ↓ 浏览器直接请求预构建结果例如项目:
importlodashfrom'lodash';如果lodash的发布形式不适合浏览器直接作为 ESM 消费,Vite 会通过依赖预构建处理。
为什么一定要预构建?
主要有两个原因。
原因 1:CommonJS 兼容
例如:
constfoo=require('./foo');module.exports=foo;浏览器原生 ESM 不认识 CommonJS。因此需要:
CommonJS ↓ 预构建 ↓ ESM ↓ 浏览器原因 2:减少大量模块请求
假设某个依赖内部:
library ├── a.js ├── b.js ├── c.js ├── d.js ├── e.js └── ...如果全部按照原始模块交给浏览器,可能产生大量请求。
预构建可以把依赖处理成更适合开发环境消费的形式:
很多内部模块 ↓ 依赖预构建 ↓ 优化后的依赖产物 ↓ 浏览器Vite 会对依赖进行预构建和优化,具体输出形式由依赖结构和配置决定,不应该简单理解为所有依赖都被无条件打成一个文件。
面试题 3:Vite 是怎么把 CommonJS 转成 ESM 的?
核心思路
不是浏览器自己转换,而是 Vite 在开发服务器阶段借助依赖预构建工具链提前处理。
例如:
// CommonJSconstfoo=require('./foo');module.exports={foo};经过转换后,需要形成浏览器能够消费的 ESM 形式。可以抽象理解成:
CommonJS ↓ 静态分析 ↓ 识别 require / exports / module.exports ↓ 转换为 ESM 可消费形式 ↓ 缓存预构建结果但是有一个边界
CommonJS 本身允许:
constname=getName();require(name);这种动态依赖。这就无法像标准 ESM 那样天然进行完整静态分析。所以:
CommonJS → ESM 并不是简单的字符串替换,而是构建工具对 CommonJS 模块语义进行分析和兼容处理。
面试题 4:Webpack HMR 和 Vite HMR 底层到底有什么区别?
这是非常好的 30K+ 追问。
核心思路(一句话)
Webpack HMR 的核心是“重新编译受影响模块并通过 HMR Runtime 替换”;Vite HMR 的核心是“开发服务器只重新处理发生变化的模块,并通过 ESM 模块边界传播更新”。
Webpack HMR
修改 foo.js ↓ Webpack 监听文件变化 ↓ 重新编译受影响模块 ↓ 生成 HMR 更新信息 ↓ Dev Server 通知浏览器 ↓ 浏览器请求更新内容 ↓ Webpack Runtime ↓ 执行模块更新 ↓ HMR Accept / Dispose ↓ 页面局部更新所以不能简单说:
Webpack 改一个文件就“全量重新构建”。
实际是:
Webpack HMR 会尽可能只重新编译受影响的模块和相关依赖,但它仍然依赖 Webpack 的模块图和编译流水线。
Vite HMR
修改 foo.js ↓ Vite Dev Server ↓ 定位发生变化的模块 ↓ 重新转换 foo.js ↓ WebSocket 通知浏览器 ↓ 浏览器重新请求 foo.js ↓ ESM 模块重新执行 / HMR 边界处理 ↓ 页面更新文件变化 ↓ Vite Module Graph ↓ 找到受影响模块 ↓ 失效缓存 ↓ WebSocket 通知客户端 ↓ 客户端 HMR Runtime ↓ 沿依赖关系传播 ↓ 执行对应模块更新面试题 5:为什么 Vite HMR 通常更快?
核心矛盾
Webpack:
文件变化 ↓ 进入 Webpack 编译体系 ↓ 重新处理受影响模块 ↓ Loader / Plugin ↓ 生成更新结果 ↓ 浏览器Vite:
文件变化 ↓ 定位模块 ↓ 重新转换这个模块 ↓ 浏览器重新请求因此项目越大:
Webpack 依赖图 + 构建流程 ↓ 变化模块处理成本 Vite 开发环境 ↓ 变化模块按需处理大型项目中两者的开发体验差异会被放大。
面试题 6:Vite 按需加载几百个模块,会不会产生请求瀑布?
核心思路
会增加浏览器模块请求数量,但“几百个请求 = 必然严重瀑布”是不准确的。
浏览器加载:
index.html ↓ main.js ↓ A.js B.js C.js ↓ A1.js A2.js B1.js ...如果模块之间存在深层依赖:
A ↓ B ↓ C ↓ D确实可能形成请求链。
但现代浏览器:
- HTTP/2 多路复用
- HTTP/3
- 浏览器缓存
- 并发请求
- Vite 依赖预构建
都会降低大量模块请求的实际成本。
所以不能说:
Vite 靠 HTTP/2 解决了请求瀑布。
更准确:
HTTP/2/HTTP/3 可以降低大量模块请求的连接与队头阻塞成本,但无法从根本上消除存在依赖关系时的请求链。
面试题 7:为什么 Vite 开发环境可以接受很多模块请求?
因为它优化的是:
开发阶段的反馈速度,而不是生产环境最终网络交付效率。
这是 Vite 很重要的架构取舍。
Vite │ ┌─────────┴─────────┐ ↓ ↓ 开发环境 生产环境 ↓ ↓ 原生 ESM 按需加载 构建优化 ↓ ↓ 快速启动/HMR Bundle / Chunk ↓ Tree Shaking ↓ Code Splitting ↓ 浏览器生产加载因此:
Vite 开发环境和生产环境本来就不是完全相同的模块交付方式。
面试题 8:为什么 Vite 生产环境还需要打包?
因为生产环境目标变了。
开发环境:
目标: 快速启动 快速 HMR 快速反馈生产环境:
目标: 减少请求 优化代码 Tree Shaking Code Splitting 压缩 缓存 资源优化因此生产构建需要:
源码 ↓ Module Graph ↓ 静态分析 ↓ Tree Shaking ↓ Code Splitting ↓ Chunk ↓ Minify ↓ Production AssetsVite 的生产构建长期以来基于 Rollup;现代 Vite 版本的底层实现持续演进,因此面试时不要死记成:
“Vite = Rollup”。
更准确:
Vite 是开发服务器 + 构建工具体系,生产构建使用 Rollup 生态进行构建优化,并且其底层实现会随版本演进。
面试题 9:Vite 为什么要采用“开发和生产两套模型”?
核心思路
用开发环境的按需 ESM 换取开发速度,再用生产构建解决最终交付性能。
开发阶段 原生 ESM ↓ 启动快 ↓ HMR 快 ↓ 开发体验好 生产阶段 完整构建 ↓ Tree Shaking ↓ Code Splitting ↓ Chunk 优化 ↓ 压缩 ↓ 缓存这是一种典型的:
开发体验和生产交付效率分别优化。
面试题 10:Webpack 为什么没有采用 Vite 这种开发模式?
这题不要回答成:
“Webpack 老了。”
真正原因是架构模型不同。
Webpack 从设计之初就是:
模块 ↓ Module Graph ↓ 统一编译 ↓ Bundle大量能力建立在这个统一构建模型之上:
Loader Plugin Chunk Runtime Module Graph Tree Shaking Code Splitting HMR而 Vite 开发模式更加依赖:
浏览器原生 ESM + 开发服务器按需转换 + 依赖预构建 + Module Graph所以两者是:
Webpack “先构建,再交付” Vite Dev “浏览器请求什么,我处理什么”面试题 11:如果项目有几千个模块,Vite 开发环境是不是一定比 Webpack 好?
不能绝对化。
应该分析:
项目规模 + 模块依赖深度 + CommonJS 数量 + 插件复杂度 + 浏览器环境 + HMR 场景 + 开发机性能特别是:
大量深层 ESM 依赖 ↓ 浏览器请求链增加 ↓ 可能出现模块请求开销而 Webpack:
开发阶段提前构建 ↓ 浏览器拿到 Bundle / Chunk ↓ 请求数量较少因此两者实际上是:
| Webpack Dev | Vite Dev | |
|---|---|---|
| 核心模式 | Bundle | Native ESM |
| 启动 | 构建后启动 | 按需提供 |
| HMR | 编译 + HMR Runtime | 模块失效 + ESM HMR |
| CommonJS | Loader/构建体系处理 | 依赖预构建 |
| 浏览器请求 | 相对少 | 开发阶段可能更多 |
| 生产 | Bundle | Production Build |
最重要的底层架构图
把这道题最终压缩成这一张图:
Webpack vs Vite │ ┌──────────────┴──────────────┐ ↓ ↓ Webpack Vite │ │ Bundle-first ESM-first │ │ ↓ ↓ Module Graph Browser ESM │ │ Loader + Plugin 按需转换模块 │ │ ↓ ↓ Bundle / Chunk Dependency Pre-bundle │ │ ↓ ↓ Browser Browser │ ↓ HMR最后:面试官真正想听什么?
如果面试官问:
“Webpack 和 Vite 核心差异是什么?”
不要只说:
Vite 快,因为原生 ESM。
直接这样回答:
Webpack 和 Vite 最大的区别不是性能参数,而是开发阶段的构建模型。Webpack 以构建 Module Graph 和 Bundle 为核心,文件变化后需要经过构建体系重新处理受影响模块,再通过 HMR Runtime 更新;Vite 开发环境则利用浏览器原生 ESM,源码模块按需转换和交付,同时通过依赖预构建解决 CommonJS 和大型依赖的开发性能问题。
所以 Vite 的优势来自“把开发阶段的 Bundle 工作尽量推迟或减少”,而不是简单地“不打包”。
但这也带来一个架构取舍:开发阶段浏览器可能面对更多模块请求,因此 Vite 在生产环境仍然需要完整构建,通过 Tree Shaking、Code Splitting、Chunk 优化和压缩等手段生成适合生产交付的资源。
如果继续往下追,我会重点从三个方向展开:依赖预构建如何处理 CommonJS、HMR 如何通过 Module Graph 精确定位更新,以及开发阶段原生 ESM 和生产 Bundle 之间为什么必须存在这种差异。
这道题真正要背的只有 6 句话
1. Webpack:Bundle-first。 2. Vite Dev:Native ESM-first。 3. Vite 不是“不打包”,而是开发阶段尽量避免全量 Bundle。 4. 依赖预构建:主要解决依赖兼容、请求数量和缓存问题。 5. HMR:Webpack 依赖构建体系 + HMR Runtime;Vite 依赖 Dev Server + Module Graph + ESM HMR。 6. Vite:开发追求反馈速度,生产再通过完整构建追求最终交付性能。