一、为什么要关注 Vite 插件体系
在小型项目中,vite.config.ts 往往只需要配置 Vue 插件、别名和开发代理。但在企业级后台项目中,Vite 配置通常还承担以下职责:
- 编译 Vue SFC、JSX 和 TSX;
- 集成 Vue I18n、SCSS 主题和 SVG 组件;
- 在开发阶段提供 Mock、代码检查和源码定位能力;
- 在生产构建阶段处理 CDN、压缩、日志清理和构建分析;
- 根据不同环境和 npm 脚本切换插件行为。
因此,Vite 插件列表并不是一组简单的依赖,而是项目工程能力的集中体现。插件之间的顺序、启用条件和配置边界,都会影响开发体验、构建结果和线上安全。
本文以当前项目为例,分析 build/plugins.ts 和 vite.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 Loader | Mock、主题变量和 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/**')],
})
这里有两个关键点:
include必须覆盖项目实际的语言文件目录;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 或单独的 typecheck、lint 命令承担质量门禁。
项目已经在 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_event
const 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、代理目标和后端网关路径必须一起验证。