Vite 进阶配置:插件开发、按需加载与生产环境优化

Vite 进阶配置:插件开发、按需加载与生产环境优化


目标与场景

  • 插件开发:编写可复用的 Vite 插件(基于 Rollup 插件接口 + Vite 拓展钩子)
  • 按需加载:路由/组件/资源的懒加载与自动导入,提高首屏与交互性能
  • 生产优化:分包策略、预构建、资源提示与分析,稳定降低包体与构建时间

插件架构与钩子

  • 基于 Rollup 插件体系,Vite 额外提供 configureServertransformIndexHtmlhandleHotUpdate
  • 插件编写要点:
    • name 唯一、apply 指定运行阶段('serve' | 'build' | 'client' | 'server'
    • 使用 enforce: 'pre' | 'post' 控制钩子前后顺序

示例:注入构建 banner + 开发阶段虚拟模块

ts 复制代码
// vite-plugin-banner.ts
import type { Plugin } from 'vite'
export default function bannerPlugin(text = 'Built with Vite'): Plugin {
  return {
    name: 'vite-plugin-banner',
    apply: 'build',
    generateBundle(_, bundle) {
      for (const f of Object.values(bundle)) {
        if (f.type === 'chunk') f.code = `/* ${text} */\n` + f.code
      }
    }
  }
}

// vite-plugin-virtual.ts
import type { Plugin } from 'vite'
const ID = 'virtual:env'
export default function virtualEnvPlugin(): Plugin {
  return {
    name: 'vite-plugin-virtual-env',
    resolveId(id) { return id === ID ? id : null },
    load(id) { if (id === ID) return `export const mode = '${process.env.NODE_ENV}'` },
    handleHotUpdate(ctx) {
      if (ctx.file.includes('.env')) ctx.server.ws.send({ type: 'full-reload' })
    }
  }
}

示例:注入 HTML 资源提示

ts 复制代码
// vite-plugin-preload.ts
import type { Plugin } from 'vite'
export function preloadPlugin(): Plugin {
  return {
    name: 'vite-plugin-preload',
    transformIndexHtml(html) {
      const hint = `<link rel="preconnect" href="https://cdn.example.com">`
      return html.replace('</head>', `${hint}</head>`)
    }
  }
}

开发服务器拓展

  • 通过 configureServer 注入中间件,或调用 server.ws.send 实现自定义热更新
ts 复制代码
// vite-plugin-middleware.ts
import type { Plugin } from 'vite'
export function middlewarePlugin(): Plugin {
  return {
    name: 'vite-plugin-middleware',
    apply: 'serve',
    configureServer(server) {
      server.middlewares.use('/health', (_req, res) => { res.statusCode = 200; res.end('ok') })
    }
  }
}

按需加载策略

  • 路由懒加载:() => import('...') 动态导入分离路由包
ts 复制代码
// vue-router
const routes = [ { path: '/', component: () => import('@/pages/Home.vue') } ]
  • 文件式路由与批量懒加载:
ts 复制代码
const pages = import.meta.glob('/src/pages/**/*.vue', { eager: false })
  • 组件按需与自动导入:
ts 复制代码
// vite.config.ts
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
export default { plugins: [
  AutoImport({ imports: ['vue','vue-router','pinia'] }),
  Components({ resolvers: [ElementPlusResolver()] })
] }
  • 资源懒加载:图片 loading="lazy"、大型组件 defineAsyncComponent、非关键脚本 type=module + async/defer

生产环境优化配置

  • 分包策略与手动切块:
ts 复制代码
// vite.config.ts(Rollup 配置)
import { defineConfig } from 'vite'
export default defineConfig({
  build: {
    sourcemap: true,
    chunkSizeWarningLimit: 800,
    rollupOptions: {
      output: {
        manualChunks: {
          framework: ['vue','vue-router','pinia'],
          ui: ['element-plus'],
          chart: ['echarts']
        }
      }
    }
  }
})
  • 预构建与依赖优化:
ts 复制代码
export default defineConfig({
  optimizeDeps: { include: ['lodash-es','dayjs'], exclude: ['big-lib'] }
})
  • 资源提示与公共路径:
ts 复制代码
export default defineConfig({
  base: 'https://cdn.example.com/',
  build: { assetsInlineLimit: 8 * 1024 }
})
  • CSS 与图片:开启 CSS 代码分割(默认),图片使用 AVIF/WebP 与响应式 srcset
  • Env 与编译:define 注入常量、esbuild 目标(target: 'es2017')减少转译成本

SSR/SSG 与 Streaming(可选)

  • ssr.noExternal 控制外部依赖打包;vite-plugin-ssr 或框架内置(Nuxt/Next)实现 SSR/SSG
  • HTML Streaming:结合框架 Suspense 输出,提高可感知速度

构建分析与检查

  • 可视化:
ts 复制代码
import { visualizer } from 'rollup-plugin-visualizer'
export default defineConfig({ build: { rollupOptions: { plugins: [visualizer({ filename: 'stats.html' })] } } })
  • 插件诊断:vite-plugin-inspect 查看插件链与转换结果
ts 复制代码
import Inspect from 'vite-plugin-inspect'
export default defineConfig({ plugins: [Inspect()] })

Dev 与 Proxy

ts 复制代码
export default defineConfig({
  server: {
    proxy: {
      '/api': { target: 'https://api.example.com', changeOrigin: true, rewrite: p => p.replace(/^\/api/, '') }
    }
  }
})

生产脚本与 CI

  • 使用 CI=true vite build --mode production 固化生产模式;在 CI 缓存 node_modules.vite 目录
  • 结合 pnpm i --frozen-lockfile 与构建日志采集,形成度量闭环

常见坑与修复

  • 预构建冲突:optimizeDeps.include/exclude 精确控制;避免同时引入 CJS/ESM 导致重复
  • 过度分包导致请求过多:为首屏路由保留合并,后续路由按需分割
  • 插件顺序:enforce 配置不当导致转换顺序错误;通过 vite-plugin-inspect 排查
  • CDN 路径与资源 404:base 与静态资源路径需一致;部署端正确配置缓存与回源

实战清单(12 项)

  • 编写通用插件:banner、virtual、preload 与中间件注入
  • 路由/文件式懒加载:import()import.meta.glob
  • 自动导入:unplugin-auto-import 与组件按需 unplugin-vue-components
  • 分包:框架/UI/图表独立与首屏合并;manualChunks
  • 预构建:optimizeDeps 的 include/exclude 管理
  • CDN 与公共路径:baseassetsInlineLimit
  • 图片与字体:现代格式与 font-display: swap
  • Env 与编译:defineesbuild 目标
  • SSR/SSG(可选):ssr.noExternal 与 Streaming
  • 分析:visualizer 与 inspect
  • DevServer 与代理:server.proxy 与中间件
  • CI:锁定依赖、缓存目录与构建日志度量

结果与总结

  • 通过插件与按需策略,首屏包体与交互性能显著改善;生产构建更稳定、分析与度量更清晰
  • 建议以"度量→策略→验证"的闭环长期维护,结合 CDN 与缓存策略持续优化用户体验

高级插件示例

ts 复制代码
import type { Plugin } from 'vite'
export function rewritePlugin(): Plugin {
  return {
    name: 'vite-plugin-rewrite',
    enforce: 'pre',
    transform(code, id) {
      if (id.endsWith('.ts') || id.endsWith('.js')) return code.replace(/@\//g, '/src/')
    }
  }
}

预构建与扫描优化

ts 复制代码
import { defineConfig } from 'vite'
export default defineConfig({
  optimizeDeps: {
    entries: ['src/main.ts'],
    include: ['lodash-es','dayjs'],
    esbuildOptions: { target: 'es2017' }
  }
})

模块预加载

ts 复制代码
export default defineConfig({ build: { modulePreload: { polyfill: true } } })

构建度量

bash 复制代码
CI=true vite build --mode production --profile

文件系统与 HMR

ts 复制代码
export default defineConfig({ server: { fs: { strict: true }, hmr: { overlay: true } } })

更多问题与定位

  • 插件覆盖与顺序:调整 enforce 并使用 vite-plugin-inspect 观察链路
  • CJS 与 ESM 混用:通过 optimizeDeps.exclude 排除并统一使用 ESM 入口
  • 资源哈希与缓存:校验 build.assetsInlineLimit 与输出哈希策略

度量目标

  • 首屏路由包体下降 30% 以上
  • 构建时间下降 40% 以上,二次构建下降 70% 以上
  • 预构建扫描耗时下降 50% 以上
相关推荐
一只小风华~11 小时前
Vue.js 核心知识点全面解析
前端·javascript·vue.js
2022.11.7始学前端11 小时前
n8n第七节 只提醒重要的待办
前端·javascript·ui·n8n
SakuraOnTheWay11 小时前
React Grab实践 | 记一次与Cursor的有趣对话
前端·cursor
阿星AI工作室11 小时前
gemini3手势互动圣诞树保姆级教程来了!附提示词
前端·人工智能
徐小夕11 小时前
知识库创业复盘:从闭源到开源,这3个教训价值百万
前端·javascript·github
ZouZou老师11 小时前
C++设计模式之适配器模式:以家具生产为例
java·设计模式·适配器模式
xhxxx11 小时前
函数执行完就销毁?那闭包里的变量凭什么活下来!—— 深入 JS 内存模型
前端·javascript·ecmascript 6
StarkCoder11 小时前
求求你试试 DiffableDataSource!别再手算 indexPath 了(否则迟早崩)
前端
fxshy11 小时前
Cursor 前端Global Cursor Rules
前端·cursor
红彤彤11 小时前
前端接入sse(EventSource)(@fortaine/fetch-event-source)
前端