为什么 Vite 开发用 esbuild,生产构建却用 Rollup?
Vite 在开发环境主要依赖原生 ESM、esbuild 和开发服务器,在生产环境默认使用 Rollup 打包。这个设计不是“开发一套、上线另一套”的随意拼接,而是把两个阶段最在意的目标分开处理:开发时要快启动、快更新、少等待;上线时要稳定产物、代码分割、tree-shaking、资源压缩和缓存友好。
开发环境为什么不先打包
传统打包工具在启动 dev server 前,通常要先分析入口、递归构建依赖图,再把一大包代码交给浏览器。项目越大,启动越慢,改一个文件也可能牵动一片模块。Vite 的思路更直接:源码模块先按浏览器原生 ESM 方式请求,浏览器需要哪个文件,开发服务器就转换哪个文件。
这里 esbuild 主要做两件事。第一是依赖预构建,把 CommonJS 或多入口依赖转换成更适合浏览器加载的 ESM。第二是处理 TypeScript、JSX 等语法转换,因为 esbuild 用 Go 写,冷启动和转换速度通常明显快于纯 JavaScript 转译链。
ts// vite.config.ts import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [react()], optimizeDeps: { include: ['react', 'react-dom'] }, server: { port: 5173, hmr: true } })
这段配置里的 optimizeDeps 就是开发期优化依赖的入口。踩坑点在于:如果某个依赖内部动态引用很复杂,Vite 自动扫描可能漏掉它,表现为开发时偶发加载失败或 HMR 异常,这时手动 include 往往比怀疑业务代码更有效。
生产环境为什么还要 Rollup
生产环境关注的是最终资源质量。浏览器可以加载 ESM,但真实线上页面不能把几千个源码模块原样丢给用户,否则请求数量、缓存策略、兼容性和压缩效果都会变差。Rollup 更擅长静态分析,它能把模块合并成合理的 chunk,删除未使用导出,提取 CSS,并配合插件处理资源 hash。
Vite 使用 Rollup 不是因为 esbuild 不快,而是因为“快”和“产物可控”不是同一个目标。esbuild 也能打包,但 Rollup 的插件生态、chunk 控制和库模式构建更成熟。对业务应用来说,生产构建慢一点可以接受;如果产物拆包不合理、缓存失效频繁或公共依赖重复打入多个 chunk,线上代价会更高。
tsexport default defineConfig({ build: { target: 'es2018', sourcemap: false, rollupOptions: { output: { manualChunks: { vendor: ['react', 'react-dom'] } } } } })
manualChunks 不是越细越好。拆得太粗,首屏包大;拆得太细,请求和调度成本上升。常见取舍是把稳定的大依赖单独拆出来,让业务代码变更时不影响 vendor 缓存。
开发和生产差异会带来什么边界
Vite 的双模式很快,但也意味着开发环境不等于生产环境。开发期按需转换,生产期整体打包,所以有些问题只会在 vite build 后暴露,比如动态导入路径无法静态分析、Node API 被错误带入浏览器、CSS 顺序在打包后变化。
因此团队里不能只跑 vite 就认为没问题。提交前至少跑一次 vite build,涉及路由懒加载、第三方库升级、浏览器兼容目标变化时,还应该用 vite preview 看构建产物。
追问
Vite 开发环境完全不用打包吗?
严格说,源码部分通常不做传统意义上的全量打包,而是按浏览器请求即时转换。依赖部分会做预构建,因为很多 npm 包并不是天然适合浏览器直接按 ESM 加载。这个取舍让启动速度更快,但也带来依赖扫描边界,遇到动态 require 或条件导出时可能需要手动配置。不要把“不打包”理解成“不处理”,Vite 只是把处理时机拆开了。
esbuild 这么快,为什么生产不全用它?
esbuild 的优势是速度,尤其适合开发期频繁转换。生产构建还要考虑 chunk 结构、插件兼容、资源命名、CSS 提取和库模式输出,这些是 Rollup 长期积累的强项。用 esbuild 全量生产打包不是不行,但复杂项目里可控性通常不如 Rollup。实际选择时,开发体验优先速度,线上产物优先稳定和可调。
开发正常但 build 失败通常查哪里?
先看动态导入、环境变量和依赖格式。开发时某些代码路径可能没被浏览器请求到,生产构建会做全局静态分析,所以隐藏问题更容易暴露。常见坑是 import(path) 的 path 无法被 Rollup 推断,或把 process.env、fs 这类 Node 侧能力带进前端包。排查时不要只盯报错文件,顺着依赖链看是谁把不该进浏览器的代码引入了。
如何判断要不要配置 manualChunks?
小项目通常先别配,Vite 默认策略已经够用。只有当构建产物里某个 chunk 过大、公共依赖重复、或者业务发布导致大依赖缓存频繁失效时,再考虑手动拆分。配置时要看真实 bundle 分析结果,而不是凭感觉把所有库都拆出来。拆包本质是在首屏请求数和缓存命中率之间做取舍。
vite preview 能代替线上验证吗?
不能完全代替,但它能帮你用本地静态服务器检查生产产物。它适合发现资源路径、懒加载、路由 fallback、hash 文件引用等问题。边界在于它不等同于 CDN、网关和真实浏览器矩阵,也不会模拟线上缓存策略。上线前如果改了 base、反向代理或静态资源域名,仍然需要在接近生产的环境验证。