假设你走进一家传统酒楼,服务员刚记下你点的第一道菜,后厨却已经开始按照整本菜单把所有菜都做一遍——包括隔壁桌还没点的、甚至今晚根本不会有人点的菜。等这满满一后厨的菜全部烧好,你的第一道菜才终于端上来。这就是使用 Webpack 开发时的真实体验:启动一个大型项目,要先把整个依赖图完整打包一遍,往往要等几十秒甚至几分钟。 而 Vite 的做法,像极了一家日式板前料理店。客人落座,师傅当面料理,你点一道,他做一道。上菜快如闪电,因为根本不需要把整本菜单提前做完。Vite 在开发时压根不打包你的源码,而是让浏览器直接通过原生 ESM(ECMAScript Module)按需加载模块,谁被请求到,谁才被实时转换。再加上用 Go 语言写的 esbuild 对依赖做预构建,用精准的 HMR 只更新改动的模块——最终成就了「秒开」的开发体验。 这篇文章,就要掰开揉碎地讲清楚 Vite 到底快在哪里、为什么能这么快,以及它和 Webpack 在生产环境上的角色分工。

岗位关联

对 AI 从业者而言,Vite 的按需编译、缓存复用、精准更新思想,与 AI 推理服务的懒加载、缓存命中、增量更新如出一辙。弄懂 Vite,你就掌握了现代 AI 基建中「用最小开销服务最大规模」的通用思维。

目录
  1. 可视化演示
  2. 开发服务秒启
  3. 依赖预构建
  4. 按需编译
  5. 插件机制
  6. HMR热更新
  7. 生产构建
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示Vite开发服务器ESM原生加载、Rollup生产打包、HMR热更新与依赖预构建优化,切换模式理解现代构建工具核心原理。

本节配有交互动效(进入视口后自动加载,约 3KB)。

Vite开发服务器与ESM原生加载 浏览器直接请求ESM模块,Vite拦截请求按需编译单文件,省去打包步骤实现毫秒级冷启动 依赖预构建用esbuild将CJS/UMD转为ESM并合并小模块,源码按需编译通过中间件拦截编译返回 browser → Vite middleware → compile(mod) → ESM response ESM原生加载让开发服务器免打包,按需编译极大提升启动速度

开发服务秒启

传统酒楼的后厨,接到一桌客人的点单后,会把整本菜单上的菜全部起锅。Webpack 启动开发服务器时也是同样的逻辑:从入口文件出发,递归地解析每一个 import,构建出全量依赖图,再对所有模块做转换和打包。项目越大,要处理的模块越多,等待时间就越长。这就解释了为什么老项目打开 dev server 要喝一杯咖啡才缓过神来。

而 Vite 的板前料理哲学是:客人没点的菜,绝不动刀。它启动时压根不做全量打包,只做一件事——把开发服务器跑起来。浏览器发出请求时,Vite 才实时转换单个文件并返回。所以 Vite 的冷启动速度几乎不随项目规模增长而衰减,始终稳在百毫秒级。

启动耗时可以这样对比:

这份快,不是哪个环节做了魔法级的优化,而是「什么都不做」本身带来的速度。板前师傅的高明,恰恰在于他敢于不提前备菜——因为他信任自己手上的刀够快。

Tvite=C,Twebpack=∑i=1nti\displaystyle T_{vite} = C, \quad T_{webpack} = \sum_{i=1}^{n} t_i
前者是一个常数(启动服务器本身的开销);后者是全部 n 个模块的编译时间之和。当 n 很大时,两者差距极大。
  1. 常量项$T_{vite} = C$ 表示 Vite 的启动时间是个固定常数,只与启动服务器的基础开销有关,与你项目里有多少个文件无关。
    Tvite=C\displaystyle T_{vite} = C
  2. 求和项$\displaystyle \sum_{i=1}^{n} t_i$ 表示 Webpack 要花费的时间等于把第 1 个到第 n 个模块全部处理一遍的耗时总和。
    ∑i=1nti\displaystyle \sum_{i=1}^{n} t_i
  3. 对比逻辑当项目模块数量 n 增大时,$T_{webpack}$ 线性增长,而 $T_{vite}$ 始终不变,这就是「秒开」的本质来源。
    Tvite≪Twebpack\displaystyle T_{vite} \ll T_{webpack}

