编译构建与打包全面优化之Vite

编译构建与打包全面优化之Vite

Vite 开发构建优化详解

作为现代前端构建工具,Vite 凭借其极快的冷启动速度和热更新机制,已成为 Vue.js、React 等框架的首选构建工具。本文将深入探讨 Vite 在开发和构建阶段的全面优化策略。

开发模式优化

开发模式的优化是提升前端开发体验的关键环节。Vite 通过多种机制来确保开发过程的高效性和流畅性。

启用模块热替换 (HMR)

HMR 是 Vite 的核心优势之一,它允许在运行时更新模块而无需完全刷新页面。

javascript 复制代码
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  server: {
    hmr: {
      // 配置 HMR 选项
      port: 24678, // HMR 端口
      overlay: true, // 错误覆盖层
      // 自定义 HMR 更新逻辑
      clientPort: 3000
    }
  }
})

HMR API 使用示例:

javascript 复制代码
// 在模块中使用 HMR API
if (import.meta.hot) {
  // 接受自身模块的更新
  import.meta.hot.accept()
  
  // 接受依赖模块的更新
  import.meta.hot.accept('./dependency.js', (newModule) => {
    // 处理依赖更新逻辑
    updateDependency(newModule)
  })
  
  // 监听自定义事件
  import.meta.hot.on('custom-event', (data) => {
    console.log('收到自定义事件:', data)
  })
  
  // 模块销毁时的清理逻辑
  import.meta.hot.dispose((data) => {
    // 清理副作用
    clearInterval(data.timer)
  })
}
调整 optimizeDeps 选项

依赖预构建是 Vite 性能优化的重要环节,通过 esbuild 将 CommonJS 和 UMD 依赖转换为 ESM。

javascript 复制代码
// vite.config.js
export default defineConfig({
  optimizeDeps: {
    // 明确指定需要预构建的依赖
    include: [
      'vue',
      'vue-router',
      'vuex',
      'axios',
      'lodash-es',
      // 处理深层依赖
      'element-plus/es/components/button/style/css',
      'element-plus/es/components/input/style/css'
    ],
    
    // 排除不需要预构建的依赖
    exclude: [
      'your-local-package',
      '@your-org/internal-lib'
    ],
    
    // 自定义 esbuild 选项
    esbuildOptions: {
      // 支持 Node.js 内置模块
      define: {
        global: 'globalThis'
      },
      // 处理 JSX
      jsx: 'transform',
      jsxFactory: 'h',
      jsxFragment: 'Fragment'
    },
    
    // 强制重新构建依赖
    force: process.env.FORCE_OPTIMIZE === 'true'
  }
})

动态依赖预构建配置:

javascript 复制代码
// 根据环境动态配置依赖预构建
const getOptimizeDeps = (isDev) => ({
  include: [
    'vue',
    'vue-router',
    ...(isDev ? ['vue-devtools'] : []),
    // 根据功能模块按需加载
    ...getFeatureModules()
  ],
  exclude: isDev ? [] : ['vue-devtools']
})

function getFeatureModules() {
  const features = process.env.VITE_FEATURES?.split(',') || []
  const moduleMap = {
    'charts': ['echarts', 'd3'],
    'editor': ['monaco-editor', 'codemirror'],
    'ui': ['element-plus', 'ant-design-vue']
  }
  
  return features.flatMap(feature => moduleMap[feature] || [])
}

export default defineConfig({
  optimizeDeps: getOptimizeDeps(process.env.NODE_ENV === 'development')
})
使用 esbuild 进行依赖预构建

esbuild 的极速编译能力是 Vite 性能的核心保障,合理配置 esbuild 可以进一步提升构建效率。

javascript 复制代码
// vite.config.js
export default defineConfig({
  esbuild: {
    // 开发环境保留注释,生产环境移除
    keepNames: true,
    minifyIdentifiers: process.env.NODE_ENV === 'production',
    minifySyntax: process.env.NODE_ENV === 'production',
    minifyWhitespace: process.env.NODE_ENV === 'production',
    
    // 自定义 JSX 处理
    jsxDev: process.env.NODE_ENV === 'development',
    jsxFactory: 'React.createElement',
    jsxFragment: 'React.Fragment',
    
    // 目标环境配置
    target: ['es2020', 'chrome80', 'firefox78', 'safari13'],
    
    // 全局替换
    define: {
      __DEV__: process.env.NODE_ENV === 'development',
      __PROD__: process.env.NODE_ENV === 'production',
      __VERSION__: JSON.stringify(process.env.npm_package_version)
    }
  }
})

Vite 开发模式优化流程图:

graph TD A[项目启动] --> B[依赖扫描] B --> C{是否有缓存} C -->|有| D[加载缓存依赖] C -->|无| E[esbuild 预构建] E --> F[生成依赖缓存] F --> D D --> G[启动开发服务器] G --> H[文件变更监听] H --> I{文件类型判断} I -->|源码文件| J[HMR 更新] I -->|依赖文件| K[重新预构建] J --> L[浏览器更新] K --> F L --> H

环境变量配置

环境变量的合理配置是项目部署和多环境管理的基础,Vite 提供了灵活的环境变量处理机制。

使用 .env 文件配置环境变量

Vite 支持多种 .env 文件的层级加载,提供了完善的环境变量管理方案。

javascript 复制代码
// .env
VITE_APP_TITLE=My Application
VITE_APP_VERSION=1.0.0

// .env.local (本地开发,不提交到版本控制)
VITE_API_SECRET=local-secret-key
VITE_DEBUG_MODE=true

// .env.development
VITE_API_BASE_URL=https://dev-api.example.com
VITE_LOG_LEVEL=debug
VITE_MOCK_ENABLED=true

// .env.production
VITE_API_BASE_URL=https://api.example.com
VITE_LOG_LEVEL=error
VITE_MOCK_ENABLED=false

// .env.staging
VITE_API_BASE_URL=https://staging-api.example.com
VITE_LOG_LEVEL=warn
VITE_ENABLE_ANALYTICS=true

环境变量类型定义:

javascript 复制代码
// env.d.ts
/// <reference types="vite/client" />

