Vite 生产构建的 Rollup 配置调优:插件顺序、输出格式与资源内联

📅 2026/7/19 17:48:05
Vite 生产构建的 Rollup 配置调优:插件顺序、输出格式与资源内联
Vite 生产构建的 Rollup 配置调优插件顺序、输出格式与资源内联一、Vite 生产构建的底层架构Vite 的生产构建本质上是 Rollup 的一次受控执行。Vite 在 Rollup 之上叠加了两层封装第一层是插件兼容层将 Vite 插件格式转换为 Rollup 插件接口第二层是配置聚合层将vite.config.ts中的分散配置项映射到 Rollup 的OutputOptions、InputOptions等结构。理解这个映射关系是调优的前提。很多开发者直接修改vite.config.ts的build.rollupOptions字段却不知道 Vite 内部已经注入了隐含默认值——比如output.format默认为esmoutput.manualChunks默认使用 Vite 的自动分割策略。盲目覆盖可能导致内建优化失效。具体而言配置流程始于vite.config.ts经由 Vite 配置聚合层处理后分别映射为 Rollup 的InputOptions负责插件排序与入口定义和OutputOptions负责输出格式与分块策略。这两者共同驱动 Rollup 执行最终生成 ESM/CJS/IIFE 格式的产物及静态资源。二、插件顺序构建链路的性能瓶颈Rollup 插件按注册顺序执行钩子分为build阶段解析、转换和output阶段生成、写入。插件顺序直接影响两个指标构建耗时与产物体积。2.1 构建阶段插件的排序原则// vite.config.ts — 正确的插件排序示范 import { defineConfig } from vite; import react from vitejs/plugin-react;import legacy from vitejs/plugin-legacy;import compression from vite-plugin-compression;import { visualizer } from rollup-plugin-visualizer;export default defineConfig({plugins: [// 1. 语法转换类插件——最先执行确保后续插件能处理标准 ASTreact(),// 2. 分析类插件——在转换后执行读取标准化 AST visualizer({ open: false, gzipSize: true }), // 3. 产物后处理类插件——最后执行 legacy({ targets: [defaults, not IE 11] }), compression({ algorithm: gzip, threshold: 10240 }),],build: {rollupOptions: {// 以下配置直接映射到 Rollup OutputOptionsoutput: {format: esm,entryFileNames: assets/[name]-[hash].js,chunkFileNames: assets/[name]-[hash].js,assetFileNames: assets/[name]-[hash].[ext],},},},});排序原则总结 - **语法转换优先**babel/swc/ts后续插件依赖标准化 AST - **代码分析居中**visualizer/coverage需要读取最终 AST - **产物后处理末尾**compression/legacy操作最终输出 错误排序的典型后果将 compression 放在 react 之前会导致 JSX 未转换就被压缩产物不可用。 ### 2.2 插件钩子冲突检测 typescript /** * 检测插件钩子注册顺序冲突 * build 钩子必须先于 output 钩子 */ function validatePluginOrder(plugins: Plugin[]): string[] { const warnings: string[] []; const buildHooks [resolveId, load, transform]; const outputHooks [renderChunk, writeBundle, generateBundle]; let lastBuildIndex -1; plugins.forEach((plugin, index) { const hasBuild Object.keys(plugin).some(k buildHooks.includes(k)); const hasOutput Object.keys(plugin).some(k outputHooks.includes(k)); if (hasOutput !hasBuild index lastBuildIndex) { warnings.push( 插件 ${plugin.name} 仅含 output 钩子却位于 build 钩子插件之前建议调整顺序 ); } if (hasBuild) { lastBuildIndex index; } }); return warnings; }三、输出格式选择与多格式配置3.1 三种输出格式的适用场景格式适用场景Tree-shaking动态导入浏览器直接加载ESM现代浏览器、构建工具链消费完整支持完整支持支持CJSNode.js 服务端、旧版工具部分支持需 polyfill不支持IIFECDN 直接引入、非构建场景不支持不支持支持3.2 多格式输出配置当库需要同时支持 ESM 和 CJS 消费者时import { defineConfig } from vite; export default defineConfig({ build: { lib: { entry: src/index.ts, name: MyLib, fileName: (format) my-lib.${format}.js, }, rollupOptions: { // 多格式输出每个格式独立配置 output: [ { format: esm, // ESM 输出保留动态导入不做 polyfill preserveModules: true, preserveModulesStructure: true, entryFileNames: [name].mjs, }, { format: cjs, // CJS 输出需处理动态导入为同步 require dynamicImportInCjs: true, entryFileNames: [name].cjs, }, ], // 外部化 Node.js 内置模块 external: [fs, path, crypto], }, }, });注意preserveModules: true与manualChunks互斥。前者按源文件结构分割模块后者按自定义逻辑合并——两者不能同时生效。四、资源内联策略权衡请求数与首屏体积4.1 小资源内联的阈值决策CSS 小文件、SVG 图标、JSON 配置等小型资源可以内联到 JS bundle 中减少 HTTP 请求。但内联会增加 JS 体积阻塞主线程解析。/** * 资源内联阈值计算器 * 根据资源体积与当前 bundle 大小动态决策 */ function calculateInlineThreshold( resourceSize: number, bundleSize: number, httpCostMs: number 50 // 单次额外请求的延迟估算 ): { shouldInline: boolean; reason: string } { // 内联后 JS 解析延迟增加估算1KB ≈ 1ms移动端 const parseCostMs resourceSize / 1024; const inlineOverheadMs parseCostMs; // 外联的额外 HTTP 请求成本 const externalCostMs httpCostMs; if (resourceSize 4096) { // 小于 4KB内联收益明确 return { shouldInline: true, reason: 体积 4KB请求节省优先 }; } if (inlineOverheadMs externalCostMs) { return { shouldInline: true, reason: 内联解析延迟 ${inlineOverheadMs}ms 外联请求延迟 ${externalCostMs}ms }; } return { shouldInline: false, reason: 内联解析延迟 ${inlineOverheadMs}ms 外联请求延迟 ${externalCostMs}ms }; }4.2 Vite 内联配置实践// vite.config.ts — 资源内联精细化配置 export default defineConfig({ build: { // 小于 4KB 的资源内联为 base64/data URI assetsInlineLimit: 4096, // CSS 内联策略开发环境外联便于 HMR生产环境内联 cssCodeSplit: true, rollupOptions: { output: { // 手动分块将 runtime 与业务代码分离 manualChunks(id) { if (id.includes(node_modules)) { // 第三方依赖按包名分块 const match id.match(/node_modules\/([^/])/); if (match) { const pkgName match[1]; // 大型框架单独分块 if ([react, react-dom, vue].includes(pkgName)) { return vendor-${pkgName}; } return vendor-misc; } } }, }, }, }, });五、总结Vite 生产构建调优的核心认知是Vite 并非零配置即可用的黑箱而是 Rollup 的受控封装。调优的三个关键维度插件顺序语法转换 → 代码分析 → 产物后处理逆序会导致构建失败或产物异常输出格式ESM 优先tree-shaking 完整CJS 兼容服务端消费IIFE 仅用于 CDN 直接引入资源内联4KB 阈值是经验起点实际决策应对比内联解析延迟与外联请求延迟配置调优不是一次性动作。随着依赖版本升级、业务模块增长分块策略和内联阈值需要定期重新评估。rollup-plugin-visualizer 是持续监控产物体积的必备工具。