依赖预构建

板前料理无须提前准备本地食材,但有一类东西必须提前处理:从国外进口的冷冻牛蒡、冰鲜鱼块。它们又硬又韧,没法直接切给客人看,必须提前解冻、切成细丝、分装小碟。Vite 眼中的 node_modules 依赖,就是这种「进口冷冻食材」。

依赖为什么难处理?第一,很多第三方库是老牌的 CommonJS 格式,浏览器完全看不懂;第二,有的包内部有几十个相互引用的子文件,如果直接交给浏览器,一个 import 可能触发上百次请求,把网络打爆。所以 Vite 在启动时用 esbuild 做依赖预构建:把所有 CJS 依赖转成 ESM 格式,并把内部有大量子模块的依赖「合并」成少数几个预打包文件。

esbuild 用 Go 语言编写,采用多核并行 + 原生编译,打包速度比传统 JS 打包器快 10 到 100 倍。预构建的产物还会被缓存起来,只要依赖的锁文件不变,下次启动直接复用。

简单说,Vite 聪明地把依赖处理这件事外包给了一个「专业切菜机器人」,它几下就把所有冻肉处理成了规整的薄片,等待客人随时点单。

Sspeedup=TjsTgo≈10∼100\displaystyle S_{speedup} = \frac{T_{js}}{T_{go}} \approx 10 \sim 100
表示 esbuild 相比传统 JS 工具链的加速比,量级上是几十倍到上百倍。
  1. 分子项$T_{js}$ 代表传统 JavaScript 打包器处理同样依赖所需的时间。
    Tjs\displaystyle T_{js}
  2. 分母项$T_{go}$ 代表 esbuild(Go 语言实现)处理同样任务所需的时间。
    Tgo\displaystyle T_{go}
  3. 比值含义$S_{speedup} \approx 10 \sim 100$ 说明预构建这步用了快一个数量级以上的工具,把「备菜」成本压到几乎可以忽略。
    Sspeedup≈10∼100\displaystyle S_{speedup} \approx 10 \sim 100

按需编译

在日料店里,客人点一份金枪鱼中腹,师傅才会拿起那块鱼肉;点一份玉子烧,才会去热锅。Vite 对源码的处理,完全遵循这个「点单式」节奏。

当浏览器执行 import 语句时,会向 Vite 开发服务器发起 HTTP 请求。Vite 接收到请求后,才对该文件做必要转换:把 TypeScript 编译成 JavaScript、把 Vue/JSX 模板编译成函数、把 CSS 转成一个可注入的模块。转换完立刻返回给浏览器,整个过程只涉及这一个文件,其他文件完全不碰。这就是「按需编译」:源码不打包、请求即转换、浏览器原生解析。

这带来惊人的效果:哪怕项目里有一千个模块,页面首屏只需要十几个模块,Vite 就只编译这十几个。换算成时间感受:

另外,Vite 还会把转换结果放进内存缓存。同一个文件再次被请求时,直接走缓存,连转换都省了。这就像师傅记住你上次点的口味,第二次直接按记忆出菜,连刀都不用动。

Tload=∑j∈Rtj,R⊂G\displaystyle T_{load} = \sum_{j \in R} t_j, \quad R \subset G
其中 R 是页面实际请求的模块集合,G 是全量模块集合。Vite 只编译 R 中的模块,因此加载总时间大幅压缩。
  1. 子集符号$R \subset G$ 表示浏览器实际请求的模块集合 R 只是全部模块 G 的一个小部分。
    R⊂G\displaystyle R \subset G
  2. 求和范围$\displaystyle T_{load} = \sum_{j \in R} t_j$ 说明总加载时间只取决于那些真正被请求的模块,而不是所有模块。
    Tload=∑j∈Rtj\displaystyle T_{load} = \sum_{j \in R} t_j
  3. 对比含义因为 $|R| \ll |G|$,所以按需编译的总耗时远小于全量编译的耗时,这正是页一面就秒开的原因。
    ∣R∣≪∣G∣\displaystyle |R| \ll |G|

