编译构建与打包全面优化之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 开发模式优化流程图:
环境变量配置
环境变量的合理配置是项目部署和多环境管理的基础,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/**']
}
})()
}
})
文件监听优化流程图:
性能监控与调试
构建分析工具集成
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 }
}
}
代码分割优化流程图:
资源优化
资源优化是提升应用加载性能的重要环节,通过对图片、字体、样式等静态资源的精细化处理,可以显著减少资源体积和请求数量。
图片和字体资源优化
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()
]
})
构建优化分析流程图:
补充概念和官方文档参考
核心概念总结
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 官方文档
性能优化最佳实践