interface ImportMetaEnv {
  readonly VITE_APP_TITLE: string
  readonly VITE_APP_VERSION: string
  readonly VITE_API_BASE_URL: string
  readonly VITE_LOG_LEVEL: 'debug' | 'info' | 'warn' | 'error'
  readonly VITE_MOCK_ENABLED: string
  readonly VITE_DEBUG_MODE: string
  readonly VITE_ENABLE_ANALYTICS: string
}

interface ImportMeta {
  readonly env: ImportMetaEnv
}

环境变量使用示例:

javascript 复制代码
// utils/config.js
export const config = {
  // API 配置
  api: {
    baseURL: import.meta.env.VITE_API_BASE_URL,
    timeout: 10000,
    retries: 3
  },
  
  // 应用配置
  app: {
    title: import.meta.env.VITE_APP_TITLE,
    version: import.meta.env.VITE_APP_VERSION,
    debugMode: import.meta.env.VITE_DEBUG_MODE === 'true'
  },
  
  // 功能开关
  features: {
    mockEnabled: import.meta.env.VITE_MOCK_ENABLED === 'true',
    analyticsEnabled: import.meta.env.VITE_ENABLE_ANALYTICS === 'true'
  },
  
  // 日志配置
  logging: {
    level: import.meta.env.VITE_LOG_LEVEL || 'info'
  }
}

// 环境变量验证
function validateEnv() {
  const required = [
    'VITE_API_BASE_URL',
    'VITE_APP_TITLE'
  ]
  
  const missing = required.filter(key => !import.meta.env[key])
  
  if (missing.length > 0) {
    throw new Error(`Missing required environment variables: ${missing.join(', ')}`)
  }
}

validateEnv()
通过 define 选项注入全局变量

define 选项允许在编译时注入全局常量,实现代码的静态替换。

javascript 复制代码
// vite.config.js
export default defineConfig({
  define: {
    // 应用信息
    __APP_INFO__: JSON.stringify({
      name: process.env.npm_package_name,
      version: process.env.npm_package_version,
      buildTime: new Date().toISOString(),
      gitHash: process.env.GIT_COMMIT_HASH || 'unknown'
    }),
    
    // 环境标识
    __DEV__: process.env.NODE_ENV === 'development',
    __PROD__: process.env.NODE_ENV === 'production',
    __TEST__: process.env.NODE_ENV === 'test',
    
    // 功能标识
    __ENABLE_MOCK__: process.env.VITE_MOCK_ENABLED === 'true',
    __ENABLE_PWA__: process.env.VITE_PWA_ENABLED === 'true',
    
    // API 配置
    __API_CONFIG__: JSON.stringify({
      baseURL: process.env.VITE_API_BASE_URL,
      version: process.env.VITE_API_VERSION || 'v1',
      timeout: parseInt(process.env.VITE_API_TIMEOUT || '10000')
    })
  }
})

全局变量使用示例:

javascript 复制代码
// utils/constants.js
export const APP_INFO = __APP_INFO__
export const IS_DEV = __DEV__
export const IS_PROD = __PROD__
export const API_CONFIG = __API_CONFIG__

// 条件编译示例
if (__DEV__) {
  console.log('Development mode enabled')
  console.log('App info:', APP_INFO)
}

// services/api.js
import axios from 'axios'

const apiClient = axios.create({
  baseURL: API_CONFIG.baseURL,
  timeout: API_CONFIG.timeout,
  headers: {
    'X-API-Version': API_CONFIG.version
  }
})

// 开发环境特殊处理
if (__DEV__) {
  apiClient.interceptors.request.use((config) => {
    console.log('API Request:', config)
    return config
  })
}

export default apiClient

文件监听优化

文件监听机制直接影响开发体验,合理的配置可以显著提升响应速度。

调整 server.watch 选项
javascript 复制代码
// vite.config.js
export default defineConfig({
  server: {
    watch: {
      // 使用轮询模式(适用于网络文件系统)
      usePolling: process.env.VITE_USE_POLLING === 'true',
      
      // 轮询间隔(毫秒)
      interval: 1000,
      
      // 二进制轮询间隔
      binaryInterval: 2000,
      
      // 忽略文件/目录
      ignored: [
        '**/node_modules/**',
        '**/.git/**',
        '**/dist/**',
        '**/coverage/**',
        '**/.nyc_output/**',
        '**/tmp/**',
        '**/*.log',
        // 忽略大文件
        '**/*.{mp4,avi,mov,wmv}',
        '**/*.{zip,tar,gz,rar}'
      ],
      
      // 监听深度
      depth: 99,
      
      // 跟踪符号链接
      followSymlinks: true,
      
      // 原子写入检测
      atomic: true,
      
      // 初始扫描时忽略的文件
      ignoreInitial: false
    },
    
    // 文件系统缓存
    fs: {
      // 允许访问的目录
      allow: [
        // 项目根目录
        process.cwd(),
        // 额外的搜索路径
        '../shared-packages',
        '/usr/local/lib/node_modules'
      ],
      
      // 严格模式
      strict: true,
      
      // 拒绝访问的文件
      deny: [
        '.env.local',
        '.env.*.local'
      ]
    }
  }
})
优化开发体验的关键点

1. 智能文件过滤配置:

javascript 复制代码
// utils/watch-config.js
export function createWatchConfig(options = {}) {
  const {
    includePaths = [],
    excludePaths = [],
    monorepoMode = false
  } = options
  
  const defaultIgnored = [
    '**/node_modules/**',
    '**/.git/**',
    '**/dist/**',
    '**/build/**',
    '**/.vscode/**',
    '**/.idea/**',
    '**/coverage/**'
  ]
  
  const monorepoIgnored = monorepoMode ? [
    '**/packages/*/node_modules/**',
    '**/packages/*/dist/**'
  ] : []
  
  return {
    ignored: [
      ...defaultIgnored,
      ...monorepoIgnored,
      ...excludePaths
    ],
    paths: includePaths.length > 0 ? includePaths : ['.']
  }
}

// vite.config.js
import { createWatchConfig } from './utils/watch-config.js'

