Vite插件开发框架与高频场景解决方案
版本:0.0.9 | 协议:MIT | 依赖:Vite ^5.0.0 || ^6.0.0 || ^7.0.0
1. 版本定位
@meng-xi/vite-plugin@0.0.9
是一个双面库——它既是一组覆盖前端构建高频场景的即用型插件集合,又是一个可扩展的 Vite 插件开发框架。这种设计使得 6 个内置插件并非独立实现,而是共享同一套生命周期、配置验证、错误处理和日志基础设施,同时将这套基础设施完整导出,供开发者构建自有插件。
版本兼容性
| 依赖项 | 版本要求 |
|---|---|
| Vite | ^5.0.0 || ^6.0.0 || ^7.0.0 |
| Node.js | 需支持 ES2020+ |
| TypeScript | 项目内置 ^5.9.3(用户项目可选) |
模块入口
{
".": "全量导出(框架 + 插件)",
"./common": "通用工具(fs、format、object、validation)",
"./factory": "开发框架(BasePlugin、createPluginFactory)",
"./logger": "日志模块(Logger)",
"./plugins": "仅插件(6 个内置插件工厂函数)"
}
每个入口均提供 ESM(.mjs)、CJS(.cjs)和类型声明(.d.ts)三种格式。
2. 设计决策与架构
2.1 核心设计原则
本库的架构围绕三个设计原则展开:
原则一:框架级一致性 — 所有插件共享相同的基础设施,而非各自为政。这意味着一个插件中的错误处理策略、日志格式、配置验证方式与另一个插件完全一致,降低了认知负担。
原则二:防御性编程 — safeExecute + errorStrategy 机制确保单个插件的异常不会意外中断整个构建流程。开发者可通过配置选择 'throw'(默认,严格模式)、'log'(容错模式)或 'ignore'(静默模式)。
原则三:可观测性 — 每个插件实例通过 pluginInstance 属性暴露内部状态,Logger 采用单例 + 代理架构统一管理日志输出,Validator 批量收集错误而非逐个抛出——这些设计都指向同一个目标:让插件的行为可观测、可调试。
2.2 分层架构
┌─────────────────────────────────────────────────────┐
│ 应用层(内置插件) │
│ buildProgress · copyFile · generateRouter │
│ generateVersion · injectIco · injectLoading │
├─────────────────────────────────────────────────────┤
│ 框架层(开发基础设施) │
│ BasePlugin · Validator · Logger · createPluginFactory│
├─────────────────────────────────────────────────────┤
│ 工具层(通用能力) │
│ fs(增量复制·并发控制) · format(日期·哈希·模板) │
│ object(深度合并) · validation(Validator) │
└─────────────────────────────────────────────────────┘
三层之间的依赖关系是单向的:应用层依赖框架层,框架层依赖工具层。工具层不依赖任何上层模块,可独立使用。
3. 框架层:插件开发基础设施
3.1 BasePlugin — 生命周期与配置管理
BasePlugin 是整个库的核心抽象。所有内置插件和自定义插件都继承自它,获得以下能力:
生命周期
constructor → 配置合并 · 日志初始化 · 配置验证
↓
toPlugin() → 生成 Vite Plugin 对象
├── addPluginHooks() → 子类注册业务钩子
├── configResolved → 自动组合:基类存储 viteConfig → 子类钩子
└── closeBundle → 自动组合:子类清理资源 → 基类注销日志
↓
destroy() → 资源清理 · Logger.unregister()
toPlugin() 最精巧的设计是钩子自动组合:configResolved 保证基类先存储 viteConfig,子类钩子才能通过 this.viteConfig 访问;closeBundle
保证子类先清理资源(如关闭 watcher),基类再注销日志配置。子类无需手动注册这两个钩子,只需重写 onConfigResolved() 和 destroy() 即可。
三层配置合并
protected mergeOptions(options: T): Required<T> {
const baseDefaults = { enabled: true, verbose: true, errorStrategy: 'throw' }
const pluginDefaults = this.getDefaultOptions()
return deepMerge(baseDefaults, pluginDefaults, options) as Required<T>
}
优先级:baseDefaults < pluginDefaults < userOptions。deepMerge 的关键语义:undefined 不覆盖(保护默认值),null 会覆盖(允许显式置空),嵌套对象递归合并,数组直接覆盖。
错误处理策略
| 策略 | 行为 | 适用场景 |
|---|---|---|
throw | 记录错误并抛出异常,中断构建 | 生产环境(默认) |
log | 记录错误但不抛出,构建继续 | CI/CD 容错场景 |
ignore | 记录错误但不抛出,构建继续 | 静默降级场景 |
所有异步操作通过 safeExecute 包裹,确保 errorStrategy 生效。
3.2 Validator — 批量错误收集
Validator 采用链式 API 设计,核心特征是延迟抛出——验证错误不立即抛出,而是收集到 errors 数组中,最终在 validate() 时一次性抛出所有错误。这让用户能一次看到所有配置问题,而非逐个修复。
this.validator.field('sourceDir').required().string().field('overwrite').boolean().default(true).field('incremental').boolean().default(true).validate()
支持的方法:field()、required()、string()/boolean()/number()/array()/object()、default()、custom(fn, msg)、validate()。
Validator 可独立使用——injectIco 插件中为嵌套的 copyOptions 创建了独立的 Validator 实例。
3.3 Logger — 单例 + 代理架构
Logger 采用全局单例管理所有插件的日志配置,通过 createPluginLogger() 为每个插件创建代理对象:
Logger(全局单例)
├── pluginConfigs: Map<string, boolean> ← 各插件日志开关
├── Logger.create({ name, enabled }) ← 注册插件 + 返回单例
├── Logger.unregister(name) ← 注销插件日志配置
└── createPluginLogger(name) → 代理对象
├── info / success / warn / error
└── 统一前缀:[@meng-xi/vite-plugin:插件名]
每个插件通过 verbose 选项独立控制日志开关;插件销毁时自动 Logger.unregister(),防止内存泄漏。
3.4 createPluginFactory — 类到插件的桥梁
function createPluginFactory<T, P extends BasePlugin<T>, R = T>(PluginClass: new (options: T, loggerConfig?: LoggerOptions) => P, normalizer?: OptionsNormalizer<T, R>): PluginFactory<T, R>
三重泛型:T(插件配置类型)、P(插件实例类型)、R(原始配置类型,支持简写)。
OptionsNormalizer 支持字符串简写——例如 injectIco('/assets') 自动转换为 { base: '/assets' }。
pluginInstance 暴露 — 返回的 Vite Plugin 对象上附加了 pluginInstance 属性,允许外部访问插件内部状态(options、logger 等)。
4. 工具层:通用能力
4.1 fs 模块
| 导出项 | 功能 |
|---|---|
checkSourceExists | 验证源路径存在性,不存在时抛出错误 |
copySourceToTarget | 完整的目录复制流程:递归遍历 → 增量判断 → 并发复制 → 统计输出 |
shouldUpdateFile | 增量判断核心:比较 mtimeMs(修改时间戳)和 size(文件大小) |
runWithConcurrency | Worker Pool 并发控制:N 个 Worker 共享递增索引,结果按原始顺序存储 |
readFileContent | 读取文件内容(异步) |
writeFileContent | 写入文件内容(异步) |
fileExists | 判断文件是否存在(异步) |
增量判断逻辑:shouldUpdateFile 通过 Promise.all 并行获取源文件和目标文件的 stat 信息,当 sourceStats.mtimeMs > targetStats.mtimeMs 或 sourceStats.size !== targetStats.size 时判定需要更新。目标文件不存在时返回
true。
并发控制模型:runWithConcurrency 创建 min(concurrency, items.length) 个 Worker,每个 Worker 通过共享的递增索引 index 从任务队列中取任务执行,结果按原始索引存储,保证顺序一致性。
4.2 format 模块
| 导出项 | 功能 |
|---|---|
generateRandomHash | 生成指定长度的随机十六进制哈希(1-32 位) |
getDateFormatParams | 从 Date 对象提取格式化参数对象 |
parseTemplate | 解析占位符模板(如 {YYYY}.{MM}.{DD}) |
formatDate | 日期格式化快捷方法 |
stripJsonComments | 剥离 JSON 字符串中的注释 |
toCamelCase | 路径转驼峰命名 |
toPascalCase | 路径转帕斯卡命名 |
4.3 object 模块
deepMerge — 递归深度合并对象,undefined 不覆盖、null 覆盖、嵌套对象递归、数组直接覆盖。
4.4 validation 模块
即 Validator 类,详见 3.2 节。
5. 应用层:内置插件
5.1 buildProgress — 构建进度可视化
解决的问题:Vite 构建过程中缺乏进度反馈,大型项目构建时开发者无法判断构建状态。
实现方式:通过监听 Vite 构建生命周期钩子(buildStart → resolveId → load → transform → buildEnd → closeBundle),基于当前阶段和模块转换比例计算进度百分比。
进度计算模型
| 阶段 | 进度范围 | 触发钩子 |
|---|---|---|
| 配置解析 | 5% | configResolved |
| 模块解析与加载 | 5% - 80% | resolveId/load |
| 模块转换 | 5% - 80% | transform |
| 构建收尾 | 80% - 95% | buildEnd |
| 完成 | 100% | closeBundle |
关键设计决策:
- 进度只进不退 — 维护
lastPercentage变量,新进度低于已显示进度时忽略,避免视觉回退 - TTY 感知 — 检测终端是否支持 ANSI 控制码,非 TTY 环境(如 CI/CD)自动降级为日志输出
- 模块排除 —
node_modules和.virtual模块自动排除,不计入进度统计
三种输出格式
| 格式 | 说明 |
|---|---|
bar | 终端进度条 + 百分比 + 阶段标签 + Spinner |
minimal | 仅百分比 + 阶段标签 |
full | 完整信息:进度条 + 百分比 + 模块名 + Spinner |
配置示例
buildProgress({
format: 'bar', // 'bar' | 'minimal' | 'full'
clearOnComplete: true, // 完成后清除进度条
showModuleName: true, // 显示当前处理模块名
theme: {
// 自定义颜色主题
barColor: 'cyan',
percentColor: 'green'
}
})
适用场景:大型项目构建监控、CI/CD 流水线进度追踪、团队协作中的构建状态可视化。
5.2 copyFile — 智能文件复制
解决的问题:Vite 的 publicDir 只支持单一目录,且无法增量复制,每次构建都全量拷贝。
实现方式:在 writeBundle 钩子中执行文件复制,委托 common/fs 模块的 copySourceToTarget 完成实际操作。
增量复制机制:通过 shouldUpdateFile 比较源文件与目标文件的 mtimeMs 和 size,仅复制发生变化的文件。首次复制为全量,后续为增量。
并发复制:通过 runWithConcurrency 控制并发数(默认 10),大量小文件场景下显著提升复制速度。
配置示例
copyFile({
sourceDir: 'src/assets', // 源目录(必填)
targetDir: 'dist/assets', // 目标目录(必填)
overwrite: true, // 覆盖已存在文件
recursive: true, // 递归复制子目录
incremental: true // 启用增量复制
})
输出统计:复制完成后输出 复制了 N 个文件,跳过了 M 个文件,耗时 Xms 的详细日志。
适用场景:多静态资源目录复制、构建产物后处理、CI/CD 中的增量部署。
注意事项:增量复制依赖文件的 mtimeMs 和 size,某些构建工具可能重置文件时间戳导致增量判断失效。
5.3 generateRouter — uni-app 路由自动生成
解决的问题:uni-app 项目中路由配置需手动维护 pages.json 与路由文件的映射关系,多人协作时易产生冲突。
实现方式:读取 pages.json,解析页面配置和子包配置,自动生成路由配置文件。
用户修改保留(preserveRouteChanges) — 这是该插件最独特的设计。当开启此选项后,插件在重新生成路由时会合并用户的已有修改:
private mergeRoutes(newRoutes, existingRoutesMap) {
return newRoutes.map(newRoute => {
const existingRoute = existingRoutesMap.get(newRoute.path)
if (!existingRoute) return newRoute
// 合并 meta:先用新生成的作为基础,再用用户现有的覆盖
const mergedMeta = {}
if (newRoute.meta) Object.assign(mergedMeta, newRoute.meta)
if (existingRoute.meta) Object.assign(mergedMeta, existingRoute.meta)
return {
...existingRoute, // 保留用户对整个路由的修改
path: newRoute.path, // path 始终使用新的(标识符,由 pages.json 决定)
meta: Object.keys(mergedMeta).length > 0 ? mergedMeta : undefined
}
})
}
路由名称策略
| 策略 | 示例路径 | 生成名称 |
|---|---|---|
path | pages/user/info | pages_user_info |
camelCase | pages/user/info | pagesUserInfo |
pascalCase | pages/user/info | PagesUserInfo |
custom | 自定义函数 | 由函数决定 |
文件监听:支持监听 pages.json 变更,自动重新生成路由配置(开发模式默认开启)。
metaMapping:将 pages.json 中的页面配置字段映射为路由 meta 属性,如 navigationBarTitleText → title。
配置示例
generateRouter({
pagesJsonPath: 'src/pages.json',
outputPath: 'src/router.config.ts',
outputFormat: 'ts', // 'ts' | 'js'
nameStrategy: 'camelCase', // 'path' | 'camelCase' | 'pascalCase' | 'custom'
includeSubPackages: true, // 包含子包页面
watch: true, // 监听 pages.json 变更
exportTypes: true, // 导出类型定义
preserveRouteChanges: true, // 保留用户修改
metaMapping: {
navigationBarTitleText: 'title',
requireAuth: 'requireAuth'
}
})
适用场景:uni-app 项目路由自动化、多人协作避免路由配置冲突、路由 meta 自定义字段管理。
注意事项:pages.json 支持 JSON 注释(自动调用 stripJsonComments);使用 nameStrategy: 'custom' 时必须提供 customNameGenerator 函数。
5.4 generateVersion — 多格式版本号生成
解决的问题:前端项目缺乏统一的版本号管理机制,构建产物难以追溯。
实现方式:在 buildStart 钩子中生成版本号,根据 outputType 选择输出方式。
6 种版本号格式
| 格式 | 示例输出 | 说明 |
|---|---|---|
timestamp | 20260203153000 | 时间戳格式 |
date | 2026.02.03 | 日期格式 |
datetime | 2026.02.03.153000 | 日期时间格式 |
semver | 1.0.0 | 语义化版本(基于 semverBase) |
hash | a1b2c3d4 | 随机哈希(1-32 位可配置) |
custom | 自定义 | 基于模板解析,支持占位符 |
自定义模板占位符:{YYYY}、{YY}、{MM}、{DD}、{HH}、{mm}、{ss}、{SSS}、{timestamp}、{hash}、{major}、{minor}、{patch}。
双输出模式
| outputType | 行为 |
|---|---|
file | 写入 version.json 到构建输出目录 |
define | 通过 Vite define 注入全局变量(默认 __APP_VERSION__) |
both | 同时执行文件写入和全局变量注入 |
version.json 结构
{
"version": "20260203153000",
"buildTime": "2026-02-03T15:30:00.000Z",
"timestamp": 1738567800000,
"format": "timestamp"
}
可通过 extra 字段追加自定义信息,通过 prefix/suffix 添加前缀/后缀。
配置示例
generateVersion({
format: 'datetime',
semverBase: '1.0.0',
outputType: 'both',
defineName: '__APP_VERSION__',
hashLength: 8,
prefix: 'v',
extra: { environment: process.env.NODE_ENV }
})
适用场景:构建产物版本追溯、前端版本检测与热更新提示、自动化部署版本标记。
注意事项:outputType: 'define' 时 TypeScript 项目需自行声明全局变量类型;format: 'custom' 时必须提供 customFormat 模板。
5.5 injectIco — HTML 图标注入
解决的问题:手动在 HTML 模板中维护图标链接繁琐,且无法根据构建配置动态调整。
实现方式:通过 transformIndexHtml 钩子(order: 'pre')注入图标标签到 <head> 中。
双模式注入:
- 自定义 link 标签模式 — 配置
link字段时,使用字符串替换将完整的<link>标签注入到</head>前 - HtmlTagDescriptor 模式 — 配置
icons数组时,使用 Vite 原生HtmlTagDescriptorAPI 注入
字符串简写:通过 OptionsNormalizer 支持 injectIco('/assets') 简写形式,自动转换为 { base: '/assets' }。
图标文件复制:可选配置 copyOptions,在 writeBundle 阶段将图标文件复制到目标目录,复用 common/fs 的增量复制能力。copyOptions 使用独立的 Validator 实例验证。
配置示例
// 完整配置
injectIco({
base: '/',
icons: [
{ rel: 'icon', href: '/favicon.svg', type: 'image/svg+xml' },
{ rel: 'apple-touch-icon', href: '/apple-touch-icon.png', sizes: '180x180' }
],
copyOptions: {
sourceDir: 'src/assets/icons',
targetDir: 'dist/assets/icons'
}
})
// 字符串简写
injectIco('/assets')
适用场景:多尺寸 favicon 注入、PWA 图标配置、不同环境使用不同图标。
注意事项:link 与 icons 互斥,配置了 link 时不会处理 icons 数组。
5.6 injectLoading — 全局 Loading 状态管理
解决的问题:前端应用白屏体验差、请求状态缺乏统一 Loading 管理、Loading 频繁闪烁。
实现方式:通过 transformIndexHtml 钩子(order: 'post')将 Loading 管理代码注入到 HTML 中。
双阶段注入策略 — 根据 defaultVisible 配置决定注入方式:
defaultVisible = true(白屏 Loading)
┌───────────────────────────────────────────────────┐
│ <html> │
│ <head> │
│ <!-- 阶段一:CSS + HTML 静态注入 --> │
│ <style data-loading-style ...>...</style> │
│ <div id="__loading-root__">...</div> │
│ → HTML 解析即显示,零 JS 依赖 │
│ </head> │
│ <body> │
│ <!-- 阶段二:JS 管理器注入 --> │
│ <script> Loading Manager IIFE </script> │
│ </body> │
│ </html> │
└───────────────────────────────────────────────────┘
defaultVisible = false(按需 Loading)
┌───────────────────────────────────────────────────┐
│ <html> │
│ <head> │
│ <!-- 无静态注入 --> │
│ </head> │
│ <body> │
│ <!-- 完整注入:动态创建 CSS/HTML + JS 管理器 -->│
│ <script>...</script> │
│ </body> │
│ </html> │
└───────────────────────────────────────────────────┘
白屏 Loading 的关键:CSS 和 HTML 以静态标签形式直接注入到 <head> 中,浏览器解析到 <head> 时 Loading 即可见,无需等待 JS 执行。
资源缓存:CSS 和 HTML 只生成一次并缓存(_cachedAssets),供 head 和 body 注入共享。
注入降级策略:依次尝试 </body> → </html> → 追加到末尾。
请求拦截 — 当 autoBind 为 'fetch'/'xhr'/'all' 时,自动拦截对应类型的请求:
- fetch 拦截:保存原始
window.fetch,替换为包装函数,请求开始调用_requestStart,请求结束(resolve/reject)调用_requestEnd - XHR 拦截:保存原始
XMLHttpRequest.prototype.open和send,open时记录 URL 和方法,send时调用_requestStart并通过addEventListener('loadend')监听请求结束
并发请求管理:内部维护 pendingCount 计数器,首个请求开始时显示 Loading,最后一个请求结束时隐藏。
请求过滤:通过 requestFilter 精细控制哪些请求触发 Loading:
requestFilter: {
excludeUrls: [/\/api\/health/], // 排除特定 URL(正则)
includeUrls: [/\/api\/data/], // 仅包含特定 URL(优先级更高)
excludeMethods: ['OPTIONS'], // 排除特定 HTTP 方法
excludeUrlPrefixes: ['/static/'] // 排除特定 URL 前缀
}
防闪烁三重机制
请求开始 → delayShow(延迟显示,快速完成的请求不触发 Loading)
→ minDisplayTime(最小显示时间,避免一闪而过)
→ debounceHide(防抖隐藏,避免频繁切换)
- delayShow(默认 200ms):请求开始后延迟显示,若请求在此时间内完成则不显示
- minDisplayTime(默认 300ms):Loading 至少显示指定时长
- debounceHide(默认关闭,100ms):最后一次 hide 调用后延迟执行
元素就绪重试:_doShow 方法在 Loading DOM 元素未就绪时自动重试(最多 20 次,每次 50ms),确保动态注入场景下也能正常显示。
请求拦截恢复:destroy() 方法恢复原始的 fetch 和 XMLHttpRequest 方法,避免内存泄漏和副作用残留。
安全验证(0.0.9 版本新增)
| 验证器 | 防护目标 |
|---|---|
validateCustomTemplate | 阻止 customTemplate 包含 <script> |
validateCallbacks | 阻止回调字符串包含 <script> |
validateGlobalName | 阻止 __proto__/constructor 等危险属性 |
validateDefaultText | 空字符串时发出警告 |
validateAutoHideOn | defaultVisible 为 false 时 autoHideOn 无效,发出警告 |
CSS 性能优化
.__loading-overlay__ {
contain: content; /* CSS Containment,优化渲染性能 */
will-change: opacity; /* 提示浏览器优化 opacity 动画 */
}
.__loading-overlay__.__loading-hidden__ .__loading-spinner__ {
animation-play-state: paused; /* 隐藏时暂停动画,减少 CPU 开销 */
}
运行时 API
注入后通过全局变量(默认 window.__LOADING_MANAGER__)提供以下方法:
| 方法 | 说明 |
|---|---|
show(text?) | 显示 Loading(受 delayShow 控制) |
hide() | 隐藏 Loading(受 minDisplayTime 和 debounceHide 控制) |
forceHide() | 强制隐藏(忽略所有延迟和防抖) |
toggle(text?) | 切换显示/隐藏状态 |
enablePointerEvents() | 启用遮罩层指针事件(阻止底层交互) |
disablePointerEvents() | 禁用遮罩层指针事件(允许交互穿透) |
togglePointerEvents() | 切换指针事件状态 |
updateText(text) | 更新 Loading 文本 |
isVisible() | 获取当前可见状态 |
isPointerEventsEnabled() | 获取指针事件是否启用 |
getPendingCount() | 获取当前进行中的请求数 |
destroy() | 销毁管理器(移除 DOM、恢复拦截器、执行回调) |
4 种 Spinner 类型:spinner(旋转圆环)、dots(跳动圆点)、pulse(脉冲效果)、bar(进度条动画)。
autoHideOn 自动隐藏(仅 defaultVisible = true 时生效)
| 值 | 行为 |
|---|---|
DOMContentLoaded | DOM 加载完成后自动隐藏(默认) |
load | 页面完全加载后自动隐藏 |
manual | 手动调用 hide() 隐藏 |
配置示例
injectLoading({
position: 'center',
defaultText: '加载中...',
spinnerType: 'spinner',
defaultVisible: true,
autoHideOn: 'DOMContentLoaded',
autoBind: 'all',
globalName: '__LOADING_MANAGER__',
style: {
overlayColor: 'rgba(255, 255, 255, 0.7)',
spinnerColor: '#4361ee',
spinnerSize: '40px',
zIndex: 9999,
pointerEvents: true,
backdropBlur: false,
backdropBlurAmount: 4
},
transition: { enabled: true, duration: 200, easing: 'ease-out' },
minDisplayTime: { enabled: true, duration: 300 },
delayShow: { enabled: true, duration: 200 },
debounceHide: { enabled: false, duration: 100 },
requestFilter: {
excludeUrls: [/\/api\/health/],
excludeMethods: ['OPTIONS'],
excludeUrlPrefixes: ['/static/']
},
callbacks: {
onBeforeShow: 'return true',
onShow: '',
onBeforeHide: 'return true',
onHide: '',
onDestroy: ''
}
})
适用场景:白屏 Loading、请求自动 Loading、手动控制 Loading、防闪烁场景、运行时动态切换交互。
注意事项:
defaultVisible: true时要求 HTML 模板包含<head>和</head>标签autoBind会修改全局fetch和XMLHttpRequest,不需要时调用destroy()恢复- 回调以函数体字符串形式提供(需注入到客户端代码中)
- 生成的代码包含 SSR 环境检测,服务端渲染时自动跳过
pointerEvents默认为true(阻止交互),需允许穿透时显式设为falsecustomTemplate不允许包含<script>标签,回调逻辑应通过callbacks配置globalName必须是合法的 JavaScript 标识符,且不能是__proto__、constructor、prototype
6. 与同类方案的差异化分析
| 对比维度 | @meng-xi/vite-plugin | 同类单一功能插件 |
|---|---|---|
| 功能覆盖 | 6 个插件 + 开发框架 | 每个仅解决单一问题 |
| 插件开发框架 | BasePlugin + Validator + Logger | 无 |
| 错误处理 | 三级策略(throw/log/ignore) | 无统一策略 |
| 配置验证 | 内置 Validator,批量错误收集 | 无 |
| 日志管理 | 单例 + 代理,统一前缀 | 各自为政 |
| 类型安全 | 全链路 TypeScript | 部分 |
| 增量复制 | mtimeMs + size 双判断 | 部分支持或全量复制 |
| Loading 防闪烁 | delayShow + minDisplayTime + debounceHide | — |
| Loading 指针控制 | enablePointerEvents / disablePointerEvents | — |
| 路由用户修改保留 | preserveRouteChanges | — |
| 按需导入 | 5 个子路径入口 | 单入口 |
核心差异化:本库不是 6 个独立插件的简单打包,而是一个统一架构下的功能集合。所有插件共享相同的基础设施,开发者也可以基于这套基础设施构建自有插件,享受与内置插件完全一致的生命周期管理、配置验证、错误处理和日志输出。
7. 快速上手
7.1 安装
npm install @meng-xi/vite-plugin
# 或
pnpm add @meng-xi/vite-plugin
7.2 基础配置
// vite.config.ts
import { defineConfig } from 'vite'
import { buildProgress, copyFile, generateRouter, generateVersion, injectIco, injectLoading } from '@meng-xi/vite-plugin'
export default defineConfig({
plugins: [
buildProgress(),
copyFile({ sourceDir: 'src/assets', targetDir: 'dist/assets' }),
generateRouter(),
generateVersion({ format: 'datetime', outputType: 'both' }),
injectIco('/assets'),
injectLoading({ defaultVisible: true, autoHideOn: 'DOMContentLoaded', autoBind: 'all' })
]
})
7.3 按需导入
import { buildProgress, copyFile } from '@meng-xi/vite-plugin/plugins'
import { BasePlugin, createPluginFactory } from '@meng-xi/vite-plugin/factory'
import { Logger } from '@meng-xi/vite-plugin/logger'
import { deepMerge, copySourceToTarget } from '@meng-xi/vite-plugin/common'
7.4 自定义插件开发
import { BasePlugin, createPluginFactory } from '@meng-xi/vite-plugin/factory'
import type { Plugin } from 'vite'
interface MyPluginOptions {
message: string
count?: number
}
class MyPlugin extends BasePlugin<MyPluginOptions> {
protected getPluginName(): string {
return 'my-plugin'
}
protected getDefaultOptions() {
return { count: 10 }
}
protected validateOptions() {
this.validator.field('message').required().string().field('count').number().validate()
}
protected addPluginHooks(plugin: Plugin): void {
plugin.buildStart = () => {
this.logger.info(this.options.message)
}
}
protected destroy(): void {
super.destroy()
// 清理 watcher、定时器等
}
}
export const myPlugin = createPluginFactory(MyPlugin)
7.5 运行时交互
const plugin = injectLoading({ defaultVisible: true, autoBind: 'all' })
// 访问插件内部状态
console.log(plugin.pluginInstance.options)
console.log(plugin.pluginInstance.logger)
8. 典型应用场景
场景一:uni-app 项目工程化
export default defineConfig({
plugins: [
buildProgress({ format: 'bar' }),
generateRouter({
pagesJsonPath: 'src/pages.json',
preserveRouteChanges: true,
metaMapping: { navigationBarTitleText: 'title', requireAuth: 'requireAuth' }
}),
generateVersion({ format: 'datetime', outputType: 'both', defineName: '__APP_VERSION__' }),
injectLoading({
defaultVisible: true,
autoHideOn: 'DOMContentLoaded',
autoBind: 'all',
requestFilter: { excludeUrlPrefixes: ['/static/'] }
})
]
})
场景二:Web 应用白屏优化
export default defineConfig({
plugins: [
buildProgress(),
injectLoading({
defaultVisible: true,
autoHideOn: 'load',
spinnerType: 'pulse',
style: { overlayColor: 'rgba(255,255,255,0.9)', backdropBlur: true, backdropBlurAmount: 8, pointerEvents: true },
delayShow: { enabled: false },
minDisplayTime: { enabled: true, duration: 500 }
}),
injectIco({
base: '/',
icons: [
{ rel: 'icon', href: '/favicon.svg', type: 'image/svg+xml' },
{ rel: 'apple-touch-icon', href: '/apple-touch-icon.png', sizes: '180x180' }
]
})
]
})
场景三:CI/CD 构建监控
export default defineConfig({
plugins: [
buildProgress({ format: 'minimal', clearOnComplete: false, showModuleName: false }),
generateVersion({
format: 'custom',
customFormat: '{YYYY}.{MM}.{DD}.{hash}',
hashLength: 6,
outputType: 'both',
extra: { environment: process.env.NODE_ENV, commitHash: process.env.CI_COMMIT_SHA }
}),
copyFile({ sourceDir: 'public', targetDir: 'dist', incremental: true, errorStrategy: 'log' })
]
})
场景四:运行时动态控制交互
// vite.config.ts
export default defineConfig({
plugins: [
injectLoading({
autoBind: 'all',
style: { pointerEvents: true },
debounceHide: { enabled: true, duration: 100 }
})
]
})
// 应用代码
const loading = window.__LOADING_MANAGER__
loading.show('提交中...')
loading.disablePointerEvents() // 允许交互穿透
loading.enablePointerEvents() // 恢复交互阻止
loading.togglePointerEvents() // 切换交互状态
9. 已知限制与注意事项
通用
- Vite 版本 — 要求 Vite ^5.0.0 || ^6.0.0 || ^7.0.0,不兼容 Vite 4.x 及更早版本
- errorStrategy — 生产环境建议使用默认的
'throw';CI/CD 等容错场景使用'log' - verbose — 生产构建可设置
verbose: false减少日志输出
buildProgress
- 非 TTY 环境 — CI/CD 中进度条自动降级为日志输出,无需额外配置
- 模块排除 —
node_modules和.virtual模块自动排除
copyFile
- 增量复制前提 — 依赖文件的
mtimeMs和size,构建工具重置时间戳会导致增量判断失效 - 并发控制 — 默认并发限制为 10,大量小文件场景可适当提高
generateRouter
- pages.json 格式 — 支持 JSON 注释,但需确保 JSON 结构合法
- preserveRouteChanges — 删除 pages.json 中的页面不会自动删除路由配置中的用户修改字段
- customNameGenerator — 使用
nameStrategy: 'custom'时必须提供
generateVersion
- define 模式 — TypeScript 项目需自行声明全局变量类型
- custom 格式 — 必须提供
customFormat模板
injectIco
- 注入顺序 —
transformIndexHtml设置order: 'pre' - link 与 icons 互斥 — 配置
link时不处理icons数组
injectLoading
- 白屏 Loading 条件 —
defaultVisible: true时要求 HTML 包含<head>和</head>标签 - 请求拦截副作用 —
autoBind修改全局fetch和XMLHttpRequest,不需要时调用destroy()恢复 - 回调格式 — 以函数体字符串形式提供,不是函数引用
- SSR 兼容 — 生成的代码包含 SSR 环境检测
- XHR 拦截 — 使用
addEventListener('loadend')监听请求结束 - pointerEvents 默认值 — 默认
true(阻止交互),需穿透时显式设为false - customTemplate 安全 — 不允许包含
<script>标签 - globalName 安全 — 必须是合法 JavaScript 标识符,不能是内置属性
- autoHideOn 生效条件 — 仅在
defaultVisible为true时生效
本文基于 @meng-xi/vite-plugin@0.0.9 版本撰写。
更多推荐

所有评论(0)