nova-admin/build/autoProxy.ts

225 lines
6.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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