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', }, }; }