mirror of
https://github.com/chansee97/nova-admin.git
synced 2025-08-09 06:39:49 +08:00
225 lines
6.5 KiB
TypeScript
225 lines
6.5 KiB
TypeScript
import type { ProxyOptions, UserConfig } from 'vite'
|
||
import { mkdirSync, writeFileSync } from 'node:fs'
|
||
import { dirname } from 'node:path'
|
||
|
||
/** 服务配置接口 */
|
||
interface ServiceConfig {
|
||
[key: string]: string
|
||
}
|
||
|
||
/** 服务环境类型 */
|
||
type ServiceEnvType = string
|
||
|
||
/** 完整的服务配置类型 */
|
||
interface FullServiceConfig {
|
||
[key: ServiceEnvType]: ServiceConfig
|
||
}
|
||
|
||
/** 代理项接口 */
|
||
interface ProxyItem {
|
||
/** 代理路径 */
|
||
path: string
|
||
/** 原始地址 */
|
||
rawPath: string
|
||
}
|
||
|
||
/** 代理地址映射接口 */
|
||
interface ProxyMapping {
|
||
[serviceName: string]: ProxyItem
|
||
}
|
||
|
||
/** 插件选项接口 */
|
||
export interface ServiceProxyPluginOptions {
|
||
/** 服务配置对象(必填) */
|
||
serviceConfig: FullServiceConfig
|
||
/** 代理路径前缀(可选,默认为 'proxy-') */
|
||
proxyPrefix?: string
|
||
/** 开发环境名称(可选,默认为 'development') */
|
||
devEnvName?: ServiceEnvType
|
||
/** 是否启用代理配置 */
|
||
enableProxy?: boolean
|
||
/** 环境变量名(可选,默认为 '__PROXY_MAPPING__') */
|
||
envName?: string
|
||
/** d.ts 类型文件生成路径(可选,如果传入路径则在该路径生成 d.ts 类型文件) */
|
||
dts?: string
|
||
}
|
||
|
||
export default function createServiceProxyPlugin(options: ServiceProxyPluginOptions) {
|
||
const {
|
||
serviceConfig,
|
||
devEnvName = 'development',
|
||
proxyPrefix = 'proxy-',
|
||
enableProxy = true,
|
||
envName = '__PROXY_MAPPING__',
|
||
dts,
|
||
} = options
|
||
|
||
return {
|
||
name: 'vite-auto-proxy',
|
||
config(config: UserConfig, { command }: { mode: string, command: 'build' | 'serve' }) {
|
||
// 只在开发环境(serve命令)时生成代理配置
|
||
const isDev = command === 'serve'
|
||
|
||
// 在非开发环境也注入空的代理映射,避免运行时错误
|
||
if (!config.define) {
|
||
config.define = {}
|
||
}
|
||
|
||
if (!enableProxy || !isDev) {
|
||
// 在非开发环境下,生成原始地址映射(path 和 rawPath 都是原始地址)
|
||
const rawMapping: ProxyMapping = {}
|
||
const envConfig = serviceConfig[devEnvName]
|
||
if (envConfig) {
|
||
Object.entries(envConfig).forEach(([serviceName, serviceUrl]) => {
|
||
rawMapping[serviceName] = {
|
||
path: serviceUrl,
|
||
rawPath: serviceUrl,
|
||
}
|
||
})
|
||
}
|
||
config.define[envName] = JSON.stringify(rawMapping)
|
||
|
||
// 生成 d.ts 类型文件(如果指定了路径)
|
||
if (dts) {
|
||
generateDtsFile(rawMapping, dts, envName)
|
||
}
|
||
return
|
||
}
|
||
|
||
console.warn(`[auto-proxy] 已加载${devEnvName}模式 ${Object.keys(serviceConfig[devEnvName]).length} 个服务地址`)
|
||
|
||
const { proxyConfig, proxyMapping } = generateProxyFromServiceConfig(serviceConfig, devEnvName, proxyPrefix)
|
||
|
||
Object.entries(proxyMapping).forEach(([serviceName, proxyItem]) => {
|
||
console.warn(`[auto-proxy] 服务: ${serviceName} | 代理地址: ${proxyItem.path} | 实际地址: ${proxyItem.rawPath}`)
|
||
})
|
||
|
||
if (proxyConfig && Object.keys(proxyConfig).length > 0) {
|
||
// 确保 server 对象存在
|
||
if (!config.server) {
|
||
config.server = {}
|
||
}
|
||
|
||
// 合并代理配置
|
||
config.server.proxy = {
|
||
...config.server.proxy,
|
||
...proxyConfig,
|
||
}
|
||
config.define[envName] = JSON.stringify(proxyMapping)
|
||
console.warn(`[auto-proxy] 代理映射已注入到 ${envName}`)
|
||
|
||
// 生成 d.ts 类型文件(如果指定了路径)
|
||
if (dts) {
|
||
generateDtsFile(proxyMapping, dts, envName)
|
||
}
|
||
}
|
||
else {
|
||
console.warn(`[auto-proxy] 未生成任何代理配置`)
|
||
config.define[envName] = JSON.stringify({})
|
||
|
||
// 生成空的 d.ts 类型文件(如果指定了路径)
|
||
if (dts) {
|
||
generateDtsFile({}, dts, envName)
|
||
}
|
||
}
|
||
},
|
||
}
|
||
}
|
||
|
||
function generateProxyFromServiceConfig(
|
||
serviceConfig: FullServiceConfig,
|
||
mode: ServiceEnvType,
|
||
proxyPrefix: string,
|
||
): { proxyConfig: Record<string, ProxyOptions>, proxyMapping: ProxyMapping } {
|
||
try {
|
||
// 获取当前环境的配置
|
||
const envConfig = serviceConfig[mode]
|
||
if (!envConfig) {
|
||
console.warn(`[auto-proxy] 未找到环境 "${mode}" 的配置,使用 development 配置`)
|
||
const defaultConfig = serviceConfig.development
|
||
if (!defaultConfig) {
|
||
console.error(`[auto-proxy] 也未找到 development 配置`)
|
||
return { proxyConfig: {}, proxyMapping: {} }
|
||
}
|
||
return generateProxyFromConfig(defaultConfig, proxyPrefix)
|
||
}
|
||
|
||
return generateProxyFromConfig(envConfig, proxyPrefix)
|
||
}
|
||
catch (error) {
|
||
console.error(`[auto-proxy] 生成代理配置失败:`, (error as Error).message)
|
||
return { proxyConfig: {}, proxyMapping: {} }
|
||
}
|
||
}
|
||
|
||
function generateProxyFromConfig(
|
||
envConfig: ServiceConfig,
|
||
proxyPrefix: string,
|
||
): { proxyConfig: Record<string, ProxyOptions>, proxyMapping: ProxyMapping } {
|
||
const proxyConfig: Record<string, ProxyOptions> = {}
|
||
const proxyMapping: ProxyMapping = {}
|
||
|
||
Object.entries(envConfig).forEach(([serviceName, serviceUrl]) => {
|
||
if (typeof serviceUrl === 'string' && serviceUrl.trim()) {
|
||
const proxyPath = `/${proxyPrefix}${serviceName}`
|
||
|
||
const isWs = serviceUrl.startsWith('ws://') || serviceUrl.startsWith('wss://')
|
||
// 生成代理配置
|
||
proxyConfig[proxyPath] = {
|
||
target: serviceUrl,
|
||
changeOrigin: true,
|
||
ws: isWs,
|
||
rewrite: (path: string): string => path.replace(new RegExp(`^/${proxyPrefix}${serviceName}`), ''),
|
||
}
|
||
|
||
// 生成代理映射
|
||
proxyMapping[serviceName] = {
|
||
path: proxyPath,
|
||
rawPath: serviceUrl,
|
||
}
|
||
}
|
||
})
|
||
|
||
return { proxyConfig, proxyMapping }
|
||
}
|
||
|
||
function generateDtsFile(
|
||
mapping: ProxyMapping,
|
||
outputPath: string,
|
||
envName: string,
|
||
) {
|
||
try {
|
||
const serviceNames = Object.keys(mapping).map(name => `'${name}'`).join(' | ')
|
||
const serviceNameType = serviceNames || 'never'
|
||
|
||
const dtsContent = `/* eslint-disable */
|
||
/* prettier-ignore */
|
||
// @ts-nocheck
|
||
// noinspection JSUnusedGlobalSymbols
|
||
// Generated by auto-proxy
|
||
// biome-ignore lint: disable
|
||
export {}
|
||
|
||
type serviceName = ${serviceNameType}
|
||
|
||
declare global {
|
||
const ${envName}: {
|
||
[K in serviceName]: {
|
||
path: string
|
||
rawPath: string
|
||
}
|
||
}
|
||
}
|
||
`
|
||
|
||
const dir = dirname(outputPath)
|
||
if (dir) {
|
||
mkdirSync(dir, { recursive: true })
|
||
}
|
||
writeFileSync(outputPath, dtsContent, 'utf-8')
|
||
}
|
||
catch (error) {
|
||
console.error(`[auto-proxy] 生成 d.ts 文件失败:`, (error as Error).message)
|
||
}
|
||
}
|