插件机制

一家日料店的师傅总有自己的独门酱料配方:同一条鱼,用柚子胡椒还是山葵酱油,风味完全不同。Vite 的插件体系,就是一套「配方管理系统」,让框架和开发者能自定义每种文件的处理方式。

在 Vite 的世界里,.vue 文件、.tsx 文件、.scss 文件都算「特殊食材」,浏览器不认。插件负责把这些食材加工成浏览器听得懂的 ESM 格式。Vite 的插件机制继承自 Rollup 生态,核心是一组钩子函数,构成一条转换管道:

插件钩子除了 transform(转换源码),还有 resolveId(解析模块路径)、load(加载模块内容)等。Vue、React 等框架的 Vite 插件,本质上就是一套为自家「食材」定制的完整酱料方案。师傅的配方越多,能驾驭的食材就越广,板前的菜单也就越丰富。

Eout=Tpk(⋯Tp2(Tp1(Fin)))\displaystyle E_{out} = T_{p_k}\left( \cdots T_{p_2}\left( T_{p_1}\left( F_{in} \right) \right) \right)
源文件 $F_{in}$ 依次经过插件 $p_1, p_2, \dots, p_k$ 的 transform 钩子处理,最终输出标准 ESM 模块 $E_{out}$。
  1. 输入文件$F_{in}$ 是被请求的原始源文件,比如一个 .vue 单文件组件。
    Fin\displaystyle F_{in}
  2. 嵌套转换$T_{p_1}\left( F_{in} \right)$ 表示第一个插件先处理文件;$T_{p_2}$ 再处理第一个插件的结果,层层嵌套。
    Tp2(Tp1(Fin))\displaystyle T_{p_2}\left( T_{p_1}\left( F_{in} \right) \right)
  3. 最终输出$E_{out}$ 是经过所有插件处理后的最终 ESM 模块,浏览器可以原生执行。
    Eout\displaystyle E_{out}

HMR热更新

客人正在用餐,突然发现刚才那道烤银杏果凉了。日料师傅不会把整桌菜撤下去重做——他只是夹起那一碟,回炉热十秒,再送回客人面前。这就是 HMR(Hot Module Replacement,热模块替换)的哲学:动最小手术,解决最痛的问题。

Vite 中,每个模块都可以声明自己的「更新边界」。当源码文件变化时,开发服务器通过 WebSocket 通知浏览器:哪些模块变了。浏览器沿着依赖图找到变化模块的边界,只重新请求并执行这些模块,页面其他部分原封不动。表单里填了一半的输入框、滚动到一半的页面位置、弹窗的开合状态,全都保留——因为整个页面根本没有刷新。

精确到模块的更新极大地降低了反馈延迟:

你改一行 CSS,浏览器 50 毫秒内就更新了样式;你改一个组件逻辑,只有那个组件的实例被重新挂载。这比传统酒楼的「全桌重摆」——整页刷新——不知道高到哪里去了。

Uscope=Mboundary,ΔU=∣Mboundary∣∣Gall∣\displaystyle U_{scope} = M_{boundary}, \quad \Delta U = \frac{|M_{boundary}|}{|G_{all}|}
$M_{boundary}$ 是受影响的模块边界集合,$G_{all}$ 是页面全部模块。HMR 只更新前者,所以单次更新开销极小。
  1. 边界集合$M_{boundary}$ 表示这个模块依赖边界范围内的模块集合,通常只有几个文件。
    Uscope=Mboundary\displaystyle U_{scope} = M_{boundary}
  2. 更新开销$\displaystyle \Delta U = \frac{|M_{boundary}|}{|G_{all}|}$ 表示单次热更新涉及的模块数占全量的比例。
    ΔU=∣Mboundary∣∣Gall∣\displaystyle \Delta U = \frac{|M_{boundary}|}{|G_{all}|}
  3. 比例意义因为 $|M_{boundary}|$ 很小,$\Delta U$ 几乎趋近于 0,所以每次改动都能瞬间看到效果,开发体验丝般顺滑。
    ΔU→0\displaystyle \Delta U \to 0