const watchConfig = createWatchConfig({
  monorepoMode: true,
  excludePaths: ['**/test-results/**']
})

export default defineConfig({
  server: {
    watch: watchConfig
  }
})

2. 条件化监听策略:

javascript 复制代码
// vite.config.js
export default defineConfig({
  server: {
    watch: (() => {
      // Docker 环境使用轮询
      if (process.env.DOCKER === 'true') {
        return {
          usePolling: true,
          interval: 2000
        }
      }
      
      // WSL 环境优化
      if (process.env.WSL_DISTRO_NAME) {
        return {
          usePolling: true,
          interval: 1000,
          binaryInterval: 2000
        }
      }
      
      // 默认配置
      return {
        ignored: ['**/node_modules/**', '**/.git/**']
      }
    })()
  }
})

文件监听优化流程图:

graph TD A[文件变更] --> B{文件类型检查} B -->|忽略文件| C[跳过处理] B -->|源码文件| D[语法检查] B -->|配置文件| E[重新加载配置] D --> F{是否有错误} F -->|有错误| G[显示错误信息] F -->|无错误| H[触发 HMR] E --> I[重启开发服务器] H --> J[更新浏览器] G --> K[等待修复] K --> A J --> L[完成更新] C --> L I --> L

性能监控与调试

构建分析工具集成
javascript 复制代码
// vite.config.js
import { defineConfig } from 'vite'
import { visualizer } from 'rollup-plugin-visualizer'
import { bundleAnalyzer } from 'vite-bundle-analyzer'

export default defineConfig({
  plugins: [
    // 构建分析
    visualizer({
      filename: 'dist/stats.html',
      open: true,
      gzipSize: true,
      brotliSize: true
    }),
    
    // 开发环境性能监控
    process.env.ANALYZE === 'true' && bundleAnalyzer({
      analyzerMode: 'server',
      openAnalyzer: true
    })
  ].filter(Boolean),
  
  // 性能监控
  build: {
    // 报告压缩详情
    reportCompressedSize: true,
    
    // Chunk 大小警告限制
    chunkSizeWarningLimit: 1000,
    
    // Rollup 配置
    rollupOptions: {
      output: {
        // 手动 chunk 分割
        manualChunks: {
          'vue-vendor': ['vue', 'vue-router'],
          'ui-vendor': ['element-plus'],
          'utils-vendor': ['lodash-es', 'dayjs']
        }
      }
    }
  }
})

性能数据收集:

javascript 复制代码
// utils/performance.js
class VitePerformanceMonitor {
  constructor() {
    this.metrics = new Map()
    this.startTime = Date.now()
  }
  
  // 记录开始时间
  start(label) {
    this.metrics.set(label, {
      start: performance.now(),
      end: null,
      duration: null
    })
  }
  
  // 记录结束时间
  end(label) {
    const metric = this.metrics.get(label)
    if (metric) {
      metric.end = performance.now()
      metric.duration = metric.end - metric.start
    }
  }
  
  // 获取性能报告
  getReport() {
    const report = {}
    for (const [label, metric] of this.metrics) {
      if (metric.duration !== null) {
        report[label] = `${metric.duration.toFixed(2)}ms`
      }
    }
    return report
  }
  
  // 输出性能报告
  logReport() {
    console.table(this.getReport())
  }
}

// 全局性能监控实例
export const perfMonitor = new VitePerformanceMonitor()

// HMR 性能监控
if (import.meta.hot) {
  import.meta.hot.on('vite:beforeUpdate', () => {
    perfMonitor.start('hmr-update')
  })
  
  import.meta.hot.on('vite:afterUpdate', () => {
    perfMonitor.end('hmr-update')
    perfMonitor.logReport()
  })
}

Vite 产物构建优化详解

生产环境构建是前端项目性能优化的关键环节。Vite 基于 Rollup 的生产构建机制,通过精细化配置可以实现极致的产物优化,确保最终交付给用户的应用具备最佳的加载速度和运行性能。

生产模式优化

生产模式的核心目标是生成体积最小、性能最佳的构建产物,同时确保代码的可维护性和部署便利性。

使用 build.minify 选项

代码压缩是减少包体积的基础手段,Vite 提供了多种压缩策略供选择。

javascript 复制代码
// vite.config.js
import { defineConfig } from 'vite'

export default defineConfig({
  build: {
    // 压缩方式选择
    minify: 'esbuild', // 'esbuild' | 'terser' | false
    
    // esbuild 压缩配置
    esbuildOptions: {
      // 移除 console 和 debugger
      drop: process.env.NODE_ENV === 'production' ? ['console', 'debugger'] : [],
      
      // 压缩配置
      minifyIdentifiers: true,
      minifySyntax: true,
      minifyWhitespace: true,
      
      // 保留类名(用于调试)
      keepNames: false,
      
      // 法律注释处理
      legalComments: 'none'
    },
    
    // 当使用 terser 时的配置
    terserOptions: {
      compress: {
        // 移除 console
        drop_console: true,
        // 移除 debugger
        drop_debugger: true,
        // 移除无用代码
        dead_code: true,
        // 全局定义
        global_defs: {
          __DEV__: false
        }
      },
      mangle: {
        // 保留类名
        keep_classnames: true,
        // 保留函数名
        keep_fnames: true
      },
      format: {
        // 移除注释
        comments: false
      }
    }
  }
})

高级压缩策略配置:

javascript 复制代码
// utils/build-config.js
export function createBuildConfig(env = 'production') {
  const isProduction = env === 'production'
  const isAnalyze = process.env.ANALYZE === 'true'
  
  return {
    // 构建目标
    target: ['es2020', 'chrome80', 'firefox78', 'safari13'],
    
    // 输出目录
    outDir: `dist/${env}`,
    
    // 静态资源目录
    assetsDir: 'assets',
    
    // 压缩配置
    minify: isProduction ? 'esbuild' : false,
    
    // 源码映射
    sourcemap: isProduction ? false : 'inline',
    
    // CSS 代码分割
    cssCodeSplit: true,
    
    // 生成清单文件
    manifest: isProduction,
    
    // 报告文件大小
    reportCompressedSize: !isAnalyze,
    
    // 文件大小警告阈值
    chunkSizeWarningLimit: 1000,
    
    // esbuild 选项
    esbuildOptions: {
      drop: isProduction ? ['console', 'debugger'] : [],
      define: {
        __PROD__: isProduction,
        __DEV__: !isProduction
      }
    }
  }
}

