Vite插件v0.2.0:开发代理新升级

版本:0.2.0 | 协议:MIT | 依赖:Vite >=5.0.0 <8.0.0


写在前面

v0.2.0 的主题是:让开发代理更优雅,让代码更干净

前端开发中,代理配置是连接本地开发与后端服务的桥梁。然而 Vite 原生的 server.proxy 配置分散在 vite.config.ts 中,缺乏环境切换、延迟模拟、请求日志等开发期常见需求。v0.2.0 新增的 proxyManager
插件,以声明式规则体系替代繁琐的 http-proxy-middleware 配置,支持环境切换、规则文件、请求日志、延迟模拟、响应修改和环境变量覆盖,将代理配置从构建配置中解耦为独立、可维护的规则体系。

同时,本版还从多个插件中提取了重复逻辑到 common 工具模块,修复了 proxyManager 的四个关键缺陷,整体代码质量显著提升。

本版重点

能力 一句话说明 你需要做什么
proxyManager 开发代理 声明式代理规则管理,支持环境切换、延迟模拟、响应修改 新增配置
proxyManager 请求日志 basic/verbose 两级日志,verbose 含状态码、耗时、目标 新增配置
proxyManager 延迟模拟 规则级/全局级延迟,支持固定值和随机范围 新增配置
proxyManager 环境变量覆盖 通过 PROXY_*_TARGET 动态覆盖代理目标 新增配置
common/format 增强 新增 parseTemplateWithDelimiter 通用模板解析 可直接使用
common/fs 增强 新增 scanAndMapFilesdeleteFiles 可直接使用

升级方式:修改 devDependencies 中版本号为 ^0.2.0。无 Breaking Changes,完全向后兼容。


一、5 分钟快速上手

1.1 安装与最小配置

{
	"devDependencies": {
		"@meng-xi/vite-plugin": "^0.2.0"
	}
}
import { proxyManager } from '@meng-xi/vite-plugin'

export default defineConfig({
	plugins: [
		proxyManager({
			rules: [{ context: '/api', target: 'http://localhost:3000' }]
		})
	]
})

1.2 立刻看到效果

启动开发服务器后,代理请求自动生效,终端输出请求日志:

ℹ️ [@meng-xi/vite-plugin:proxy-manager] 已加载 1 条代理规则 (环境: development)
ℹ️ [@meng-xi/vite-plugin:proxy-manager] GET /api/users → 200 (45ms) [http://localhost:3000]

1.3 运行时访问规则数据

const plugin = proxyManager({ rules: [...] })

// 开发服务器启动后...
const rules = plugin.pluginInstance?.getResolvedRules?.() // 当前生效的代理规则

二、核心能力:proxyManager 开发代理

2.1 声明式规则体系

一条 ProxyRule 即定义一条完整的代理规则,替代 Vite 原生 server.proxy 中分散的配置:

proxyManager({
	rules: [
		// 基本代理
		{ context: '/api', target: 'http://localhost:3000' },

		// 带路径重写
		{
			context: '/upload',
			target: 'http://oss.example.com',
			rewrite: path => path.replace(/^\/upload/, '/v2/files')
		},

		// 正则匹配
		{ context: /^\/(v1|v2)\//, target: 'http://legacy-api.example.com' },

		// WebSocket 代理
		{ context: '/ws', target: 'ws://localhost:8080', ws: true }
	]
})

2.2 环境切换

通过 env 字段限定规则生效环境,自动按 NODE_ENV 过滤:

proxyManager({
	rules: [
		{ context: '/api', target: 'http://localhost:3000', env: ['development'] },
		{ context: '/api', target: 'https://staging-api.example.com', env: ['staging'] },
		{ context: '/api', target: 'https://api.example.com', env: ['production'] }
	]
})

2.3 请求日志

三级日志输出,满足不同调试需求:

级别 输出内容 适用场景
none 不输出日志 生产环境
basic 请求方法、路径 日常开发
verbose 方法、路径、状态码、耗时、目标地址 排查问题
proxyManager({
	rules: [...],
	logLevel: 'verbose' // 输出:GET /api/users → 200 (45ms) [http://localhost:3000]
})

2.4 延迟模拟

模拟慢网络环境,测试 Loading 状态和超时处理:

proxyManager({
	rules: [
		// 固定延迟 500ms
		{ context: '/api', target: 'http://localhost:3000', delay: 500 },

		// 随机延迟 200~800ms
		{ context: '/slow-api', target: 'http://localhost:3000', delay: { min: 200, max: 800 } }
	],
	// 全局默认延迟(对未配置 delay 的规则生效)
	defaultDelay: { min: 100, max: 300 }
})

2.5 响应修改

在代理响应返回客户端前修改 JSON 响应体,用于 Mock 数据或字段脱敏:

proxyManager({
	rules: [
		{
			context: '/api/users',
			target: 'http://localhost:3000',
			modifyResponse: (body, proxyRes) => {
				// 脱敏手机号
				if (body?.phone) {
					body.phone = body.phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')
				}
				return body
			}
		}
	]
})

2.6 环境变量覆盖

通过环境变量动态覆盖代理目标,无需修改代码即可切换后端地址:

# .env.local
PROXY_API_TARGET=http://staging-api.example.com
proxyManager({
	rules: [
		// 当 PROXY_API_TARGET 存在时,target 被覆盖
		{ context: '/api', target: 'http://localhost:3000' }
	],
	envPrefix: 'PROXY_' // 默认值
})

环境变量命名规则:${envPrefix}${context大写}_TARGET,如 context 为 /api 则读取 PROXY_API_TARGET

2.7 规则文件

将代理规则抽离到独立文件,与 vite.config.ts 解耦:

// .proxyrc.ts
import type { ProxyRule } from '@meng-xi/vite-plugin'

export default [
	{ context: '/api', target: 'http://localhost:3000', label: '本地 API' },
	{ context: '/ws', target: 'ws://localhost:8080', ws: true, label: 'WebSocket' }
] satisfies ProxyRule[]

支持 .proxyrc.ts.proxyrc.js.proxyrc.mjs 三种格式,ESM 和 CJS 均可。

2.8 完整配置项

interface ProxyManagerOptions extends BasePluginOptions {
	rules?: ProxyRule[] // 代理规则列表,默认 []
	configFile?: string | false // 规则文件路径,默认 '.proxyrc.ts'
	logLevel?: 'none' | 'basic' | 'verbose' // 日志级别,默认 'basic'
	defaultDelay?: DelayConfig // 全局默认延迟,默认 false
	envPrefix?: string // 环境变量前缀,默认 'PROXY_'
}

ProxyRule 类型

属性 类型 描述
context string | RegExp 匹配路径,字符串前缀或正则
target string 代理目标地址
changeOrigin boolean 修改 Origin,默认 true
secure boolean 验证 SSL,默认 false
rewrite (path: string) => string 路径重写函数
headers Record<string, string> 自定义请求头
env string[] 生效环境列表
delay number | { min: number; max: number } 延迟模拟
modifyResponse (body: any, proxyRes: IncomingMessage) => any 响应修改回调
ws boolean WebSocket 代理,默认 false
label string 规则备注

继承 BasePluginOptions 通用配置:enabledverboseerrorStrategy


三、Bug 修复

v0.2.0 修复了 proxyManager 插件中四个影响代理正确性与可观测性的关键缺陷:

3.1 modifyResponse 不生效

问题:配置 modifyResponse 后,代理响应未被修改即返回客户端。

原因:实现仅修改了 content-length 响应头,未将修改后的内容写入客户端响应流。

修复:收集代理响应 chunks → 解析 JSON → 应用 modifyResponse 回调 → 通过 res.end() 写回修改后内容。

// 修复后的核心逻辑
proxy.on('proxyRes', (proxyRes, _req, res) => {
	const chunks: Buffer[] = []
	proxyRes.on('data', (chunk: Buffer) => chunks.push(chunk))
	proxyRes.on('end', () => {
		const body = Buffer.concat(chunks).toString()
		try {
			const parsed = JSON.parse(body)
			const modified = rule.modifyResponse!(parsed, proxyRes)
			if (modified !== undefined) {
				const modifiedBody = JSON.stringify(modified)
				res.setHeader('content-length', Buffer.byteLength(modifiedBody))
				res.end(modifiedBody)
				return
			}
		} catch {
			/* 非 JSON 响应,跳过修改 */
		}
		res.end(body)
	})
})

3.2 .mjs 规则文件加载失败

问题.proxyrc.mjs 配置文件无法加载,报 require() of ES Module 错误。

原因loadConfigFile 使用同步 require() 加载配置文件,无法处理 ESM .mjs 文件。

修复:改为 async 函数,.mjs 使用 await import() 动态导入,其他扩展名仍用 require()

3.3 日志 duration 不含延迟时间

问题:配置延迟模拟后,日志输出的 duration 不包含延迟时间,与实际体验不符。

原因:延迟中间件先于日志中间件注册,导致 startTime 在延迟执行之后才记录。

修复:调整中间件注册顺序——日志中间件先注册(先执行)记录 startTime,延迟中间件后注册(后执行)。

3.4 RegExp context 生成无效环境变量名

问题context 为正则表达式时,resolveTargetFromEnv 拼接出非法环境变量名(如 PROXY_/^\/API/_TARGET)。

原因:未对 RegExp 类型的 context 做类型守卫。

修复:增加 typeof context === 'string' 检查,RegExp context 跳过环境变量解析。


四、Common 工具模块增强

v0.2.0 从多个插件中提取了重复出现的逻辑到 common 目录,消除冗余代码:

4.1 common/format — 新增 parseTemplateWithDelimiter

通用模板解析函数,支持自定义左右分隔符,从 envGuard 插件中提取:

import { parseTemplateWithDelimiter } from '@meng-xi/vite-plugin/common/format'

// 默认 {{ }} 分隔符
parseTemplateWithDelimiter('Hello {{name}}!', { name: 'World' })
// 'Hello World!'

// 自定义分隔符
parseTemplateWithDelimiter('Hello {name}!', { name: 'World' }, '{', '}')
// 'Hello World!'

// formatDate 内部使用此函数,分隔符为 { 和 }
parseTemplateWithDelimiter('{YYYY}-{MM}-{DD}', getDateFormatParams(), '{', '}')
// '2026-06-17'

安全特性

  • 键名中的正则特殊字符自动转义,避免正则注入
  • 值中的 $ 安全处理,避免 $$ 替换问题

4.2 common/fs — 新增 scanAndMapFiles

递归扫描目录并将文件信息映射为自定义结构,封装"扫描 + 过滤 + 映射"模式:

import { scanAndMapFiles } from '@meng-xi/vite-plugin/common/fs'

const candidates = await scanAndMapFiles('dist', {
	scanOptions: {
		filter: (filePath, ext, size) => size > 1024
	},
	mapFn: (file, dirPath) => ({
		filePath: file.filePath,
		relativePath: path.relative(dirPath, file.filePath),
		size: file.size,
		ext: file.extension
	})
})

4.3 common/fs — 新增 deleteFiles

批量删除文件列表(自动去重,删除失败静默忽略),从 compressAssets 插件中提取:

import { deleteFiles } from '@meng-xi/vite-plugin/common/fs'

await deleteFiles(['/dist/app.js', '/dist/app.js.gz', '/dist/app.js.br'])

五、插件开发框架

本包导出完整的插件开发框架,帮助快速构建符合规范的自定义 Vite 插件。

5.1 BasePlugin 插件基类

所有内置插件的基类,提供配置管理、日志记录、错误处理和生命周期管理:

import { BasePlugin, createPluginFactory } from '@meng-xi/vite-plugin/factory'
import type { Plugin } from 'vite'

interface MyPluginOptions {
	prefix?: string
}

class MyPlugin extends BasePlugin<MyPluginOptions> {
	protected getPluginName() {
		return 'my-plugin'
	}
	protected getDefaultOptions() {
		return { prefix: '[app]' }
	}
	protected validateOptions() {
		this.validator.field('prefix').string().notEmpty().validate()
	}
	protected addPluginHooks(plugin: Plugin) {
		plugin.writeBundle = {
			order: 'post',
			handler: async () => {
				await this.safeExecute(async () => {
					this.logger.info('插件执行中...')
				}, '执行自定义逻辑')
			}
		}
	}
}

export const myPlugin = createPluginFactory(MyPlugin)

核心能力

方法 描述
getDefaultOptions() 提供插件默认配置
validateOptions() 使用 Validator 校验配置
getPluginName() 定义插件名称
getEnforce() 定义执行时机(pre/post)
addPluginHooks(plugin) 注册 Vite 钩子
safeExecute(fn, context) 安全执行异步函数,自动错误处理
safeExecuteSync(fn, context) 安全执行同步函数
handleError(error, context) 根据 errorStrategy 处理错误
onConfigResolved(config) 配置解析完成回调
destroy() 插件销毁回调

5.2 createPluginFactory 插件工厂

将 BasePlugin 子类转换为 Vite 插件函数,支持选项标准化器:

const myPlugin = createPluginFactory(MyPlugin)

// 带标准化器(支持字符串简写配置)
const myPlugin = createPluginFactory(MyPlugin, opt => (typeof opt === 'string' ? { path: opt } : opt))

5.3 Validator 链式验证器

this.validator
	.field('sourceDir')
	.required()
	.string()
	.field('targetDir')
	.required()
	.string()
	.field('overwrite')
	.boolean()
	.default(true)
	.field('concurrency')
	.number()
	.minValue(1)
	.maxValue(50)
	.field('format')
	.enum(['json', 'html', 'text'])
	.validate()

5.4 Logger 日志管理器

全局单例日志管理器,为每个插件提供独立日志代理:

import { Logger } from '@meng-xi/vite-plugin/logger'

const logger = Logger.create({ name: 'my-plugin', enabled: true })
const pluginLogger = logger.createPluginLogger('my-plugin')

pluginLogger.success('操作成功')
pluginLogger.info('信息提示')
pluginLogger.warn('警告信息')
pluginLogger.error('错误信息')

六、内置插件全景

v0.2.0 共包含 15 个实用插件,覆盖构建优化的各个方面:

插件 enforce 描述
assetManifest post 构建后生成资源映射清单,支持 Vite/Webpack/自定义格式、按入口分组和运行时注入
autoImport pre 自动导入,支持预设映射、通配符('*')、目录扫描、Vue 模板自动导入和类型声明生成
buildProgress - 终端实时构建进度条,支持 bar / spinner / minimal
bundleAnalyzer post 构建产物体积分析,支持 JSON/HTML 报告、gzip 计算和阈值告警
compressAssets post 构建产物压缩,支持 gzip / brotli / both,并发压缩和统计报告
copyFile post 构建完成后复制文件或目录,支持增量复制
envGuard post 环境变量校验,支持类型检查、范围验证、自定义规则和运行时守卫
faviconManager post 管理网站图标链接注入和文件复制
generateRouter post 根据 pages.json 自动生成路由配置与类型声明(uni-app)
generateVersion post 自动生成版本号,支持文件输出和全局变量注入
htmlInject post HTML 内容注入,支持多种位置、选择器定位、条件注入和安全过滤
imageOptimizer post 图片优化压缩与格式转换,支持 WebP/AVIF 转换、SVG 优化、并发处理
loadingManager post 全局 Loading 状态管理,支持请求拦截、防抖、过渡动画
proxyManager - 新增 开发代理管理,支持环境切换、规则文件、请求日志、延迟模拟和响应修改
versionUpdateChecker post 运行时版本更新检查,支持多种提示样式和自定义回调

七、Common 工具模块全景

8 个工具模块,支持子路径按需导入:

子路径 导出内容 描述
common/concurrency runWithConcurrency 并发控制,工作池模式
common/format getDateFormatParams, parseTemplate, parseTemplateWithDelimiter, formatDate, formatFileSize, calcRatio 日期格式化、模板替换、文件大小格式化、压缩率计算
common/fs checkSourceExists, copySourceToTarget, writeFileContent, scanDirectory, writeJsonReport, writeFileSyncSafely, shouldUpdateFileContent, resolveReportPath, scanAndMapFiles, deleteFiles 文件系统操作、目录扫描、报告生成
common/html injectBeforeTag, injectHeadAndBody, sanitizeContent, escapeHtmlAttr HTML 注入、安全消毒、属性转义
common/path normalizePath, isExtensionIncluded, isPathExcluded, isPreCompressed 路径规范化、扩展名过滤、路径排除匹配
common/script makeCallback 回调函数体包装为安全函数表达式
common/ui ANSI 终端 ANSI 颜色码常量
common/validation Validator, validateGlobalName, validateNoScriptInTemplate, validateCallbackFields 链式验证器、全局名称校验、脚本检测

八、类型导出

proxyManager 新增类型

类型 描述
ProxyManagerOptions 插件配置选项
ProxyRule 代理规则定义
ProxyLogLevel 日志级别类型
DelayConfig 延迟配置类型
ResolvedProxyRule 已解析的代理规则
ProxyLogEntry 代理请求日志条目

common/format 新增类型

无独立类型导出,parseTemplateWithDelimiter 参数均为基础类型。

common/fs 新增类型

无独立类型导出,scanAndMapFiles 为泛型函数,deleteFiles 参数为基础类型。


九、实战场景

9.1 多环境代理配置

import { proxyManager } from '@meng-xi/vite-plugin'

export default defineConfig({
	plugins: [
		proxyManager({
			rules: [
				{
					context: '/api',
					target: 'http://localhost:3000',
					env: ['development'],
					label: '本地 API'
				},
				{
					context: '/api',
					target: 'https://staging-api.example.com',
					env: ['staging'],
					changeOrigin: true,
					label: '预发布 API'
				},
				{
					context: '/api',
					target: 'https://api.example.com',
					env: ['production'],
					changeOrigin: true,
					secure: true,
					label: '生产 API'
				}
			],
			logLevel: 'verbose'
		})
	]
})

9.2 延迟模拟 + Loading 测试

import { proxyManager, loadingManager } from '@meng-xi/vite-plugin'

export default defineConfig({
	plugins: [
		loadingManager({ defaultVisible: true, autoHideOn: 'DOMContentLoaded' }),
		proxyManager({
			rules: [
				{
					context: '/api',
					target: 'http://localhost:3000',
					delay: { min: 200, max: 800 },
					label: '模拟慢网络'
				}
			],
			defaultDelay: 100,
			logLevel: 'verbose'
		})
	]
})

9.3 响应修改 + 数据脱敏

proxyManager({
	rules: [
		{
			context: '/api/users',
			target: 'https://api.example.com',
			changeOrigin: true,
			modifyResponse: body => {
				// 脱敏手机号
				if (Array.isArray(body?.data)) {
					body.data = body.data.map((user: any) => ({
						...user,
						phone: user.phone?.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')
					}))
				}
				return body
			}
		}
	]
})

9.4 环境变量覆盖 + 规则文件

# .env.local — 切换到预发布环境
PROXY_API_TARGET=https://staging-api.example.com
PROXY_UPLOAD_TARGET=https://staging-oss.example.com
// .proxyrc.ts
import type { ProxyRule } from '@meng-xi/vite-plugin'

export default [
	{ context: '/api', target: 'http://localhost:3000', label: 'API 服务' },
	{ context: '/upload', target: 'http://localhost:4000', label: '上传服务' },
	{ context: '/ws', target: 'ws://localhost:8080', ws: true, label: 'WebSocket' }
] satisfies ProxyRule[]
// vite.config.ts
proxyManager({
	configFile: '.proxyrc.ts',
	envPrefix: 'PROXY_',
	logLevel: 'verbose'
})

9.5 WebP 转换 + CDN 部署 + 代理联调

import { defineConfig } from 'vite'
import { imageOptimizer, assetManifest, proxyManager } from '@meng-xi/vite-plugin'

export default defineConfig({
	plugins: [
		proxyManager({
			rules: [{ context: '/api', target: 'http://localhost:3000', delay: { min: 100, max: 300 } }],
			logLevel: 'verbose'
		}),
		imageOptimizer({
			convertToWebp: { png: true, jpeg: true },
			keepOriginal: true,
			quality: { webp: 75 }
		}),
		assetManifest({
			publicPath: 'https://cdn.example.com/',
			injectRuntime: true
		})
	]
})

十、迁移指南

从 0.1.x 升级到 0.2.0

  1. 更新依赖版本:将 @meng-xi/vite-plugin 版本改为 ^0.2.0
  2. 无 Breaking Changes:所有 0.1.x 配置完全兼容
  3. 可选迁移:如果之前在 vite.config.ts 中手动配置 server.proxy,可迁移到 proxyManager
// 迁移前
export default defineConfig({
	server: {
		proxy: {
			'/api': {
				target: 'http://localhost:3000',
				changeOrigin: true,
				rewrite: path => path.replace(/^\/api/, '')
			}
		}
	}
})

// 迁移后
export default defineConfig({
	plugins: [
		proxyManager({
			rules: [
				{
					context: '/api',
					target: 'http://localhost:3000',
					changeOrigin: true,
					rewrite: path => path.replace(/^\/api/, '')
				}
			],
			logLevel: 'basic'
		})
	]
})

迁移后额外获得:环境切换、延迟模拟、请求日志、环境变量覆盖等能力。

posted @ 2026-06-19 00:24  PedroQue99  阅读(11)  评论(0)    收藏  举报