生产构建

板前料理的精髓在于「食材新鲜、随点随做」,但如果你要开一百家连锁店,总不能每家店都让寿司大师傅当场切鱼。你必须建中央厨房:提前批量采购、统一加工、按标准分装,再运往各门店。这就是 Vite 在「生产环境」做的事——换用 Rollup 打包。

为什么开发和生产要用两套体系?因为开发时浏览器原生 ESM 虽然快,但不适合直接上线:每个模块都会变成单独的 HTTP 请求(项目大了请求数爆炸);老浏览器不支持原生 ESM;源码里有大量注释、空行、未使用的代码,体积臃肿。Rollup 作为老牌打包器,成熟稳定,支持 tree-shaking(摇树优化)、代码分割、兼容降级等深度优化,能产出体积最小、兼容最好的生产包。

所以对 Vite 而言,「快」是一把双刃剑:开发时有多随性,生产时就有多严谨。板前师傅负责在客人面前表演极致的鲜,中央厨房负责在看不见的地方保证百店的稳。两者分工明确,各自精彩。

Bout=Split(TreeShake(G)),Smin=min⁡∑∣Bi∣\displaystyle B_{out} = \text{Split}\left( \text{TreeShake}\left( G \right) \right), \quad S_{min} = \min \sum |B_i|
先对全量模块图做 tree-shaking 删掉无用代码,再做代码分割得到多个产物包,目标是让所有包的总体积最小。
  1. 摇树优化$\text{TreeShake}\left( G \right)$ 把模块图里被 import 且真正用到的代码保留下来,其余死代码全部剔除。
    TreeShake(G)\displaystyle \text{TreeShake}\left( G \right)
  2. 代码分割$\text{Split}(\cdot)$ 把优化后的代码按路由或按需拆分成多个小包,避免首屏加载一个巨型文件。
    Split(⋅)\displaystyle \text{Split}\left( \cdot \right)
  3. 体积最小化$\displaystyle S_{min} = \min \sum |B_i|$ 表示 Rollup 在压缩和分包策略中寻找总字节数最小的方案。
    Smin=min⁡∑∣Bi∣\displaystyle S_{min} = \min \sum |B_i|

AI 应用场景

AI 大模型推理服务的按需加载缓存

阶段:模型部署与服务优化

解决的问题:传统推理服务在启动时把所有模型全部加载进显存,冷启动慢、资源占用高,就像 Webpack 启动时全量打包所有模块。AI 服务需要像 Vite 一样「按需编译」——用户请求哪个模型才加载哪个,配合缓存复用(类似 Vite 的预构建缓存),让冷启动从分钟级降到毫秒级。

具体案例:某 AI 绘画平台同时提供十几种风格模型,早期启动时全部预加载,GPU 显存爆炸且启动要 5 分钟。参考 Vite 的依赖预构建与内存缓存思路后,改为「模型懒加载 + 热缓存」,用户选择风格时仅加载对应模型权重,命中缓存的模型 200ms 内响应。最终显存占用下降 70%,首请求时间从 300 秒降到 1 秒内。

机器学习特征平台的增量构建

阶段:数据工程与特征管道

解决的问题:传统特征管道每次训练都全量重算所有特征,极其浪费算力。借鉴 Vite 的 HMR「精准更新」思想:特征仓库监听数据源变化,只重算受影响的特征子集,其余特征直接从缓存读取,实现训练管道级的热更新。

具体案例:某广告推荐团队的特征平台每天凌晨全量重算 3000 个特征,耗时 6 小时。引入增量特征计算后,每天只有约 3% 的特征被数据更新波及,平台通过依赖图定位影响边界,仅重算这 3%,总耗时降到 40 分钟,算力成本下降 85%。

Python 演示