// vite.config.js
import { createBuildConfig } from './utils/build-config.js'

export default defineConfig({
  build: createBuildConfig(process.env.NODE_ENV)
})
配置 build.rollupOptions

Rollup 选项提供了对构建过程的精细控制,是实现高级优化的关键配置。

javascript 复制代码
// vite.config.js
export default defineConfig({
  build: {
    rollupOptions: {
      // 外部依赖配置
      external: [
        // 将大型库标记为外部依赖
        'vue',
        'vue-router',
        'element-plus'
      ],
      
      // 输入配置
      input: {
        main: 'index.html',
        // 多页面应用
        admin: 'admin.html',
        mobile: 'mobile.html'
      },
      
      // 输出配置
      output: {
        // 全局变量名映射
        globals: {
          vue: 'Vue',
          'vue-router': 'VueRouter',
          'element-plus': 'ElementPlus'
        },
        
        // 手动代码分割
        manualChunks: {
          // 核心框架
          'vue-vendor': ['vue', 'vue-router', '@vue/shared'],
          
          // UI 组件库
          'ui-vendor': ['element-plus', '@element-plus/icons-vue'],
          
          // 工具库
          'utils-vendor': ['lodash-es', 'dayjs', 'axios'],
          
          // 业务模块
          'business-core': [
            './src/store',
            './src/router',
            './src/utils'
          ]
        },
        
        // 文件命名策略
        chunkFileNames: (chunkInfo) => {
          const facadeModuleId = chunkInfo.facadeModuleId
          if (facadeModuleId) {
            // 根据模块路径生成chunk名称
            const moduleName = facadeModuleId.split('/').pop().replace(/\.\w+$/, '')
            return `js/[name]-[hash].${moduleName}.js`
          }
          return 'js/[name]-[hash].js'
        },
        
        // 入口文件命名
        entryFileNames: 'js/[name]-[hash].js',
        
        // 资源文件命名
        assetFileNames: (assetInfo) => {
          const extType = assetInfo.name.split('.').pop()
          const assetTypeMap = {
            css: 'css/[name]-[hash].css',
            png: 'images/[name]-[hash].png',
            jpg: 'images/[name]-[hash].jpg',
            jpeg: 'images/[name]-[hash].jpeg',
            gif: 'images/[name]-[hash].gif',
            svg: 'images/[name]-[hash].svg',
            ico: 'images/[name]-[hash].ico',
            woff: 'fonts/[name]-[hash].woff',
            woff2: 'fonts/[name]-[hash].woff2',
            ttf: 'fonts/[name]-[hash].ttf'
          }
          return assetTypeMap[extType] || 'assets/[name]-[hash].[ext]'
        }
      },
      
      // 插件配置
      plugins: [
        // 生产环境插件
        ...(process.env.NODE_ENV === 'production' ? [
          // 文件大小分析
          bundleAnalyzer(),
          // 压缩优化
          compressionPlugin()
        ] : [])
      ]
    }
  }
})

动态 Rollup 配置策略:

javascript 复制代码
// utils/rollup-config.js
export function createRollupConfig(options = {}) {
  const {
    multiPage = false,
    splitVendor = true,
    externalLibs = [],
    customChunks = {}
  } = options
  
  // 基础外部依赖
  const baseExternal = [
    ...externalLibs,
    ...(process.env.VITE_EXTERNAL_DEPS?.split(',') || [])
  ]
  
  // 基础手动分割配置
  const baseManualChunks = {
    // 基础框架
    framework: ['vue', '@vue/shared', '@vue/runtime-core'],
    
    // 路由和状态管理
    router: ['vue-router', 'pinia'],
    
    // 工具库
    utils: ['lodash-es', 'dayjs', 'axios'],
    
    // 自定义分割
    ...customChunks
  }
  
  // 多页面配置
  const multiPageInput = multiPage ? {
    main: 'index.html',
    admin: 'admin/index.html',
    mobile: 'mobile/index.html'
  } : undefined
  
  return {
    external: baseExternal,
    input: multiPageInput,
    output: {
      manualChunks: splitVendor ? baseManualChunks : undefined,
      
      // 优化的文件命名
      chunkFileNames: (chunkInfo) => {
        // 根据chunk类型生成不同的文件名
        if (chunkInfo.isEntry) {
          return 'js/entry/[name]-[hash].js'
        }
        
        if (chunkInfo.isDynamicEntry) {
          return 'js/dynamic/[name]-[hash].js'
        }
        
        return 'js/chunks/[name]-[hash].js'
      }
    }
  }
}

// vite.config.js
import { createRollupConfig } from './utils/rollup-config.js'

export default defineConfig({
  build: {
    rollupOptions: createRollupConfig({
      multiPage: true,
      splitVendor: true,
      externalLibs: ['vue', 'element-plus'],
      customChunks: {
        'charts': ['echarts', 'd3'],
        'editor': ['monaco-editor']
      }
    })
  }
})

代码分割和懒加载

代码分割是现代前端应用性能优化的核心技术,通过合理的代码拆分可以实现按需加载,显著提升应用的首屏加载速度。

使用 manualChunks 进行手动分割

手动代码分割提供了对打包结果的精确控制,是优化加载性能的重要手段。

