vite前端项目运行时切换代理
背景
- vite前端项目开发时,经常需要切换后端服务环境进行接口联调,频繁修改 vite 配置;并且在多人协作前端项目中,经常会把 vite 配置改动提交的代码仓库,导致代码冲突的问题频频发生。
痛点
- 频繁修改配置文件;
- 多人协作,导致代码冲突。
需求
- 不需要修改配置文件的情况,也能进行后端服务环境的切换,提高开发效率。
解决方案
vite 自定义插件 + 切换后端服务环境的界面
- 实现思路:
- 写一个 vite 自定义插件,在 configureServer 钩子中,自定义 middleware 中去拦截切换代理的 post 请求 /switch_env;在界面上切换后端服务环境的时候,发送 /switch_env 请求,在运行时修改 vite 实例配置。也就是 vite.config.js 中的 proxy.target 修改为指定后端服务地址。
- 修改完 vite 配置后,自动 reload page。即完成后端服务的切换动作,不需要每次都去修改 vite 配置。
第一版:
- 具体实现:
- 实现 vite 插件 proxy-switch-vite-plugin.js,引入到 vite.config.js;
- 实现 ui 组件 switch-env.vue,引入到 App.vue。
vite插件核心实现:
javascript
// proxy-switch-vite-plugin.js
import type { Plugin } from 'vite'
const ENV_MAP = {
server1: 'http://localhost:3001',
server2: 'http://localhost:3002'
}
export function proxySwitchPlugin(): Plugin {
let server: any
return {
name: 'vite-plugin-proxy-switch',
apply: 'serve',
configureServer(viteServer) {
server = viteServer
// 处理环境切换请求
server.middlewares.use('/__switch_env', (req: any, res: any) => {
if (req.method !== 'POST') return
let body = ''
req.on('data', (chunk: any) => { body += chunk.toString() })
req.on('end', () => {
const { env, customUrl } = JSON.parse(body)
const target = env === 'custom' ? customUrl : ENV_MAP[env]
// 动态更新代理目标
applyTarget(server, target)
res.writeHead(200, { 'Content-Type': 'application/json' })
res.end(JSON.stringify({ ok: true, env, target }))
})
})
}
}
}
// 动态更新代理配置的核心函数
function applyTarget(server: any, target: string) {
if (!server.config.server?.proxy) return
const proxy = server.config.server.proxy as Record<string, any>
Object.keys(proxy).forEach(key => {
if (proxy[key]) {
proxy[key].target = target
console.log(`Updated proxy for ${key} to target: ${target}`)
}
})
}
UI组件实现:
vue
<!-- switch-env.vue -->
<template>
<div class="env-switcher">
<div class="env-container">
<span>当前环境: {{ currentEnv }}</span>
<el-select v-model="selectedEnv" @change="handleEnvChange">
<el-option label="Server 1" value="server1" />
<el-option label="Server 2" value="server2" />
</el-select>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const currentEnv = ref('server1')
const selectedEnv = ref('server1')
const handleEnvChange = async () => {
try {
const response = await fetch('/__switch_env', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ env: selectedEnv.value })
})
if (response.ok) {
const result = await response.json()
currentEnv.value = result.env
localStorage.setItem('proxy-env', JSON.stringify(result))
setTimeout(() => window.location.reload(), 500)
}
} catch (error) {
console.error('切换环境出错:', error)
}
}
</script>
<style scoped>
...
</style>
在App.vue中引入:
vue
<!-- App.vue -->
<template>
<div class="app">
<EnvSwitch />
<RouterView />
</div>
</template>
<script setup>
import EnvSwitch from './components/switch-env.vue'
</script>
- 缺点:
- 第一版解决问题,满足需求,但是 ui 组件入侵业务代码,这点不是很好。
第二版:vue 组件运行时注入
- 改进:
- ui 组件在运行时注入到 App.vue 中,在自定义 vite 插件的 transform 中注入 vue 组件。
虚拟模块实现:
javascript
// vite-plugin-proxy-switch.ts
const VIRTUAL_ID = 'virtual:env-switch.vue'
const RESOLVED_ID = '/@virtual/env-switch.vue'
function getEnvSwitchSFC(): string {
return `<template>
<div class="env-switcher">
<el-select v-model="selectedEnv" @change="handleEnvChange">
<el-option label="Server 1" value="server1" />
<el-option label="Server 2" value="server2" />
<el-option label="自定义" value="custom" />
</el-select>
</div>
</template>
<script setup>
import { ref } from 'vue'
const selectedEnv = ref('server1')
const handleEnvChange = async () => {
// 切换逻辑...
}
</script>
<style scoped>
.env-switcher {
position: fixed;
top: 0;
left: 0;
right: 0;
background: #409eff;
padding: 10px;
}
</style>`
}
export function proxySwitchPlugin(): Plugin {
return {
name: 'vite-plugin-proxy-switch',
apply: 'serve',
resolveId(id) {
if (id === VIRTUAL_ID) return RESOLVED_ID
},
load(id) {
if (id === RESOLVED_ID) return getEnvSwitchSFC()
},
transform(code, id) {
// 过滤掉子块请求和非 App.vue 文件
if (!id.includes('App.vue') || id.includes('?')) return
let modified = code
// 动态导入虚拟模块
if (!code.includes(VIRTUAL_ID)) {
modified = modified.replace(
`import { RouterView } from 'vue-router'`,
`import EnvSwitch from '${VIRTUAL_ID}'\nimport { RouterView } from 'vue-router'`
)
}
// 动态添加组件到模板
if (!code.includes('<EnvSwitch')) {
modified = modified.replace(
`<div class="app-container">`,
`<div class="app-container">\n <EnvSwitch />`
)
}
if (modified !== code) return { code: modified }
}
}
}
- 缺点:
- 每次启动开发环境,都会启动该插件,并不是每种情况都需要去启动该功能。
第三版:提高可控性
- 改进:
- 在 npm scripts 中,加一条 script,启动开发环境的同时启动该功能;通过 vite 变量来做启动/关闭该功能的控制 VITE_ENABLE_PROXY_SWITCH。
环境变量控制插件加载:
typescript
// vite.config.ts
import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import { proxySwitchPlugin } from './vite-plugin-proxy-switch'
export default defineConfig(({ mode }) => {
// 加载环境变量
const env = loadEnv(mode, process.cwd(), '')
// 根据环境变量决定是否启用代理切换插件
const plugins = [
vue(),
]
// 只有当 VITE_ENABLE_PROXY_SWITCH 为 true 时才加载代理切换插件
if (env.VITE_ENABLE_PROXY_SWITCH === 'true') {
plugins.unshift(proxySwitchPlugin())
}
return {
plugins,
server: {
proxy: {
'/api': {
target: 'http://localhost:3001', // 默认使用server1
changeOrigin: true
}
}
}
}
})
npm脚本配置:
json
{
"scripts": {
"dev": "vite", // 普通开发模式
"dev:proxy": "VITE_ENABLE_PROXY_SWITCH=true vite", // 启用代理切换
"start:server1": "cd server1 && npm start",
"start:server2": "cd server2 && npm start",
"dev:full": "concurrently \"npm run start:server1\" \"npm run start:server2\" \"npm run dev:proxy\""
}
}
插件中添加环境变量检查:
javascript
// vite-plugin-proxy-switch.ts
export function proxySwitchPlugin(): Plugin {
let server: any
// 检查是否启用代理切换
const enableProxySwitch = process.env.VITE_ENABLE_PROXY_SWITCH === 'true'
return {
name: 'vite-plugin-proxy-switch',
apply: 'serve',
resolveId(id) {
if (id === VIRTUAL_ID && enableProxySwitch) return RESOLVED_ID
},
load(id) {
if (id === RESOLVED_ID && enableProxySwitch) return getEnvSwitchSFC()
},
transform(code, id) {
// 如果未启用代理切换,则不进行任何转换
if (!enableProxySwitch) return
// transform逻辑...
},
configureServer(viteServer) {
server = viteServer
// 如果未启用代理切换,则不配置中间件
if (!enableProxySwitch) return
// 中间件配置...
}
}
}
完整示例
- Demo仓库地址 : github.com/chenjz08/de...
- 本仓库包含了proxy switch vite插件的完整实现,包括前端Vue项目、后端Express服务器和完整的配置示例
- 可直接克隆运行,包含完整的使用说明和测试接口