python79 行
"""模拟 Vite 开发服务器的按需编译与缓存机制。对比两种方式:1. 传统全量构建:启动时处理所有模块2. Vite 按需构建:收到请求才转换模块,并缓存结果"""import time  class Module:    def __init__(self, name, cost, deps=None):        self.name = name        self.cost = cost        self.deps = deps or []  class ViteDevServer:    def __init__(self):        self.module_cache = {}        self.total_transform_time = 0.0        self.cache_hit_count = 0        self.request_count = 0     def transform(self, mod):        if mod.name in self.module_cache:            self.cache_hit_count += 1            return self.module_cache[mod.name]        time.sleep(0.001 * mod.cost)        self.total_transform_time += mod.cost        self.module_cache[mod.name] = "transformed:" + mod.name        return self.module_cache[mod.name]     def serve(self, mod):        self.request_count += 1        result = self.transform(mod)        for dep in mod.deps:            self.serve(dep)        return result  ALL_MODULES = [Module("a", 5), Module("b", 3), Module("c", 4),               Module("d", 6), Module("e", 2), Module("f", 8),               Module("g", 7), Module("h", 4), Module("i", 5),               Module("j", 3)]  def full_build():    total = sum(m.cost for m in ALL_MODULES)    time.sleep(0.001 * total)    return total  def vite_first_screen(server):    entry = Module("entry", 1, deps=[ALL_MODULES[0], ALL_MODULES[2]])    return server.serve(entry)  def main():    server = ViteDevServer()    print("--- 传统全量构建 ---")    t0 = time.time()    full_cost = full_build()    print("全量构建耗时:", round(time.time() - t0, 4), "秒, 处理所有模块")    print("--- Vite 按需编译 ---")    t0 = time.time()    vite_first_screen(server)    print("首次按需编译耗时:", round(time.time() - t0, 4), "秒, 只处理 3 个模块")    print("缓存命中:", server.cache_hit_count,          "请求数:", server.request_count)    print("--- 再次请求(走缓存) ---")    t0 = time.time()    vite_first_screen(server)    print("二次耗时:", round(time.time() - t0, 4), "秒, 全部命中缓存")    print("--- 对比结论 ---")    print("全量成本:", full_cost, "按需首次成本:", server.total_transform_time)  if __name__ == "__main__":    main()
目的:用模拟数据演示 Vite「按需编译 + 缓存」为何比传统「全量构建」更快。
  1. class ViteDevServer:定义一个模仿 Vite 开发服务器的类,持有缓存字典与统计字段。
  2. self.module_cache内存缓存的字典,保存已转换过的模块,避免重复编译(对应正文中的「内存缓存」)。
  3. def transform(self, mod)模拟模块转换:命中缓存直接返回,否则耗时后存入缓存。
  4. def serve(self, mod)模拟浏览器请求模块:先转换自己,再递归处理其依赖(对应「请求即转换」)。
  5. full_build()模拟 Webpack 全量构建:所有模块无论是否用到都要处理一遍。
  6. vite_first_screen(server)模拟页面首屏,只请求入口及其两个依赖模块。
  7. print("缓存命中:", ...)输出缓存命中情况,直观展示第二次请求几乎零耗时。

常见误区

误区Vite 快是因为它开发时不做任何打包工作,直接原样把文件丢给浏览器。

正解源码文件确实不打包,但 node_modules 里的依赖必须经过 esbuild 的「依赖预构建」:一方面把 CommonJS 转成 ESM,另一方面合并内部模块减少请求数。这个过程相当于处理冻肉——客人面前可以不备菜,但后厨的机器人早就切好了。省略预构建,浏览器加载依赖时会被几百个请求拖垮。

为何常见:只看到「不打包源码」的表象,忽略了依赖预构建这个隐藏的准备工作,以为 Vite 真的零构建。

误区既然 esbuild 比 Rollup 快几十倍,Vite 生产构建一定也用它。

正解Vite 开发时用 esbuild 做预构建和转译,但生产构建的主力是 Rollup。因为生产环境追求的不只是快,更要 tree-shaking 剔死代码、代码分割控制加载体积、兼容老浏览器降级——这些深度优化 esbuild 目前还不够成熟。好比中央厨房的标准是统一品控和配送效率,而不是现场操作的观赏性。

为何常见:把「开发阶段快」和「生产构建快」混为一谈,忽略了两阶段目标的本质差异。

误区改完代码浏览器自动更新,这不就是刷新页面吗?刷新就完事了,还更简单。