javascript 复制代码
// vite.config.js
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        // 策略化手动分割
        manualChunks: (id) => {
          // Node modules 处理
          if (id.includes('node_modules')) {
            // 大型库单独分割
            if (id.includes('echarts')) {
              return 'echarts'
            }
            if (id.includes('monaco-editor')) {
              return 'monaco-editor'
            }
            if (id.includes('element-plus')) {
              return 'element-plus'
            }
            
            // 框架核心
            if (id.includes('vue') || id.includes('@vue')) {
              return 'vue-vendor'
            }
            
            // 路由相关
            if (id.includes('vue-router') || id.includes('pinia')) {
              return 'router-vendor'
            }
            
            // 工具库
            if (id.includes('lodash') || id.includes('dayjs') || id.includes('axios')) {
              return 'utils-vendor'
            }
            
            // 其他依赖
            return 'vendor'
          }
          
          // 业务模块分割
          if (id.includes('/src/views/')) {
            // 按路由模块分割
            const routeMatch = id.match(/\/src\/views\/([^\/]+)/)
            if (routeMatch) {
              return `route-${routeMatch[1]}`
            }
          }
          
          if (id.includes('/src/components/')) {
            // 组件库分割
            return 'components'
          }
          
          if (id.includes('/src/utils/')) {
            // 工具函数分割
            return 'utils'
          }
        }
      }
    }
  }
})

智能分割策略配置:

javascript 复制代码
// utils/chunk-strategy.js
export class ChunkStrategy {
  constructor(options = {}) {
    this.options = {
      // 默认配置
      vendorSizeLimit: 500 * 1024, // 500KB
      chunkSizeLimit: 200 * 1024,  // 200KB
      maxChunks: 20,
      ...options
    }
    
    this.chunkSizes = new Map()
  }
  
  // 获取模块大小(估算)
  getModuleSize(id) {
    // 简单的大小估算逻辑
    if (id.includes('echarts')) return 800 * 1024
    if (id.includes('monaco-editor')) return 1500 * 1024
    if (id.includes('element-plus')) return 600 * 1024
    if (id.includes('vue')) return 100 * 1024
    return 10 * 1024 // 默认大小
  }
  
  // 智能分割策略
  manualChunks(id) {
    // 超大库独立分割
    const largeLibraries = [
      'echarts',
      'monaco-editor',
      '@antv/g6',
      'three'
    ]
    
    for (const lib of largeLibraries) {
      if (id.includes(lib)) {
        return lib.replace(/[@\/]/g, '-')
      }
    }
    
    // Node modules 分类
    if (id.includes('node_modules')) {
      return this.categorizeNodeModule(id)
    }
    
    // 业务代码分类
    return this.categorizeBusiness(id)
  }
  
  categorizeNodeModule(id) {
    const categories = {
      'vue-core': ['vue', '@vue', 'vue-router', 'pinia'],
      'ui-framework': ['element-plus', 'ant-design-vue', 'naive-ui'],
      'utility': ['lodash', 'dayjs', 'moment', 'axios', 'qs'],
      'charts': ['echarts', 'd3', '@antv', 'chart.js'],
      'editor': ['monaco-editor', 'codemirror', '@codemirror']
    }
    
    for (const [category, packages] of Object.entries(categories)) {
      if (packages.some(pkg => id.includes(pkg))) {
        return category
      }
    }
    
    return 'vendor'
  }
  
  categorizeBusiness(id) {
    if (id.includes('/src/views/')) {
      // 按页面分割
      const pageMatch = id.match(/\/src\/views\/([^\/]+)/)
      return pageMatch ? `page-${pageMatch[1]}` : 'pages'
    }
    
    if (id.includes('/src/components/business/')) {
      // 业务组件
      return 'business-components'
    }
    
    if (id.includes('/src/components/')) {
      // 通用组件
      return 'common-components'
    }
    
    return undefined // 默认chunk
  }
}

// vite.config.js
import { ChunkStrategy } from './utils/chunk-strategy.js'

const chunkStrategy = new ChunkStrategy({
  vendorSizeLimit: 800 * 1024,
  maxChunks: 15
})

export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks: (id) => chunkStrategy.manualChunks(id)
      }
    }
  }
})
动态导入模块

动态导入是实现按需加载的基础,合理使用可以显著减少初始包大小。

javascript 复制代码
// router/index.js - 路由懒加载
import { createRouter, createWebHistory } from 'vue-router'

// 路由懒加载配置
const routes = [
  {
    path: '/',
    name: 'Home',
    component: () => import('../views/Home.vue'),
    meta: {
      // 预加载策略
      preload: true,
      // 缓存策略
      keepAlive: true
    }
  },
  {
    path: '/dashboard',
    name: 'Dashboard',
    component: () => import('../views/Dashboard.vue'),
    children: [
      {
        path: 'analytics',
        component: () => import('../views/dashboard/Analytics.vue')
      },
      {
        path: 'reports',
        component: () => import('../views/dashboard/Reports.vue')
      }
    ]
  },
  {
    path: '/admin',
    name: 'Admin',
    // 按功能模块分组加载
    component: () => import('../views/admin/Layout.vue'),
    children: [
      {
        path: 'users',
        component: () => import('../views/admin/Users.vue')
      },
      {
        path: 'settings',
        component: () => import('../views/admin/Settings.vue')
      }
    ]
  }
]

export const router = createRouter({
  history: createWebHistory(),
  routes
})

高级动态导入策略:

javascript 复制代码
// utils/dynamic-import.js
export class DynamicImportManager {
  constructor() {
    this.loadedModules = new Set()
    this.loadingPromises = new Map()
    this.preloadQueue = []
  }
  
  // 智能导入模块
  async import(moduleFactory, options = {}) {
    const {
      retries = 3,
      timeout = 10000,
      fallback = null,
      cache = true
    } = options
    
    const moduleKey = moduleFactory.toString()
    
    // 缓存检查
    if (cache && this.loadingPromises.has(moduleKey)) {
      return this.loadingPromises.get(moduleKey)
    }
    
    const importPromise = this.executeImport(moduleFactory, retries, timeout, fallback)
    
    if (cache) {
      this.loadingPromises.set(moduleKey, importPromise)
    }
    
    try {
      const module = await importPromise
      this.loadedModules.add(moduleKey)
      return module
    } catch (error) {
      this.loadingPromises.delete(moduleKey)
      throw error
    }
  }
  
