版本: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 < userOptionsdeepMerge 的关键语义: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 属性,允许外部访问插件内部状态(optionslogger 等)。


4. 工具层:通用能力

4.1 fs 模块

导出项功能
checkSourceExists验证源路径存在性,不存在时抛出错误
copySourceToTarget完整的目录复制流程:递归遍历 → 增量判断 → 并发复制 → 统计输出
shouldUpdateFile增量判断核心:比较 mtimeMs(修改时间戳)和 size(文件大小)
runWithConcurrencyWorker Pool 并发控制:N 个 Worker 共享递增索引,结果按原始顺序存储
readFileContent读取文件内容(异步)
writeFileContent写入文件内容(异步)
fileExists判断文件是否存在(异步)

增量判断逻辑shouldUpdateFile 通过 Promise.all 并行获取源文件和目标文件的 stat 信息,当 sourceStats.mtimeMs > targetStats.mtimeMssourceStats.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 构建生命周期钩子(buildStartresolveIdloadtransformbuildEndcloseBundle),基于当前阶段和模块转换比例计算进度百分比。

进度计算模型

阶段进度范围触发钩子
配置解析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 比较源文件与目标文件的 mtimeMssize,仅复制发生变化的文件。首次复制为全量,后续为增量。

并发复制:通过 runWithConcurrency 控制并发数(默认 10),大量小文件场景下显著提升复制速度。

配置示例

copyFile({
	sourceDir: 'src/assets', // 源目录(必填)
	targetDir: 'dist/assets', // 目标目录(必填)
	overwrite: true, // 覆盖已存在文件
	recursive: true, // 递归复制子目录
	incremental: true // 启用增量复制
})

输出统计:复制完成后输出 复制了 N 个文件,跳过了 M 个文件,耗时 Xms 的详细日志。

适用场景:多静态资源目录复制、构建产物后处理、CI/CD 中的增量部署。

注意事项:增量复制依赖文件的 mtimeMssize,某些构建工具可能重置文件时间戳导致增量判断失效。


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
    }
  })
}

路由名称策略

策略示例路径生成名称
pathpages/user/infopages_user_info
camelCasepages/user/infopagesUserInfo
pascalCasepages/user/infoPagesUserInfo
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 种版本号格式

格式示例输出说明
timestamp20260203153000时间戳格式
date2026.02.03日期格式
datetime2026.02.03.153000日期时间格式
semver1.0.0语义化版本(基于 semverBase
hasha1b2c3d4随机哈希(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> 中。

双模式注入

  1. 自定义 link 标签模式 — 配置 link 字段时,使用字符串替换将完整的 <link> 标签注入到 </head>
  2. HtmlTagDescriptor 模式 — 配置 icons 数组时,使用 Vite 原生 HtmlTagDescriptor API 注入

字符串简写:通过 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 图标配置、不同环境使用不同图标。

注意事项linkicons 互斥,配置了 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.opensendopen 时记录 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() 方法恢复原始的 fetchXMLHttpRequest 方法,避免内存泄漏和副作用残留。

安全验证(0.0.9 版本新增)

验证器防护目标
validateCustomTemplate阻止 customTemplate 包含 <script>
validateCallbacks阻止回调字符串包含 <script>
validateGlobalName阻止 __proto__/constructor 等危险属性
validateDefaultText空字符串时发出警告
validateAutoHideOndefaultVisible 为 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 时生效)

行为
DOMContentLoadedDOM 加载完成后自动隐藏(默认)
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 会修改全局 fetchXMLHttpRequest,不需要时调用 destroy() 恢复
  • 回调以函数体字符串形式提供(需注入到客户端代码中)
  • 生成的代码包含 SSR 环境检测,服务端渲染时自动跳过
  • pointerEvents 默认为 true(阻止交互),需允许穿透时显式设为 false
  • customTemplate 不允许包含 <script> 标签,回调逻辑应通过 callbacks 配置
  • globalName 必须是合法的 JavaScript 标识符,且不能是 __proto__constructorprototype

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. 已知限制与注意事项

通用

  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

  1. 非 TTY 环境 — CI/CD 中进度条自动降级为日志输出,无需额外配置
  2. 模块排除node_modules.virtual 模块自动排除

copyFile

  1. 增量复制前提 — 依赖文件的 mtimeMssize,构建工具重置时间戳会导致增量判断失效
  2. 并发控制 — 默认并发限制为 10,大量小文件场景可适当提高

generateRouter

  1. pages.json 格式 — 支持 JSON 注释,但需确保 JSON 结构合法
  2. preserveRouteChanges — 删除 pages.json 中的页面不会自动删除路由配置中的用户修改字段
  3. customNameGenerator — 使用 nameStrategy: 'custom' 时必须提供

generateVersion

  1. define 模式 — TypeScript 项目需自行声明全局变量类型
  2. custom 格式 — 必须提供 customFormat 模板

injectIco

  1. 注入顺序transformIndexHtml 设置 order: 'pre'
  2. link 与 icons 互斥 — 配置 link 时不处理 icons 数组

injectLoading

  1. 白屏 Loading 条件defaultVisible: true 时要求 HTML 包含 <head></head> 标签
  2. 请求拦截副作用autoBind 修改全局 fetchXMLHttpRequest,不需要时调用 destroy() 恢复
  3. 回调格式 — 以函数体字符串形式提供,不是函数引用
  4. SSR 兼容 — 生成的代码包含 SSR 环境检测
  5. XHR 拦截 — 使用 addEventListener('loadend') 监听请求结束
  6. pointerEvents 默认值 — 默认 true(阻止交互),需穿透时显式设为 false
  7. customTemplate 安全 — 不允许包含 <script> 标签
  8. globalName 安全 — 必须是合法 JavaScript 标识符,不能是内置属性
  9. autoHideOn 生效条件 — 仅在 defaultVisibletrue 时生效

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

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