Files
EasyFlow/easyflow-ui-admin/app/vite-base-path-redirect.ts
陈子默 a75f7baf1b fix: 统一管理端开发与部署路由契约
- 开发与生产统一使用 /flow/ 基路径和 Hash 路由

- 自动规范化本地根路径入口并补充契约测试
2026-07-24 19:00:47 +08:00

96 lines
2.7 KiB
TypeScript

import type { Plugin } from 'vite';
function normalizeBasePath(base: string) {
const baseSegment = base.trim().replaceAll(/^\/+|\/+$/g, '');
return baseSegment ? `/${baseSegment}/` : '/';
}
/**
* 解析开发服务器入口地址的规范化跳转目标。
*
* @param requestUrl 当前请求地址
* @param base Vite 应用基路径
* @returns 需要跳转时返回目标地址,否则返回 null
*/
export function resolveBasePathRedirect(
requestUrl: string | undefined,
base: string,
): null | string {
if (!requestUrl) {
return null;
}
const normalizedBase = normalizeBasePath(base);
if (normalizedBase === '/') {
return null;
}
const request = new URL(requestUrl, 'http://easyflow.local');
const baseWithoutTrailingSlash = normalizedBase.slice(0, -1);
if (
request.pathname !== '/' &&
request.pathname !== baseWithoutTrailingSlash
) {
return null;
}
return `${normalizedBase}${request.search}`;
}
/**
* 创建开发入口规范化脚本,确保 HTML 回退先于前端路由执行跳转。
*
* @param base Vite 应用基路径
* @returns 可注入 HTML 头部的同步脚本
*/
export function buildBasePathRedirectScript(base: string): string {
const normalizedBase = normalizeBasePath(base);
const baseWithoutTrailingSlash = normalizedBase.slice(0, -1);
return `(()=>{const l=window.location;if(${JSON.stringify(normalizedBase)}!=="/"&&(l.pathname==="/"||l.pathname===${JSON.stringify(baseWithoutTrailingSlash)})){l.replace(${JSON.stringify(normalizedBase)}+l.search+l.hash)}})();`;
}
/**
* 创建开发服务器入口规范化插件。
*
* Vite 会对浏览器导航请求提前执行 HTML 回退,因此同时使用服务端跳转
* 与同步头部脚本,保证 `/`、`/flow` 均在路由初始化前进入 `/flow/`。
*
* @returns Vite 开发服务器插件
*/
export function createBasePathRedirectPlugin(): Plugin {
let resolvedBase = '/';
return {
apply: 'serve',
configResolved(config) {
resolvedBase = config.base;
},
configureServer(server) {
server.middlewares.use((request, response, next) => {
const redirectTarget = resolveBasePathRedirect(
request.url,
server.config.base,
);
if (!redirectTarget) {
next();
return;
}
response.statusCode = 302;
response.setHeader('Location', redirectTarget);
response.end();
});
},
enforce: 'pre',
name: 'easyflow-base-path-redirect',
transformIndexHtml: {
handler() {
return [
{
children: buildBasePathRedirectScript(resolvedBase),
injectTo: 'head-prepend',
tag: 'script',
},
];
},
order: 'pre',
},
};
}