正解刷新页面是「整桌重摆」,所有状态归零:输入框内容、滚动位置、弹窗状态全丢。HMR 是「换一道菜」:通过 WebSocket 通知浏览器精准替换变化的模块边界,页面其余部分和状态原封不动。开发调试时,你可能正在弹窗里试一个表单,HMR 会保留这些现场,而整页刷新会把你踢回起点。

为何常见:体验过快的人只感觉到了「不用手动刷新」,却没想到背后模块级替换带来的状态保全价值。

误区Vite 这么快,Webpack 要被淘汰了,所有项目都应该切换到 Vite。

正解Vite 开发体验确实更优,但生产构建仍依赖 Rollup 体系;而 Webpack 生态经过十年沉淀,拥有海量插件和配置方案,在大型复杂工程、微前端、非浏览器环境构建中仍占有一席之地。两者不是取代关系,更像日料店与中央厨房的优势互补——各有各的适用场景。

为何常见:看到速度数字差异就判定工具好坏,忽视了生态成熟度和场景适配度。

误区Vite 开发时浏览器解析原生 ESM,那生产环境直接上线 ESM 文件就行,不用打包。

正解部分现代浏览器确实支持原生 ESM,但生产环境还有三大关卡:老浏览器(如 IE)完全不支持;每个模块一个请求,首屏请求数爆炸;源码带有大量冗余代码需要压缩。Rollup 打包恰恰是解决这些问题的:合并文件、兼容降级、压缩瘦身。板前的新鲜食材再好,也不能整条三文鱼直接打包运给客人。

为何常见:把「开发环境能跑」等同于「生产环境能上线」,忽略了浏览器兼容性、网络请求数和代码体积的制约。

面试题

Q1. Vite 开发时启动极快的根本原因是?

单选题 · easy

  • A.用 Go 语言重写了所有浏览器功能
  • B.启动前做了全量依赖图优化
  • C.开发时不打包,浏览器原生 ESM 按需加载← 正确答案
  • D.使用 WebWorker 并行编译全部源码

答案:C。Vite 启动时不做全量打包,浏览器通过原生 ESM 按需请求模块,Vite 只对请求到的文件做实时转换。A 错在 Go 只用于 esbuild 预构建;B 错在全量依赖图正是 Webpack 慢的根源;D 错在源码不并行编译,是请求时才处理。

Q2. esbuild 为何比传统 JS 打包器快这么多?

单选题 · easy

  • A.因为使用了 HTTP 缓存加速
  • B.因为用 Go 编写且多核并行处理← 正确答案
  • C.因为只在生产环境运行
  • D.因为跳过了依赖解析过程

答案:B。esbuild 用 Go 语言编写,编译为原生代码,配合多核并行和极小化处理逻辑,比 Node.js 环境下的 JS 打包器快 10 到 100 倍。A 是 Vite 的缓存机制而非 esbuild 特性;C 完全相反;D 错在它同样必须解析依赖。

Q3. Vite 对 node_modules 里的依赖做预构建的主要目的是?

单选题 · mid

  • A.删除依赖中的注释和空格以减小体积
  • B.把 CommonJS 转成 ESM 并减少请求数量← 正确答案
  • C.将所有依赖合并成一个文件
  • D.把依赖编译成二进制码加速执行

答案:B。预构建有两个核心目标:将 CJS 转成 ESM 让浏览器能识别,以及把内部含大量子模块的依赖合并,避免浏览器发出上百个请求。A 是压缩工具的职责;C 过于绝对,是合并单个包内部的子模块而非全部依赖;D 浏览器执行的是 JS 并非二进制。

Q4. 浏览器通过原生 ESM 加载时,Vite 如何做到「请求即转换」?

单选题 · mid

  • A.浏览器自动识别 .vue 和 .tsx 文件
  • B.Vite 在 HTTP 请求到达时实时转换该文件← 正确答案
  • C.源码在启动前已全部预编译为 JS
  • D.依赖预构建已包含全部源码转换

答案:B。浏览器发起对某个模块的请求,Vite 服务器接到请求后才对该文件执行编译转换,返回标准 ESM。A 错在浏览器只认 JS/ESM,不认 .vue 等专有格式;C 是全量构建的做法;D 预构建只处理依赖,不处理源码。

