Vite 插件Vue 项目中的落地实践

2 阅读7分钟

一、为什么要关注 Vite 插件体系

在小型项目中,vite.config.ts 往往只需要配置 Vue 插件、别名和开发代理。但在企业级后台项目中,Vite 配置通常还承担以下职责:

  • 编译 Vue SFC、JSX 和 TSX;
  • 集成 Vue I18n、SCSS 主题和 SVG 组件;
  • 在开发阶段提供 Mock、代码检查和源码定位能力;
  • 在生产构建阶段处理 CDN、压缩、日志清理和构建分析;
  • 根据不同环境和 npm 脚本切换插件行为。

因此,Vite 插件列表并不是一组简单的依赖,而是项目工程能力的集中体现。插件之间的顺序、启用条件和配置边界,都会影响开发体验、构建结果和线上安全。

本文以当前项目为例,分析 build/plugins.tsvite.config.ts 是如何组织这套插件体系的。

二、项目中的配置入口

项目主要通过下面两个文件组织 Vite 配置:

vite.config.ts       # 环境变量、构建、开发服务器和插件入口
build/plugins.ts     # Vite 插件组合
build/utils.ts       # 路径、环境变量和项目信息工具
build/cdn.ts         # CDN 外置依赖配置
build/compress.ts    # 资源压缩配置
build/optimize.ts    # 依赖预构建配置

vite.config.ts 负责读取环境变量并调用插件工厂:

const {
  VITE_CDN,
  VITE_PORT,
  VITE_COMPRESSION,
  VITE_PUBLIC_PATH,
  VITE_PROXY_TARGET,
} = wrapperEnv(loadEnv(mode, root))
​
return {
  base: VITE_PUBLIC_PATH,
  plugins: getPluginsList(VITE_CDN, VITE_COMPRESSION),
}

这样做的好处是:环境读取、Vite 基础配置和插件组合各自负责一件事,后续增加插件时不需要把所有逻辑堆到 vite.config.ts 中。

三、插件流水线总览

当前项目的插件大致可以分为五层:

层级代表插件主要职责
核心编译@vitejs/plugin-vue@vitejs/plugin-vue-jsx编译 Vue、JSX 和 TSX
语言与质量Vue I18n、vite-plugin-checker国际化编译、TypeScript、Vue TSC 和 ESLint
开发体验Vue Inspector、构建信息、路由警告处理源码定位、开发提示和调试
本地能力Fake Server、主题预处理、SVG LoaderMock、主题变量和 SVG 组件化
交付构建CDN、压缩、移除日志、Visualizer资源优化、构建分析和线上治理

对应的插件工厂位于 build/plugins.ts

export function getPluginsList(
  VITE_CDN: boolean,
  VITE_COMPRESSION: ViteCompression,
): PluginOption[] {
  const lifecycle = process.env.npm_lifecycle_event
​
  return [
    vue(),
    vueJsx(),
    VueI18nPlugin({
      jitCompilation: false,
      include: [pathResolve('../locales/**')],
    }),
    checker({
      typescript: true,
      vueTsc: true,
      eslint: {
        lintCommand: `eslint ${pathResolve('../{src,mock,build}/**/*.{vue,js,ts,tsx}')}`,
        useFlatConfig: true,
      },
      terminal: false,
      enableBuild: false,
    }),
    vitePluginFakeServer({
      logger: false,
      include: 'mock',
      infixName: false,
      enableProd: true,
    }),
    themePreprocessorPlugin({
      scss: {
        multipleScopeVars: genScssMultipleScopeVars(),
        extract: true,
      },
    }),
    svgLoader(),
    VITE_CDN ? cdn : null,
    configCompressPlugin(VITE_COMPRESSION),
    removeConsole({ external: ['src/assets/iconfont/iconfont.js'] }),
    lifecycle === 'report'
      ? visualizer({ open: true, brotliSize: true, filename: 'report.html' })
      : null,
  ]
}

真实代码还包含源码检查、构建信息和开发环境路由警告处理等插件。上面的代码只保留了最能体现架构的部分。

四、核心编译插件

4.1 Vue SFC 编译

vue()

这是项目运行 Vue 单文件组件的基础。项目中的页面和组件普遍采用:

<script setup lang="ts">
// TypeScript + Composition API
</script><template>
  <!-- Vue template -->