  // 执行导入逻辑
  async executeImport(moduleFactory, retries, timeout, fallback) {
    for (let attempt = 1; attempt <= retries; attempt++) {
      try {
        return await Promise.race([
          moduleFactory(),
          new Promise((_, reject) => 
            setTimeout(() => reject(new Error('Import timeout')), timeout)
          )
        ])
      } catch (error) {
        console.warn(`Import attempt ${attempt} failed:`, error)
        
        if (attempt === retries) {
          if (fallback) {
            console.warn('Using fallback module')
            return fallback()
          }
          throw error
        }
        
        // 延迟重试
        await new Promise(resolve => setTimeout(resolve, attempt * 1000))
      }
    }
  }
  
  // 预加载模块
  preload(moduleFactory, priority = 'low') {
    if (this.loadedModules.has(moduleFactory.toString())) {
      return Promise.resolve()
    }
    
    return new Promise((resolve) => {
      this.preloadQueue.push({
        factory: moduleFactory,
        priority,
        resolve
      })
      
      // 按优先级处理预加载队列
      this.processPreloadQueue()
    })
  }
  
  // 处理预加载队列
  processPreloadQueue() {
    if (this.preloadQueue.length === 0) return
    
    // 按优先级排序
    this.preloadQueue.sort((a, b) => {
      const priorities = { high: 3, medium: 2, low: 1 }
      return priorities[b.priority] - priorities[a.priority]
    })
    
    // 使用 requestIdleCallback 在空闲时预加载
    const processNext = () => {
      if (this.preloadQueue.length === 0) return
      
      const { factory, resolve } = this.preloadQueue.shift()
      
      this.import(factory, { cache: true })
        .then(resolve)
        .catch(resolve)
        .finally(() => {
          if (this.preloadQueue.length > 0) {
            requestIdleCallback(processNext)
          }
        })
    }
    
    requestIdleCallback(processNext)
  }
}

// 全局导入管理器
export const importManager = new DynamicImportManager()

// 使用示例
// components/AsyncComponent.vue
export default {
  async setup() {
    // 智能导入组件
    const ChartComponent = await importManager.import(
      () => import('./charts/LineChart.vue'),
      {
        retries: 3,
        timeout: 5000,
        fallback: () => import('./charts/FallbackChart.vue')
      }
    )
    
    // 预加载相关组件
    importManager.preload(
      () => import('./charts/BarChart.vue'),
      'medium'
    )
    
    return { ChartComponent }
  }
}

代码分割优化流程图:

graph TD A[构建开始] --> B[模块依赖分析] B --> C{模块类型判断} C -->|第三方库| D[Vendor分割策略] C -->|业务代码| E[业务模块分割] C -->|共享模块| F[共享代码提取] D --> G[大小阈值检查] E --> G F --> G G -->|超过阈值| H[进一步分割] G -->|符合要求| I[生成Chunk] H --> I I --> J[动态导入点分析] J --> K[异步Chunk生成] K --> L[优化完成]

资源优化

资源优化是提升应用加载性能的重要环节,通过对图片、字体、样式等静态资源的精细化处理,可以显著减少资源体积和请求数量。

图片和字体资源优化
javascript 复制代码
// vite.config.js
import { defineConfig } from 'vite'
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
import viteImageOptimize from 'vite-plugin-imagemin'

export default defineConfig({
  plugins: [
    // SVG 图标优化
    createSvgIconsPlugin({
      iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')],
      symbolId: 'icon-[dir]-[name]',
      svgoOptions: {
        plugins: [
          {
            name: 'removeViewBox',
            active: false
          },
          {
            name: 'removeEmptyAttrs',
            active: true
          }
        ]
      }
    }),
    
    // 图片压缩优化
    viteImageOptimize({
      gifsicle: { optimizationLevel: 7 },
      mozjpeg: { quality: 85 },
      optipng: { optimizationLevel: 7 },
      pngquant: {
        quality: [0.8, 0.9],
        speed: 4
      },
      svgo: {
        plugins: [
          { name: 'removeViewBox', active: false },
          { name: 'removeEmptyAttrs', active: true },
          { name: 'removeUselessStrokeAndFill', active: true }
        ]
      },
      webp: { quality: 85 }
    })
  ],
  
  build: {
    rollupOptions: {
      output: {
        // 资源文件分类存放
        assetFileNames: (assetInfo) => {
          const extType = assetInfo.name.split('.').pop().toLowerCase()
          
          // 图片资源
          if (['png', 'jpg', 'jpeg', 'gif', 'svg', 'webp', 'avif'].includes(extType)) {
            return `images/[name]-[hash][extname]`
          }
          
          // 字体资源
          if (['woff', 'woff2', 'eot', 'ttf', 'otf'].includes(extType)) {
            return `fonts/[name]-[hash][extname]`
          }
          
          // 样式文件
          if (extType === 'css') {
            return `css/[name]-[hash][extname]`
          }
          
          // 其他资源
          return `assets/[name]-[hash][extname]`
        }
      }
    }
  }
})

智能图片处理策略:

javascript 复制代码
// utils/image-optimizer.js
export class ImageOptimizer {
  constructor(options = {}) {
    this.options = {
      // 默认质量配置
      jpeg: { quality: 85, progressive: true },
      png: { quality: [0.8, 0.9], speed: 4 },
      webp: { quality: 85, method: 6 },
      avif: { quality: 80, speed: 2 },
      
      // 响应式图片断点
      breakpoints: [320, 640, 768, 1024, 1366, 1920],
      
      // 格式优先级
      formatPriority: ['avif', 'webp', 'png', 'jpeg'],
      
      ...options
    }
  }
  
  // 生成响应式图片配置
  generateResponsiveConfig(imagePath) {
    const config = {
      formats: [],
      sizes: []
    }
    
    // 为每个断点生成不同格式
    this.options.breakpoints.forEach(width => {
      this.options.formatPriority.forEach(format => {
        if (this.shouldGenerateFormat(format, width)) {
          config.formats.push({
            format,
            width,
            quality: this.options[format]?.quality || 85,
            outputPath: `images/${format}/${width}w/`
          })
        }
      })
    })
    
    return config
  }
  
  shouldGenerateFormat(format, width) {
    // AVIF 仅为大图生成
    if (format === 'avif' && width < 768) return false
    
    // WebP 为主要格式
    if (format === 'webp') return true
    
    // PNG 保留原始质量
    if (format === 'png') return width >= 1024
    
    // JPEG 作为兜底
    return format === 'jpeg'
  }
}

