Skip to content
构建生产版本

构建生产版本

本章涵盖 vite build 的完整配置、代码分割策略、库模式、多页应用和浏览器兼容性。


构建基础

vite build                 # 默认生产构建
vite build --mode staging  # 指定模式
vite build --watch         # 监听模式(适合库开发)
vite build --sourcemap     # 生成 source map

构建流程:

源码(src) → TypeScript 编译(esbuild)→ Rollup 打包 → Terser 压缩 → 输出 dist/

🔬 深入原理:生产构建时,Vite 不再使用原生 ESM 策略,而是用 Rollup 打包所有模块并进行 Tree Shaking、代码分割和压缩,以优化最终的加载性能。


build 配置项

// vite.config.js
export default defineConfig({
  build: {
    // === 输出 ===
    outDir: 'dist',                    // 输出目录
    assetsDir: 'assets',              // 静态资源子目录
    assetsInlineLimit: 4096,          // 内联 base64 阈值(字节)
    emptyOutDir: true,                // 构建前清空输出目录

    // === 代码分割 ===
    rollupOptions: {
      output: {
        manualChunks: { /* ... */ },
      },
    },

    // === CSS ===
    cssCodeSplit: true,               // CSS 代码分割
    cssMinify: 'lightningcss',        // CSS 压缩:'esbuild' / 'lightningcss'
    cssTarget: 'chrome61',            // CSS 目标浏览器(配合 lightningcss)

    // === 输出兼容性 ===
    target: 'modules',                // JS 目标:'modules'(ES2020)/ 'es2015' 等
    modulePreload: { polyfill: true },
    minify: 'esbuild',               // 压缩器:'esbuild' / 'terser' / false

    // === Source Map ===
    sourcemap: false,                 // 是否生成 sourcemap
    // sourcemap: 'hidden',           // 生成但不引用

    // === 文件命名 ===
    rollupOptions: {
      output: {
        entryFileNames: 'assets/[name].[hash].js',
        chunkFileNames: 'assets/[name].[hash].js',
        assetFileNames: 'assets/[name].[hash].[ext]',
      },
    },

    // === 其他 ===
    copyPublicDir: true,              // 是否复制 public 目录
    reportCompressedSize: true,       // 报告 gzip 后大小
    chunkSizeWarningLimit: 500,       // chunk 大小警告阈值(KB)
    watch: null,                      // 构建监听
  },
})

代码分割(Code Splitting)

自动分割

Vite 自动对动态 import() 进行代码分割:

// 自动分割:每个动态 import 会生成独立的 chunk
const LazyComponent = lazy(() => import('./HeavyComponent.jsx'))

// 构建产物:
// dist/assets/HeavyComponent.hash.js  ← 独立 chunk
// dist/assets/index.hash.js           ← 主入口

手动分割(manualChunks)

export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          // 将 React 相关库拆分到独立 chunk
          'react-vendor': ['react', 'react-dom', 'react-router-dom'],
          // 将 UI 库拆分
          'ui-vendor': ['antd', '@ant-design/icons'],
          // 将工具库拆分
          'utils': ['lodash', 'dayjs', 'axios'],
        },
      },
    },
  },
})

函数式拆分

manualChunks(id) {
  // node_modules 中的库分包策略
  if (id.includes('node_modules')) {
    // React 生态单独打一个包
    if (id.includes('react') || id.includes('react-dom') || id.includes('react-router')) {
      return 'react-vendor'
    }
    // UI 组件库单独分包
    if (id.includes('antd') || id.includes('@ant-design')) {
      return 'ui-vendor'
    }
    // 其余第三方库
    return 'vendor'
  }
}

💡 最佳实践:不要过度拆分——浏览器并发连接有限(HTTP/1.1 约 6 个,HTTP/2 约 100 个)。React/Vue 这类稳定的大型库适合抽离为 vendor chunk,利用浏览器长期缓存。


库模式

开发供他人使用的 JS 库时,使用库模式:

// vite.config.js
export default defineConfig({
  build: {
    lib: {
      entry: './src/index.js',          // 库入口
      name: 'MyLib',                     // UMD 全局变量名
      formats: ['es', 'umd', 'cjs'],     // 输出格式:es / umd / cjs / iife
      fileName: (format) => `my-lib.${format}.js`,
    },
    rollupOptions: {
      // 外部化那些不打包进库的依赖
      external: ['react', 'react-dom'],
      output: {
        // 在 UMD/CJS 模式下,这些外部依赖需要有全局变量
        globals: {
          react: 'React',
          'react-dom': 'ReactDOM',
        },
      },
    },
  },
})
输出格式 适用场景 说明
es ESM 环境(现代打包工具、浏览器) ES Module,支持 Tree Shaking
cjs Node.js CommonJS 环境 require() 导入
umd 浏览器 script 标签、各种环境 同时支持 AMD/CJS/全局变量
iife 浏览器 script 标签 立即执行函数,污染全局

package.json 字段配置

{
  "name": "my-lib",
  "type": "module",
  "main": "./dist/my-lib.cjs.js",       // CJS 入口
  "module": "./dist/my-lib.es.js",      // ESM 入口
  "types": "./dist/index.d.ts",          // 类型声明
  "exports": {
    ".": {
      "import": "./dist/my-lib.es.js",
      "require": "./dist/my-lib.cjs.js",
      "types": "./dist/index.d.ts"
    }
  },
  "files": ["dist"]
}

💡 最佳实践:同时提供 es(for 现代构建工具 + Tree Shaking)和 cjs(for 旧 Node.js)格式。ES 格式的 type: "module" 和 CJS 的 main 字段必须正确设置。


多页应用

当项目有多个 HTML 入口时(非 SPA):

// vite.config.js
import { resolve } from 'path'

export default defineConfig({
  build: {
    rollupOptions: {
      input: {
        main: resolve(__dirname, 'index.html'),
        admin: resolve(__dirname, 'admin/index.html'),
        nested: resolve(__dirname, 'nested/index.html'),
      },
    },
  },
})

目录结构对应:

├── index.html
├── admin/
│   └── index.html
├── nested/
│   └── index.html
└── src/

每个 HTML 文件需要包含自己的 <script type="module"> 入口。


浏览器兼容性

export default defineConfig({
  build: {
    // JS 目标浏览器(传递给 esbuild)
    target: 'es2015',  // 默认 'modules'(≈ es2020)

    // CSS 目标(使用 lightningcss 时)
    cssTarget: 'chrome61',

    // 动态导入 polyfill
    modulePreload: {
      polyfill: true,  // 为旧浏览器注入 modulepreload polyfill
    },
  },
})

常见 target 值

target 值 含义 说明
'modules' 支持 ES Modules 的浏览器 默认值
'es2020' ES2020 支持的浏览器 BigInt、globalThis、import.meta
'es2019' ES2019 flat/flatMap、Object.fromEntries
'es2018' ES2018 async/await、spread
'es2015' ES2015/ES6 支持的浏览器 兼容性最好但产物最大
'chrome87' Chrome 87+ 精确到浏览器版本

@vitejs/plugin-legacy

如果需要支持更老的浏览器(如 IE11),使用 legacy 插件:

npm i -D @vitejs/plugin-legacy terser
import legacy from '@vitejs/plugin-legacy'

export default defineConfig({
  plugins: [
    legacy({
      targets: ['defaults', 'not IE 11'],
    }),
  ],
})

压缩器选择

export default defineConfig({
  build: {
    // esbuild(默认):极快压缩,体积略大于 Terser
    minify: 'esbuild',

    // terser:较慢,压缩率更高,支持高级压缩选项
    // minify: 'terser',
    // terserOptions: {
    //   compress: { drop_console: true },
    // },

    // false:不压缩(不推荐生产环境)
    // minify: false,
  },
})
压缩器 速度 压缩率 场景
esbuild ⚡ 极快 良好 默认,大多数场景首选
terser 🐢 较慢 最优 对体积有极致要求的生产环境
false 调试构建产物

构建分析

# 生成构建体积分析(Rollup 的 stats 输出)
vite build --debug

# 可视化分析
npm i -D rollup-plugin-visualizer
// vite.config.js
import { visualizer } from 'rollup-plugin-visualizer'

export default defineConfig({
  plugins: [
    visualizer({
      open: true,              // 构建后自动打开浏览器
      gzipSize: true,          // 显示 gzip 后大小
      brotliSize: true,        // 显示 brotli 大小
    }),
  ],
})

性能提示reportCompressedSize: true(默认已开启)会在构建日志中打印 gzip 后的大小,方便快速评估实际传输体积。