</template>

核心编译插件应放在插件列表前部,为后续插件提供稳定的 Vue 编译基础。

4.2 JSX 和 TSX 支持

vueJsx()

项目并不是所有逻辑都写在 .vue 文件中,部分页面 Hook 使用 .tsx 文件,这类文件通常承担页面状态编排、表格列配置和请求逻辑。引入 JSX 插件后,项目可以在 Vue SFC 和 TSX 之间选择更适合当前场景的表达方式。

需要注意的是,JSX 支持会增加团队的代码风格选择。技术文档中应明确:

  • 页面模板优先使用 .vue
  • 复杂的渲染函数或已有 TSX 结构可以使用 .tsx
  • 状态逻辑应优先抽取为 Composable,避免把 JSX 变成新的“大组件”。

4.3 Vue I18n 编译插件

项目使用 @intlify/unplugin-vue-i18n/vite

VueI18nPlugin({
  jitCompilation: false,
  include: [pathResolve('../locales/**')],
})

这里有两个关键点:

  1. include 必须覆盖项目实际的语言文件目录;
  2. pathResolve 用于避免相对路径在不同工作目录下产生歧义。

项目运行时还会在 src/plugins/i18n.ts 中通过 import.meta.glob 读取语言文件,并将项目文案和 Element Plus 语言包合并。因此,Vite 构建期配置和运行期国际化实现需要保持一致。

项目使用 vite-plugin-checker

checker({
  typescript: true,
  vueTsc: true,
  eslint: {
    lintCommand: `eslint ${pathResolve('../{src,mock,build}/**/*.{vue,js,ts,tsx}')}`,
    useFlatConfig: true,
  },
  terminal: false,
  enableBuild: false,
})

这种配置将三类检查统一纳入开发流程:

  • TypeScript:检查普通 .ts 文件;
  • Vue TSC:检查 .vue 文件中的模板和类型;
  • ESLint:检查 Vue、TypeScript、TSX 和构建脚本。

当前配置将 enableBuild 设置为 false,意味着生产构建不会重复执行这套开发检查。这样可以减少构建阶段的额外耗时,但也要求 CI 或单独的 typechecklint 命令承担质量门禁。

项目已经在 package.json 中提供了:

pnpm typecheck
pnpm lint

这是一种比较清晰的职责划分:开发阶段快速反馈,提交或 CI 阶段执行完整检查。

5.2 Vue Inspector

Inspector()

Vue Inspector 允许开发者从页面元素定位到源代码位置。对于组件数量多、页面层级深的管理后台,这类能力可以显著减少排查样式和组件来源的时间。

它的价值不在于新增业务能力,而在于降低维护成本。此类只服务于开发体验的插件,需要明确生产环境是否应该启用。

5.3 路由警告处理

项目使用 vite-plugin-router-warn 处理开发阶段部分无匹配路由警告。动态路由系统在初始化前后可能短暂出现路由未匹配状态,因此项目选择在开发环境中减少这类非关键噪声。

这里的经验是:开发警告不能简单地全部关闭。只有明确知道警告来源,并确认它不会掩盖真正问题时,才适合针对性处理。

六、Mock、主题和资源插件

6.1 Fake Server

vitePluginFakeServer({
  logger: false,
  include: 'mock',
  infixName: false,
  enableProd: true,
})

项目将 mock 目录作为 Mock 入口。插件配置中的 include: 'mock' 让 Mock 文件可以参与 Vite 服务。

当前配置显式设置了 enableProd: true。这意味着部署前必须确认:

  • 生产环境是否真的需要 Mock;
  • Mock 路由是否会覆盖真实接口;
  • Mock 数据是否包含敏感信息;
  • 构建产物中是否会携带不必要的 Mock 代码。

Mock 能力适合服务本地开发和接口联调,但生产开关必须经过明确的环境策略控制。

6.2 主题预处理

themePreprocessorPlugin({
  scss: {
    multipleScopeVars: genScssMultipleScopeVars(),
    extract: true,
  },
})

项目同时使用 Element Plus、SCSS 和 Tailwind。主题插件需要处理:

  • Element Plus 组件主题变量;
  • 项目自定义主题变量;
  • 多套作用域变量;
  • 主题样式抽取;
  • 全局 SCSS 变量注入。