// 字体优化配置
export const fontOptimization = {
  // 字体格式转换
  formats: ['woff2', 'woff'],
  
  // 字符子集化
  subset: {
    chinese: '常用汉字集合',
    latin: 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
    symbols: '!@#$%^&*()_+-=[]{}|;:,.<>?'
  },
  
  // 字体显示策略
  display: 'swap', // 'auto' | 'block' | 'swap' | 'fallback' | 'optional'
  
  // 预加载策略
  preload: [
    { family: 'PingFang SC', weight: '400' },
    { family: 'Helvetica Neue', weight: '400' }
  ]
}

// CSS 中的字体优化
const optimizedFontCSS = `
@font-face {
  font-family: 'OptimizedFont';
  src: url('/fonts/font.woff2') format('woff2'),
       url('/fonts/font.woff') format('woff');
  font-display: swap;
  font-weight: 400;
  font-style: normal;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153;
}

/* 字体回退栈 */
.text-primary {
  font-family: 'OptimizedFont', 
               'PingFang SC', 
               'Microsoft YaHei', 
               'Helvetica Neue', 
               Arial, 
               sans-serif;
}
`
控制资源内联

合理的资源内联策略可以减少 HTTP 请求数量,提升加载性能。

javascript 复制代码
// vite.config.js
export default defineConfig({
  build: {
    // 内联资源阈值 (字节)
    assetsInlineLimit: 4096, // 4KB
    
    rollupOptions: {
      output: {
        // 自定义内联策略
        assetFileNames: (assetInfo) => {
          const size = assetInfo.source?.length || 0
          const extType = assetInfo.name.split('.').pop().toLowerCase()
          
          // 小图片内联为 base64
          if (['png', 'jpg', 'jpeg', 'gif', 'svg'].includes(extType) && size < 8192) {
            return false // 返回 false 表示内联
          }
          
          // 小字体文件内联
          if (['woff', 'woff2'].includes(extType) && size < 16384) {
            return false
          }
          
          // 正常文件路径
          return `assets/[name]-[hash][extname]`
        }
      }
    }
  },
  
  // CSS 内联优化
  css: {
    // 代码分割阈值
    codegenOptions: {
      inlineThreshold: 2048 // 2KB
    }
  }
})

高级资源内联策略:

javascript 复制代码
// utils/asset-inline-strategy.js
export class AssetInlineStrategy {
  constructor(options = {}) {
    this.options = {
      // 默认阈值配置
      thresholds: {
        image: 4096,    // 4KB
        font: 8192,     // 8KB
        css: 2048,      // 2KB
        js: 1024        // 1KB
      },
      
      // 强制内联的文件
      forceInline: [
        /critical\.css$/,
        /icons?\.svg$/,
        /favicon\./
      ],
      
      // 强制不内联的文件
      forceExternal: [
        /\.woff2?$/,
        /large-image\./
      ],
      
      ...options
    }
  }
  
  // 判断是否应该内联
  shouldInline(filePath, content, size) {
    const fileName = path.basename(filePath)
    const extType = path.extname(filePath).slice(1).toLowerCase()
    
    // 强制内联检查
    if (this.options.forceInline.some(pattern => pattern.test(fileName))) {
      return true
    }
    
    // 强制外链检查
    if (this.options.forceExternal.some(pattern => pattern.test(fileName))) {
      return false
    }
    
    // 大小阈值检查
    const threshold = this.options.thresholds[this.getAssetCategory(extType)]
    return size <= threshold
  }
  
  getAssetCategory(extType) {
    const categoryMap = {
      'png': 'image', 'jpg': 'image', 'jpeg': 'image', 'gif': 'image', 'svg': 'image',
      'woff': 'font', 'woff2': 'font', 'ttf': 'font', 'eot': 'font',
      'css': 'css',
      'js': 'js'
    }
    
    return categoryMap[extType] || 'other'
  }
  
  // 生成内联资源
  generateInlineAsset(content, mimeType) {
    if (typeof content === 'string') {
      // 文本内容直接返回
      return content
    }
    
    // 二进制内容转为 base64
    const base64 = Buffer.from(content).toString('base64')
    return `data:${mimeType};base64,${base64}`
  }
}

// vite plugin 集成
import { AssetInlineStrategy } from './asset-inline-strategy.js'

export function createAssetInlinePlugin(options = {}) {
  const strategy = new AssetInlineStrategy(options)
  
  return {
    name: 'asset-inline',
    generateBundle(options, bundle) {
      Object.keys(bundle).forEach(fileName => {
        const chunk = bundle[fileName]
        
        if (chunk.type === 'asset') {
          const shouldInline = strategy.shouldInline(
            fileName,
            chunk.source,
            chunk.source.length
          )
          
          if (shouldInline) {
            // 内联处理逻辑
            this.handleInlineAsset(chunk, fileName)
          }
        }
      })
    },
    
    handleInlineAsset(chunk, fileName) {
      // 实现内联逻辑
      const mimeType = this.getMimeType(fileName)
      const inlineContent = strategy.generateInlineAsset(chunk.source, mimeType)
      
      // 更新引用
      this.updateAssetReferences(fileName, inlineContent)
    }
  }
}

分析工具

构建分析工具是优化的重要辅助手段,通过可视化的方式帮助开发者理解打包结果和性能瓶颈。

使用 rollup-plugin-visualizer 插件
javascript 复制代码
// vite.config.js
import { defineConfig } from 'vite'
import { visualizer } from 'rollup-plugin-visualizer'

export default defineConfig({
  plugins: [
    // 构建分析插件
    visualizer({
      // 输出文件名
      filename: 'dist/bundle-analysis.html',
      
      // 自动打开分析报告
      open: process.env.ANALYZE === 'true',
      
      // 显示 gzip 大小
      gzipSize: true,
      
      // 显示 brotli 大小
      brotliSize: true,
      
      // 模板类型
      template: 'treemap', // 'treemap' | 'sunburst' | 'network'
      
      // 自定义标题
      title: 'Bundle Analysis Report',
      
      // 项目根目录
      projectRoot: process.cwd(),
      
      // 源码映射
      sourcemap: true
    })
  ]
})

