编译构建与打包全面优化之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 官方文档

性能优化最佳实践

相关推荐
鼠鼠我捏,要死了捏20 分钟前
MongoDB性能优化实战指南:原理、实践与案例
数据库·mongodb·性能优化
LaoZhangAI23 分钟前
Kiro vs Cursor:2025年AI编程IDE深度对比
前端·后端
止观止26 分钟前
CSS3 粘性定位解析:position sticky
前端·css·css3
爱编程的喵36 分钟前
深入理解JavaScript单例模式:从Storage封装到Modal弹窗的实战应用
前端·javascript
lemon_sjdk1 小时前
Java飞机大战小游戏(升级版)
java·前端·python
G等你下课1 小时前
如何用 useReducer + useContext 构建全局状态管理
前端·react.js
欧阳天羲1 小时前
AI 增强大前端数据加密与隐私保护:技术实现与合规遵
前端·人工智能·状态模式
慧一居士1 小时前
Axios 和Express 区别对比
前端
I'mxx1 小时前
【html常见页面布局】
前端·css·html