@meng-xi/vite-plugin v0.0.9 版本文档
版本: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 {
const baseDefaults = { enabled: true, verbose: true, errorStrategy: 'throw' }
const pluginDefaults = this.getDefaultOptions()
return deepMerge(baseDefaults, pluginDefaults, options) as Required
}
优先级:baseDefaults< pluginDefaults< userOptions。deepMerge的关键语义:undefined不覆盖(保护默认值),null会覆盖(允许显式置空),嵌套对象递归合并,数组直接覆盖。
错误处理策略
策略 | 行为 | 适用场景 |
| 记录错误并抛出异常,中断构建 | 生产环境(默认) |
| 记录错误但不抛出,构建继续 | CI/CD 容错场景 |
| 记录错误但不抛出,构建继续 | 静默降级场景 |
所有异步操作通过 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← 各插件日志开关
├── 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 , R = T>(
PluginClass: new (options: T, loggerConfig?: LoggerOptions) => P,
normalizer?: OptionsNormalizer
): PluginFactory
三重泛型:T(插件配置类型)、P(插件实例类型)、R(原始配置类型,支持简写)。
OptionsNormalizer支持字符串简写——例如injectIco('/assets')自动转换为 { base: '/assets' }。
pluginInstance 暴露— 返回的 Vite Plugin 对象上附加了 pluginInstance属性,允许外部访问插件内部状态(options、logger等)。
4. 工具层:通用能力
4.1 fs 模块
导出项 | 功能 |
| 验证源路径存在性,不存在时抛出错误 |
| 完整的目录复制流程:递归遍历 → 增量判断 → 并发复制 → 统计输出 |
| 增量判断核心:比较 |
| Worker Pool 并发控制:N 个 Worker 共享递增索引,结果按原始顺序存储 |
| 读取文件内容(异步) |
| 写入文件内容(异步) |
| 判断文件是否存在(异步) |
增量判断逻辑:shouldUpdateFile通过 Promise.all并行获取源文件和目标文件的 stat 信息,当 sourceStats.mtimeMs > targetStats.mtimeMs或 sourceStats.size !== targetStats.size时判定需要更新。目标文件不存在时返回 true。
并发控制模型:runWithConcurrency创建 min(concurrency, items.length)个 Worker,每个 Worker 通过共享的递增索引 index从任务队列中取任务执行,结果按原始索引存储,保证顺序一致性。
4.2 format 模块
导出项 | 功能 |
| 生成指定长度的随机十六进制哈希(1-32 位) |
| 从 Date 对象提取格式化参数对象 |
| 解析占位符模板(如 |
| 日期格式化快捷方法 |
| 剥离 JSON 字符串中的注释 |
| 路径转驼峰命名 |
| 路径转帕斯卡命名 |
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% |
|
模块解析与加载 | 5% - 80% |
|
模块转换 | 5% - 80% |
|
构建收尾 | 80% - 95% |
|
完成 | 100% |
|
关键设计决策:
·进度只进不退— 维护 lastPercentage变量,新进度低于已显示进度时忽略,避免视觉回退
·TTY 感知— 检测终端是否支持 ANSI 控制码,非 TTY 环境(如 CI/CD)自动降级为日志输出
·模块排除— node_modules和 .virtual模块自动排除,不计入进度统计
三种输出格式
格式 | 说明 |
| 终端进度条 + 百分比 + 阶段标签 + Spinner |
| 仅百分比 + 阶段标签 |
| 完整信息:进度条 + 百分比 + 模块名 + 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
}
})
}
路由名称策略
策略 | 示例路径 | 生成名称 |
|
|
|
|
|
|
|
|
|
| 自定义函数 | 由函数决定 |
文件监听:支持监听 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 种版本号格式
格式 | 示例输出 | 说明 |
|
| 时间戳格式 |
|
| 日期格式 |
|
| 日期时间格式 |
|
| 语义化版本(基于 |
|
| 随机哈希(1-32 位可配置) |
| 自定义 | 基于模板解析,支持占位符 |
自定义模板占位符:{YYYY}、{YY}、{MM}、{DD}、{HH}、{mm}、{ss}、{SSS}、{timestamp}、{hash}、{major}、{minor}、{patch}。
双输出模式
outputType | 行为 |
| 写入 |
| 通过 Vite |
| 同时执行文件写入和全局变量注入 |
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> 中。
双模式注入:
1.自定义 link 标签模式— 配置 link字段时,使用字符串替换将完整的 <link> 标签注入到 </head> 前
2.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 解析即显示,零 JS 依赖│
││
│
││
│ Loading Manager IIFE │
││
│ │
└───────────────────────────────────────────────────┘
defaultVisible = false(按需 Loading)
┌───────────────────────────────────────────────────┐
│
│
││
││
│
││
│...│
││
│ │
└───────────────────────────────────────────────────┘
白屏 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 版本新增)
验证器 | 防护目标 |
| 阻止 |
| 阻止回调字符串包含 <script> |
| 阻止 |
| 空字符串时发出警告 |
|
|
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__)提供以下方法:
方法 | 说明 |
| 显示 Loading(受 delayShow 控制) |
| 隐藏 Loading(受 minDisplayTime 和 debounceHide 控制) |
| 强制隐藏(忽略所有延迟和防抖) |
| 切换显示/隐藏状态 |
| 启用遮罩层指针事件(阻止底层交互) |
| 禁用遮罩层指针事件(允许交互穿透) |
| 切换指针事件状态 |
| 更新 Loading 文本 |
| 获取当前可见状态 |
| 获取指针事件是否启用 |
| 获取当前进行中的请求数 |
| 销毁管理器(移除 DOM、恢复拦截器、执行回调) |
4 种 Spinner 类型:spinner(旋转圆环)、dots(跳动圆点)、pulse(脉冲效果)、bar(进度条动画)。
autoHideOn 自动隐藏(仅 defaultVisible = true时生效)
值 | 行为 |
| DOM 加载完成后自动隐藏(默认) |
| 页面完全加载后自动隐藏 |
| 手动调用 |
配置示例
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(阻止交互),需允许穿透时显式设为 false
·customTemplate不允许包含 <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
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. 已知限制与注意事项
通用
1.Vite 版本— 要求 Vite ^5.0.0 || ^6.0.0 || ^7.0.0,不兼容 Vite 4.x 及更早版本
2.errorStrategy— 生产环境建议使用默认的 'throw';CI/CD 等容错场景使用 'log'
3.verbose— 生产构建可设置 verbose: false减少日志输出
buildProgress
4.非 TTY 环境— CI/CD 中进度条自动降级为日志输出,无需额外配置
5.模块排除— node_modules和 .virtual模块自动排除
copyFile
6.增量复制前提— 依赖文件的 mtimeMs和 size,构建工具重置时间戳会导致增量判断失效
7.并发控制— 默认并发限制为 10,大量小文件场景可适当提高
generateRouter
8.pages.json 格式— 支持 JSON 注释,但需确保 JSON 结构合法
9.preserveRouteChanges— 删除 pages.json 中的页面不会自动删除路由配置中的用户修改字段
10.customNameGenerator— 使用 nameStrategy: 'custom'时必须提供
generateVersion
11.define 模式— TypeScript 项目需自行声明全局变量类型
12.custom 格式— 必须提供 customFormat模板
injectIco
13.注入顺序— transformIndexHtml设置 order: 'pre'
14.link 与 icons 互斥— 配置 link时不处理 icons数组
injectLoading
15.白屏 Loading 条件— defaultVisible: true时要求 HTML 包含 <head> 和 </head> 标签
16.请求拦截副作用— autoBind修改全局 fetch和 XMLHttpRequest,不需要时调用 destroy()恢复
17.回调格式— 以函数体字符串形式提供,不是函数引用
18.SSR 兼容— 生成的代码包含 SSR 环境检测
19.XHR 拦截— 使用 addEventListener('loadend')监听请求结束
20.pointerEvents 默认值— 默认 true(阻止交互),需穿透时显式设为 false
21.customTemplate 安全— 不允许包含 <script> 标签
22.globalName 安全— 必须是合法 JavaScript 标识符,不能是内置属性
23.autoHideOn 生效条件— 仅在 defaultVisible为 true时生效

本文基于 @meng-xi/vite-plugin@0.0.9版本撰写。
夜雨聆风