人人都会AI编程

基于 ESM 的开发服务器、预构建机制

更新时间:2026-07-11

现代前端构建工具中,Vite 的崛起速度令人瞩目。它之所以在开发体验上远超传统工具(如 Webpack),核心就在于它把 浏览器原生 ES Modules(ESM) 作为开发服务器的基石,并配合精巧的预构建机制解决现实问题。

开发服务器:利用浏览器原生 ESM 实现秒启

传统构建工具在开发时需要将整个应用打包成一个(或几个)大体积的 bundle,才能在浏览器中运行。这意味着每次启动开发服务器,都必须完成“解析入口 → 构建依赖图 → 打包所有模块”的完整流程。项目规模越大,启动就越慢,有时候甚至需要等待几十秒。

Vite 的思路截然不同:

  1. 直接提供源码,而非打包产物

浏览器的 <script type="module"> 已经原生支持 ESM 导入。Vite 的开发服务器不再将整个应用打包成一个大文件,而是直接将每个 .js.ts.vue 等源文件作为独立的模块提供给浏览器。

  1. 按需加载,而非一次全量

浏览器解析到 import 语句时,会向开发服务器发起一个 HTTP 请求来获取对应的模块。Vite 收到请求后,仅对该文件进行“单文件”的即时编译(如将 TypeScript 编译为 JavaScript,将 .vue 文件拆解出来),然后返回给浏览器。这样,只有当前页面真正需要的代码才会被加载,冷启动速度几乎与项目规模无关。

  1. 模块热更新(HMR)极快

由于每个模块都是独立的,当文件发生变化时,Vite 只需要让浏览器重新请求这个被修改的模块即可,不需要重建整个依赖图或打包,热更新时间通常可以控制在毫秒级别。

实际体验上,用 Vite 启动一个几十个页面的中大型项目,开发服务器往往能在 1 秒内就绪,而同等规模的 Webpack 项目可能需要十几秒甚至更长时间。这种速度提升直接改变了开发者的日常工作节奏。

预构建机制:弥补 ESM 开发的现实短板

利用浏览器原生 ESM 开发听起来很完美,但存在两个实际问题:

  1. npm 包的模块格式不兼容

绝大多数 npm 包都是 CommonJS 格式(module.exports),或者内部使用了裸导入(import { something } from 'lodash'),浏览器无法直接解析。同时,有些包虽然提供了 ESM 版本,但可能将内部依赖拆成成百上千个独立的小模块文件,按照按需加载的策略,浏览器就要发出大量级联请求,性能会很差。

  1. 依赖众多导致请求瀑布

如果一个包内部有几百个模块互相引用,浏览器在处理时会产生一系列的“请求瀑布”——A 请求完成后才解析出对 B、C 的引用,B 完成后又发现要加载 D、E…… 这个过程会严重影响首屏加载速度。

Vite 的预构建(Dependency Pre-Bundling) 就是为解决这两个问题而设计的:

  • 自动检测并转换:当你首次启动 Vite 开发服务器时,它会自动扫描你的源码,找到所有来自 node_modules 的依赖(即“裸导入”),然后用 esbuild 以极快的速度将这些依赖提前打包成单个 ESM 包

→ CommonJS 的模块被翻译为 ESM,裸导入变成可解析的相对路径,再也不需要担心兼容性。

  • 合并碎片文件:esbuild 会把如 lodash-es 拆成几百个内部模块的结构打成一个(或几个)极少的 ESM 文件,浏览器只需要发起一次(或少量)请求就能获取到整个依赖,彻底消除请求瀑布。
  • 缓存复用:预构建的结果会被缓存到 node_modules/.vite 目录下。只要依赖版本不变,下次启动就不会再重复构建,直接使用缓存,再次启动的速度更快。

总结

Vite 把“利用浏览器原生 ESM”作为开发服务器的核心,带来了毫秒级冷启动和极速 HMR。针对 npm 生态现实引入的预构建机制,又用 esbuild 的超高性能填补了 ESM 开发和实际依赖之间的鸿沟。两者结合,形成了 Vite 在开发体验上的核心竞争力——既有开发时的极致响应,又不必放弃对庞大 npm 生态的兼容。