vite前端项目运行时切换代理

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服务器和完整的配置示例
  • 可直接克隆运行,包含完整的使用说明和测试接口
相关推荐
亿元程序员6 小时前
老板说最近这款游戏很火让我抄,可是我连玩都玩不明白...
前端
谢小飞7 小时前
如何让AI用一个下午开发上架Chrome插件助我摸鱼
前端·chrome
gyx_这个杀手不太冷静7 小时前
OpenCode 进阶使用指南(第一章:Agent 模式)
前端·javascript·ai编程
树上有只程序猿7 小时前
继续堆无用代码,真的不如早点用Low code
前端·低代码
wuhen_n7 小时前
computed 的缓存哲学:如何避免不必要的重复计算?
前端·javascript·vue.js
闲云一鹤7 小时前
本地部署 B 站 IndexTTS2 模型 - AI 文本生语音神器
前端·人工智能
wuhen_n7 小时前
watch 与 watchEffect:精准监听,避免副作用滥用
前端·javascript·vue.js
晓得迷路了7 小时前
栗子前端技术周刊第 119 期 - ViteLand 月度更新汇总、Angular 21.2、Bun v1.3.10...
前端·javascript·vite
鹏多多8 小时前
Flutter使用screenshot进行截屏和截长图以及分享保存的全流程指南
android·前端·flutter