Q5. 生产环境 Vite 使用 Rollup 而非 esbuild 的核心原因是?

单选题 · mid

  • A.Rollup 启动速度比 esbuild 更快
  • B.Rollup 支持 TypeScript 而 esbuild 不支持
  • C.Rollup 具备成熟的 tree-shaking 和兼容降级能力← 正确答案
  • D.esbuild 只能开发者本地运行

答案:C。生产环境追求产物最优而非构建最快。Rollup 提供成熟的 tree-shaking(剔除无用代码)、代码分割、老浏览器语法降级等深度优化,esbuild 在这些方面尚不成熟。A 违背事实;B 两者都支持 TS;D 荒谬。

Q6. Vite 插件机制中,负责把源码字符串转换成新代码的钩子是?

单选题 · hard

  • A.resolveId
  • B.load
  • C.transform← 正确答案
  • D.buildStart

答案:C。transform 钩子接收模块源码,返回转换后的新代码,是插件体系中最核心的转换环节。resolveId 负责解析模块路径(决定去找谁);load 负责加载模块内容(把文件读成字符串);buildStart 是构建开始的声明周期钩子,不转换代码。

Q7. 以下哪些因素让 Vite 开发体验显著快于 Webpack?

多选题 · easy

  • A.源码按需编译而非全量打包← 正确答案
  • B.esbuild 预构建依赖速度极快← 正确答案
  • C.启动前生成完整的依赖关系图
  • D.HMR 只更新变化的模块边界← 正确答案

答案:ABD。Vite 的核心加速来自按需编译、esbuild 预构建和精准 HMR 三驾马车。C 是 Webpack 的做法——启动前构建全量依赖图正是它慢的原因,所以排除。A、B、D 分别对应正文中的按需编译、依赖预构建、热更新三大机制。

Q8. 关于 Webpack 和 Vite 的差异,下列叙述正确的是?

多选题 · mid

  • A.Webpack 启动时打包全部模块,Vite 只编译请求的模块← 正确答案
  • B.Vite 开发时依赖预构建使用 esbuild← 正确答案
  • C.两者生产构建使用完全相同的打包工具
  • D.Vite 生产构建交给 Rollup 完成深度优化← 正确答案

答案:ABD。A 准确概括了两者开发模式的本质差异;B 正确,esbuild 负责 CJS 转 ESM 与依赖合并;D 正确,Rollup 承担生产环境的 tree-shaking 与兼容优化;C 错误——Webpack 生产构建用自身打包器,Vite 用 Rollup,工具并不相同。

Q9. 关于 Vite HMR 的机制,下列说法正确的有?

多选题 · hard

  • A.通过 WebSocket 通知浏览器模块变更← 正确答案
  • B.更新范围限定在模块依赖边界内← 正确答案
  • C.每次更新都会整页刷新以保状态一致
  • D.更新时页面组件的状态可以得到保留← 正确答案

答案:ABD。HMR 通过 WebSocket 推送变更信息,浏览器只重新执行依赖边界内的模块,因此不做整页刷新,组件状态得以保留。A、B、D 都是 HMR 的准确描述;C 恰恰是 HMR 要避免的事——整页刷新会丢失状态,所以错误。

Q10. 为什么 Vite 生产环境不直接使用浏览器原生 ESM 而要做打包?

单选题 · extreme

  • A.原生 ESM 无法被浏览器解析无法运行
  • B.为保证开发与生产代码逻辑完全一致
  • C.模块请求数过多且老浏览器不兼容原生 ESM← 正确答案
  • D.原生 ESM 文件体积一定比打包后更大

答案:C。浏览器原生 ESM 直接上线有两大致命伤:每个模块独立请求造成首屏请求数爆炸,且老浏览器(IE 等)不支持原生 ESM。打包可以合并请求、压缩体积、降级语法。A 违反事实——现代浏览器原生支持 ESM;B 不是打包的根本原因;D 过于绝对,理论上压缩后ESM也可以小,但请求数问题无法靠压缩解决,C 是最核心原因。