vite.config.ts 中还配置了:

css: {
  preprocessorOptions: {
    scss: {
      additionalData: '@use "~/styles/var.scss" as *;',
    },
  },
}

这类配置的难点在于样式来源很多,必须区分:

  • 全局设计变量;
  • 组件局部样式;
  • Element Plus 覆盖样式;
  • Tailwind 工具类;
  • 主题运行时变量。

否则很容易出现变量覆盖顺序不明确、开发环境正常但生产环境样式不一致等问题。

6.3 SVG Loader

svgLoader()

该插件让 SVG 可以作为组件导入,适合图标、状态插图和局部可控制的矢量资源。项目还同时使用 Iconify 和字体图标,因此需要在团队规范中明确不同图标方案的适用范围,避免同一种图标在多个系统中重复维护。

七、生产构建插件

7.1 CDN 外置依赖

VITE_CDN ? cdn : null

CDN 插件通过环境变量控制是否启用。这样可以在不同部署环境间切换:

  • 内网或本地环境使用正常打包;
  • 公网部署时将部分大型依赖交给 CDN;
  • 出现 CDN 不稳定时快速关闭外置策略。

CDN 化的难点不只是减少首屏资源,还包括:

  • 外部资源版本必须锁定;
  • CDN 资源失败时是否有降级方案;
  • CSP、跨域和缓存策略是否匹配;
  • 构建产物和 HTML 中的依赖声明必须保持一致。

7.2 资源压缩

configCompressPlugin(VITE_COMPRESSION)

压缩插件通过环境变量控制压缩类型。技术文档应该明确压缩发生在构建阶段,不能把它和运行时 gzip、服务器压缩混为一谈。

实际落地时需要关注:

  • 压缩格式是否被服务器正确返回;
  • 是否会增加构建时间;
  • 是否会影响调试和错误定位;
  • 静态资源缓存策略是否与文件 hash 配合。

7.3 线上移除 console

removeConsole({
  external: ['src/assets/iconfont/iconfont.js'],
})

项目在生产构建时移除日志,但保留了字体图标文件作为例外。这说明日志清理不能简单地全局替换字符串,需要考虑第三方资源、调试代码和必要的错误上报。

更稳妥的团队规范是:

  • 业务代码禁止遗留调试日志;
  • 必要日志统一进入日志工具;
  • 错误上报不能依赖 console
  • 第三方资源通过白名单或构建规则处理。

7.4 构建分析

项目通过 npm 生命周期判断是否启用构建分析:

const lifecycle = process.env.npm_lifecycle_eventconst visualizerPlugin =
  lifecycle === 'report'
    ? visualizer({
        open: true,
        brotliSize: true,
        filename: 'report.html',
      })
    : null

对应的脚本是:

{
  "report": "rimraf dist && vite build"
}

运行 pnpm report 时,Vite 插件列表会额外加入构建分析插件。这样可以避免每次普通构建都打开分析页面,也不需要为分析场景维护另一份完整配置。

八、vite.config.ts 中的其他工程化配置

插件只是整个 Vite 工程的一部分,项目还做了几项重要配置。

8.1 依赖预构建

optimizeDeps: {
  include,
  exclude,
}

依赖预构建会影响开发启动和依赖转换。对于大型依赖、特殊格式依赖或需要排除转换的依赖,应根据实际启动日志和构建结果调整,而不是盲目将所有依赖都加入 include

8.2 构建资源命名

output: {
  chunkFileNames: 'static/js/[name]-[hash].js',
  entryFileNames: 'static/js/[name]-[hash].js',
  assetFileNames: 'static/[ext]/[name]-[hash].[ext]',
}

固定的资源目录和 hash 命名有利于:

  • 静态资源分类部署;
  • 长缓存;
  • 版本更新后的缓存失效;
  • 运维侧快速定位资源类型。

8.3 源码预热

warmup: {
  clientFiles: ['./index.html', './src/{pages,components}/*'],
}

项目通过预热常用页面和组件,减少开发服务器启动后首次访问的转换等待。预热范围不宜过大,否则会把首次访问成本转移到启动阶段。

8.4 开发代理

项目使用 /api 代理后端接口,并为 /docs 配置帮助文档代理。代理配置解决了本地开发跨域问题,但部署路径、base、代理目标和后端网关路径必须一起验证。