多维度分析配置:

javascript 复制代码
// utils/analysis-config.js
export function createAnalysisPlugins(env = 'production') {
  const plugins = []
  
  if (env === 'production' || process.env.ANALYZE) {
    // 基础分析报告
    plugins.push(
      visualizer({
        filename: 'dist/analysis/treemap.html',
        template: 'treemap',
        gzipSize: true,
        brotliSize: true
      })
    )
    
    // 网络图分析
    plugins.push(
      visualizer({
        filename: 'dist/analysis/network.html',
        template: 'network',
        gzipSize: true
      })
    )
    
    // 旭日图分析
    plugins.push(
      visualizer({
        filename: 'dist/analysis/sunburst.html',
        template: 'sunburst',
        gzipSize: true
      })
    )
  }
  
  return plugins
}

// 性能分析插件
export function createPerformanceAnalyzer() {
  return {
    name: 'performance-analyzer',
    
    buildStart() {
      this.startTime = Date.now()
      this.moduleCount = 0
      this.chunkSizes = new Map()
    },
    
    resolveId(id) {
      this.moduleCount++
    },
    
    generateBundle(options, bundle) {
      // 收集chunk信息
      Object.entries(bundle).forEach(([fileName, chunk]) => {
        if (chunk.type === 'chunk') {
          this.chunkSizes.set(fileName, {
            size: chunk.code.length,
            modules: Object.keys(chunk.modules || {}).length,
            imports: chunk.imports?.length || 0,
            exports: chunk.exports?.length || 0
          })
        }
      })
    },
    
    buildEnd() {
      const buildTime = Date.now() - this.startTime
      
      // 生成性能报告
      const report = {
        buildTime: `${buildTime}ms`,
        moduleCount: this.moduleCount,
        chunkCount: this.chunkSizes.size,
        chunks: Array.from(this.chunkSizes.entries()).map(([name, info]) => ({
          name,
          ...info,
          sizeKB: (info.size / 1024).toFixed(2)
        })).sort((a, b) => b.size - a.size)
      }
      
      // 输出性能报告
      console.table(report.chunks)
      console.log('\n📊 构建性能报告:')
      console.log(`⏱️  构建时间: ${report.buildTime}`)
      console.log(`📦 模块数量: ${report.moduleCount}`)
      console.log(`🗂️  Chunk数量: ${report.chunkCount}`)
      
      // 写入详细报告
      require('fs').writeFileSync(
        'dist/performance-report.json',
        JSON.stringify(report, null, 2)
      )
    }
  }
}

// vite.config.js
import { createAnalysisPlugins, createPerformanceAnalyzer } from './utils/analysis-config.js'

export default defineConfig({
  plugins: [
    ...createAnalysisPlugins(process.env.NODE_ENV),
    createPerformanceAnalyzer()
  ]
})

构建优化分析流程图:

graph TD A[构建开始] --> B[模块解析统计] B --> C[依赖关系分析] C --> D[代码分割执行] D --> E[资源处理] E --> F[压缩优化] F --> G[生成分析数据] G --> H{生成报告类型} H -->|TreeMap| I[树状图报告] H -->|Network| J[网络关系图] H -->|Sunburst| K[旭日图报告] H -->|Performance| L[性能分析报告] I --> M[输出到dist/analysis/] J --> M K --> M L --> M M --> N[构建完成]

补充概念和官方文档参考

核心概念总结

1. 代码分割策略层级

  • 入口分割(Entry Splitting): 多页面应用的基础分割
  • 供应商分割(Vendor Splitting): 第三方库的独立分割
  • 动态分割(Dynamic Splitting): 按需加载的路由和组件分割
  • 共享模块分割(Shared Module Splitting): 公共代码的提取和复用

2. 性能优化指标

  • 首次内容绘制(FCP): 首屏渲染时间
  • 最大内容绘制(LCP): 主要内容加载时间
  • 累积布局偏移(CLS): 视觉稳定性
  • 首次输入延迟(FID): 交互响应性
javascript 复制代码
// 性能监控集成示例
export const performanceMonitor = {
  // 关键性能指标收集
  collectVitals() {
    // Web Vitals 集成
    import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
      getCLS(console.log)
      getFID(console.log)
      getFCP(console.log)
      getLCP(console.log)
      getTTFB(console.log)
    })
  },
  
  // 资源加载性能
  collectResourceTiming() {
    const observer = new PerformanceObserver((list) => {
      list.getEntries().forEach((entry) => {
        if (entry.entryType === 'resource') {
          console.log(`${entry.name}: ${entry.duration}ms`)
        }
      })
    })
    
    observer.observe({ entryTypes: ['resource'] })
  }
}
官方文档参考链接

Vite 官方文档

Rollup 官方文档

性能优化最佳实践

相关推荐
孟祥_成都几秒前
AI 术语满天飞?90% 的人只懂名词,不懂为什么!
前端·人工智能
Lupino27 分钟前
被 React “玩弄”的 24 小时:为了修一个不存在的 Bug,我给大模型送了顿火锅钱
前端·react.js
米丘34 分钟前
了解 Javascript 模块化,更好地掌握 Vite 、Webpack、Rollup 等打包工具
前端
Heo35 分钟前
深入 React19 Diff 算法
前端·javascript·面试
滕青山37 分钟前
个人所得税计算器 在线工具核心JS实现
前端·javascript·vue.js
小怪点点37 分钟前
手写promise
前端·promise
国思RDIF框架1 小时前
RDIFramework.NET Web 敏捷开发框架 V6.3 发布 (.NET8+、Framework 双引擎)
前端
Mintopia1 小时前
如何在有限的时间里,活出几倍的人生
前端
炫饭第一名1 小时前
速通Canvas指北🦮——变形、渐变与阴影篇
前端·javascript·程序员
Neptune11 小时前
让我带你迅速吃透React组件通信:从入门到精通(上篇)
前端·javascript