@finesoft/front 0.1.32 → 0.1.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +207 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +75 -6
- package/dist/index.d.ts +75 -6
- package/dist/index.js +205 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -112,11 +112,45 @@ declare function injectCSRShell(template: string, locale: string): string;
|
|
|
112
112
|
declare function serializeServerData(data: PrefetchedIntent[]): string;
|
|
113
113
|
|
|
114
114
|
/**
|
|
115
|
-
*
|
|
115
|
+
* 框架级声明式代理路由
|
|
116
116
|
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
117
|
+
* 将代理路由从业务层手写 Hono 路由,改为框架级配置。
|
|
118
|
+
* 框架统一执行路径校验(SSRF 防护)、Host 限制、错误处理、响应头控制。
|
|
119
119
|
*/
|
|
120
|
+
|
|
121
|
+
/** 代理路由认证配置 */
|
|
122
|
+
interface ProxyAuthConfig {
|
|
123
|
+
/** 认证类型 */
|
|
124
|
+
type: "bearer" | "basic";
|
|
125
|
+
/** 环境变量名称(运行时从 process.env 读取) */
|
|
126
|
+
envKey: string;
|
|
127
|
+
}
|
|
128
|
+
/** 声明式代理路由配置 */
|
|
129
|
+
interface ProxyRouteConfig {
|
|
130
|
+
/** URL 前缀,如 "/api/apple"(必须以 "/" 开头) */
|
|
131
|
+
prefix: string;
|
|
132
|
+
/** 代理目标地址(必须以 "https://" 开头) */
|
|
133
|
+
target: string;
|
|
134
|
+
/** HTTP 方法(默认 ["all"]) */
|
|
135
|
+
methods?: ("all" | "get" | "post" | "put" | "delete" | "patch")[];
|
|
136
|
+
/** 附加到代理请求的头部 */
|
|
137
|
+
headers?: Record<string, string>;
|
|
138
|
+
/** 认证配置 */
|
|
139
|
+
auth?: ProxyAuthConfig;
|
|
140
|
+
/** Cache-Control 响应头 */
|
|
141
|
+
cache?: string;
|
|
142
|
+
/** 是否跟随重定向(默认 false) */
|
|
143
|
+
followRedirects?: boolean;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* 注册声明式代理路由到 Hono app(运行时使用:dev / preview / createServer)
|
|
147
|
+
*/
|
|
148
|
+
declare function registerProxyRoutes(app: Hono, configs: ProxyRouteConfig[]): void;
|
|
149
|
+
/**
|
|
150
|
+
* 生成代理路由的内联代码(用于 serverless/edge 入口,避免运行时依赖)
|
|
151
|
+
*/
|
|
152
|
+
declare function generateProxyCode(configs: ProxyRouteConfig[]): string;
|
|
153
|
+
|
|
120
154
|
/** 适配器上下文 — 由 vite-plugin 的 closeBundle 构造后传入 adapter.build() */
|
|
121
155
|
interface AdapterContext {
|
|
122
156
|
/** 项目根路径 */
|
|
@@ -142,6 +176,8 @@ interface AdapterContext {
|
|
|
142
176
|
* key: 精确路径或 glob 模式,value: "ssr" | "csr" | "prerender"
|
|
143
177
|
*/
|
|
144
178
|
renderModes?: Record<string, string>;
|
|
179
|
+
/** 声明式代理路由配置 */
|
|
180
|
+
proxies?: ProxyRouteConfig[];
|
|
145
181
|
vite: any;
|
|
146
182
|
fs: typeof node_fs;
|
|
147
183
|
path: {
|
|
@@ -369,8 +405,10 @@ interface ServerConfig {
|
|
|
369
405
|
defaultLocale?: string;
|
|
370
406
|
/** 端口号(默认 3000) */
|
|
371
407
|
port?: number;
|
|
372
|
-
/**
|
|
408
|
+
/** 注册自定义路由(在 SSR catch-all 之前调用,但在声明式代理之后) */
|
|
373
409
|
setup?: (app: Hono) => void | Promise<void>;
|
|
410
|
+
/** 声明式代理路由配置 */
|
|
411
|
+
proxies?: ProxyRouteConfig[];
|
|
374
412
|
/** SSR 相关选项(透传给 createSSRApp) */
|
|
375
413
|
ssr?: Pick<SSRAppOptions, "ssrEntryPath" | "ssrProductionModule">;
|
|
376
414
|
}
|
|
@@ -449,10 +487,17 @@ interface FinesoftFrontViteOptions {
|
|
|
449
487
|
entry?: string;
|
|
450
488
|
};
|
|
451
489
|
/**
|
|
452
|
-
*
|
|
490
|
+
* 声明式代理路由配置。
|
|
491
|
+
* 框架统一执行路径校验(SSRF 防护)、Host 限制、错误处理、响应头控制。
|
|
492
|
+
* 代理路由在 setup 之前注册,优先级高于自定义路由。
|
|
493
|
+
*/
|
|
494
|
+
proxies?: ProxyRouteConfig[];
|
|
495
|
+
/**
|
|
496
|
+
* 注册自定义路由(非代理类)。
|
|
453
497
|
* - 传入 Function:仅 dev/preview 时可用。
|
|
454
498
|
* - 传入 string(文件路径):dev/preview/adapter 均可用,
|
|
455
499
|
* 文件需 export default 一个 (app: Hono) => void 函数。
|
|
500
|
+
* 注意:代理路由请使用 proxies 选项,不要在 setup 中手写代理。
|
|
456
501
|
*/
|
|
457
502
|
setup?: ((app: Hono) => void | Promise<void>) | string;
|
|
458
503
|
/**
|
|
@@ -488,9 +533,33 @@ declare function finesoftFrontViteConfig(options?: FinesoftFrontViteOptions): {
|
|
|
488
533
|
command: string;
|
|
489
534
|
}): Record<string, any>;
|
|
490
535
|
configResolved(config: Record<string, any>): void;
|
|
536
|
+
/**
|
|
537
|
+
* Dev 模式 CSS 内联 — 消除 SSR 首屏布局抖动
|
|
538
|
+
*
|
|
539
|
+
* Vite dev 模式下,global.scss 等非组件 CSS 通过 JS 模块系统异步加载,
|
|
540
|
+
* 导致 SSR HTML 初次渲染缺少布局关键样式(box-sizing、flex 布局、padding-top 等)。
|
|
541
|
+
*
|
|
542
|
+
* 此 hook 在 HTML 模板变换阶段(SSR 渲染之前):
|
|
543
|
+
* 1. 找到浏览器入口脚本(排除 /@vite/client 等内部脚本)
|
|
544
|
+
* 2. 编译入口脚本,填充 Vite 模块图
|
|
545
|
+
* 3. 遍历模块图收集所有 CSS 依赖(排除 .svelte 组件 CSS,由 SSR 渲染自行处理)
|
|
546
|
+
* 4. 通过 ssrLoadModule 获取编译后 CSS(SCSS→CSS)
|
|
547
|
+
* 5. 注入 <style data-vite-dev-id> 标签到 <head>
|
|
548
|
+
*
|
|
549
|
+
* data-vite-dev-id 确保 Vite HMR 客户端复用已有标签,避免重复注入。
|
|
550
|
+
*/
|
|
551
|
+
transformIndexHtml: {
|
|
552
|
+
order: "pre";
|
|
553
|
+
handler(html: string, ctx: any): Promise<{
|
|
554
|
+
tag: string;
|
|
555
|
+
attrs: Record<string, string>;
|
|
556
|
+
children: string;
|
|
557
|
+
injectTo: string;
|
|
558
|
+
}[] | undefined>;
|
|
559
|
+
};
|
|
491
560
|
configureServer(server: any): () => Promise<void>;
|
|
492
561
|
configurePreviewServer(server: any): () => Promise<void>;
|
|
493
562
|
closeBundle(): Promise<void>;
|
|
494
563
|
};
|
|
495
564
|
|
|
496
|
-
export { type Adapter, type AdapterContext, BasePage, type FinesoftFrontViteOptions, Framework, FrameworkConfig, type InjectSSROptions, PrefetchedIntent, type RuntimeInfo, type SSRAppOptions, type SSRContext, type SSRModule, type SSRRenderConfig, type SSRRenderOptions, type SSRRenderResult, SSR_PLACEHOLDERS, type ServerConfig, type ServerInstance, type StartServerOptions, autoAdapter, cloudflareAdapter, createSSRApp, createSSRRender, createServer, detectRuntime, finesoftFrontViteConfig, injectCSRShell, injectSSRContent, netlifyAdapter, nodeAdapter, parseAcceptLanguage, resolveAdapter, resolveRoot, serializeServerData, ssrRender, startServer, staticAdapter, vercelAdapter };
|
|
565
|
+
export { type Adapter, type AdapterContext, BasePage, type FinesoftFrontViteOptions, Framework, FrameworkConfig, type InjectSSROptions, PrefetchedIntent, type ProxyAuthConfig, type ProxyRouteConfig, type RuntimeInfo, type SSRAppOptions, type SSRContext, type SSRModule, type SSRRenderConfig, type SSRRenderOptions, type SSRRenderResult, SSR_PLACEHOLDERS, type ServerConfig, type ServerInstance, type StartServerOptions, autoAdapter, cloudflareAdapter, createSSRApp, createSSRRender, createServer, detectRuntime, finesoftFrontViteConfig, generateProxyCode, injectCSRShell, injectSSRContent, netlifyAdapter, nodeAdapter, parseAcceptLanguage, registerProxyRoutes, resolveAdapter, resolveRoot, serializeServerData, ssrRender, startServer, staticAdapter, vercelAdapter };
|
package/dist/index.d.ts
CHANGED
|
@@ -112,11 +112,45 @@ declare function injectCSRShell(template: string, locale: string): string;
|
|
|
112
112
|
declare function serializeServerData(data: PrefetchedIntent[]): string;
|
|
113
113
|
|
|
114
114
|
/**
|
|
115
|
-
*
|
|
115
|
+
* 框架级声明式代理路由
|
|
116
116
|
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
117
|
+
* 将代理路由从业务层手写 Hono 路由,改为框架级配置。
|
|
118
|
+
* 框架统一执行路径校验(SSRF 防护)、Host 限制、错误处理、响应头控制。
|
|
119
119
|
*/
|
|
120
|
+
|
|
121
|
+
/** 代理路由认证配置 */
|
|
122
|
+
interface ProxyAuthConfig {
|
|
123
|
+
/** 认证类型 */
|
|
124
|
+
type: "bearer" | "basic";
|
|
125
|
+
/** 环境变量名称(运行时从 process.env 读取) */
|
|
126
|
+
envKey: string;
|
|
127
|
+
}
|
|
128
|
+
/** 声明式代理路由配置 */
|
|
129
|
+
interface ProxyRouteConfig {
|
|
130
|
+
/** URL 前缀,如 "/api/apple"(必须以 "/" 开头) */
|
|
131
|
+
prefix: string;
|
|
132
|
+
/** 代理目标地址(必须以 "https://" 开头) */
|
|
133
|
+
target: string;
|
|
134
|
+
/** HTTP 方法(默认 ["all"]) */
|
|
135
|
+
methods?: ("all" | "get" | "post" | "put" | "delete" | "patch")[];
|
|
136
|
+
/** 附加到代理请求的头部 */
|
|
137
|
+
headers?: Record<string, string>;
|
|
138
|
+
/** 认证配置 */
|
|
139
|
+
auth?: ProxyAuthConfig;
|
|
140
|
+
/** Cache-Control 响应头 */
|
|
141
|
+
cache?: string;
|
|
142
|
+
/** 是否跟随重定向(默认 false) */
|
|
143
|
+
followRedirects?: boolean;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* 注册声明式代理路由到 Hono app(运行时使用:dev / preview / createServer)
|
|
147
|
+
*/
|
|
148
|
+
declare function registerProxyRoutes(app: Hono, configs: ProxyRouteConfig[]): void;
|
|
149
|
+
/**
|
|
150
|
+
* 生成代理路由的内联代码(用于 serverless/edge 入口,避免运行时依赖)
|
|
151
|
+
*/
|
|
152
|
+
declare function generateProxyCode(configs: ProxyRouteConfig[]): string;
|
|
153
|
+
|
|
120
154
|
/** 适配器上下文 — 由 vite-plugin 的 closeBundle 构造后传入 adapter.build() */
|
|
121
155
|
interface AdapterContext {
|
|
122
156
|
/** 项目根路径 */
|
|
@@ -142,6 +176,8 @@ interface AdapterContext {
|
|
|
142
176
|
* key: 精确路径或 glob 模式,value: "ssr" | "csr" | "prerender"
|
|
143
177
|
*/
|
|
144
178
|
renderModes?: Record<string, string>;
|
|
179
|
+
/** 声明式代理路由配置 */
|
|
180
|
+
proxies?: ProxyRouteConfig[];
|
|
145
181
|
vite: any;
|
|
146
182
|
fs: typeof node_fs;
|
|
147
183
|
path: {
|
|
@@ -369,8 +405,10 @@ interface ServerConfig {
|
|
|
369
405
|
defaultLocale?: string;
|
|
370
406
|
/** 端口号(默认 3000) */
|
|
371
407
|
port?: number;
|
|
372
|
-
/**
|
|
408
|
+
/** 注册自定义路由(在 SSR catch-all 之前调用,但在声明式代理之后) */
|
|
373
409
|
setup?: (app: Hono) => void | Promise<void>;
|
|
410
|
+
/** 声明式代理路由配置 */
|
|
411
|
+
proxies?: ProxyRouteConfig[];
|
|
374
412
|
/** SSR 相关选项(透传给 createSSRApp) */
|
|
375
413
|
ssr?: Pick<SSRAppOptions, "ssrEntryPath" | "ssrProductionModule">;
|
|
376
414
|
}
|
|
@@ -449,10 +487,17 @@ interface FinesoftFrontViteOptions {
|
|
|
449
487
|
entry?: string;
|
|
450
488
|
};
|
|
451
489
|
/**
|
|
452
|
-
*
|
|
490
|
+
* 声明式代理路由配置。
|
|
491
|
+
* 框架统一执行路径校验(SSRF 防护)、Host 限制、错误处理、响应头控制。
|
|
492
|
+
* 代理路由在 setup 之前注册,优先级高于自定义路由。
|
|
493
|
+
*/
|
|
494
|
+
proxies?: ProxyRouteConfig[];
|
|
495
|
+
/**
|
|
496
|
+
* 注册自定义路由(非代理类)。
|
|
453
497
|
* - 传入 Function:仅 dev/preview 时可用。
|
|
454
498
|
* - 传入 string(文件路径):dev/preview/adapter 均可用,
|
|
455
499
|
* 文件需 export default 一个 (app: Hono) => void 函数。
|
|
500
|
+
* 注意:代理路由请使用 proxies 选项,不要在 setup 中手写代理。
|
|
456
501
|
*/
|
|
457
502
|
setup?: ((app: Hono) => void | Promise<void>) | string;
|
|
458
503
|
/**
|
|
@@ -488,9 +533,33 @@ declare function finesoftFrontViteConfig(options?: FinesoftFrontViteOptions): {
|
|
|
488
533
|
command: string;
|
|
489
534
|
}): Record<string, any>;
|
|
490
535
|
configResolved(config: Record<string, any>): void;
|
|
536
|
+
/**
|
|
537
|
+
* Dev 模式 CSS 内联 — 消除 SSR 首屏布局抖动
|
|
538
|
+
*
|
|
539
|
+
* Vite dev 模式下,global.scss 等非组件 CSS 通过 JS 模块系统异步加载,
|
|
540
|
+
* 导致 SSR HTML 初次渲染缺少布局关键样式(box-sizing、flex 布局、padding-top 等)。
|
|
541
|
+
*
|
|
542
|
+
* 此 hook 在 HTML 模板变换阶段(SSR 渲染之前):
|
|
543
|
+
* 1. 找到浏览器入口脚本(排除 /@vite/client 等内部脚本)
|
|
544
|
+
* 2. 编译入口脚本,填充 Vite 模块图
|
|
545
|
+
* 3. 遍历模块图收集所有 CSS 依赖(排除 .svelte 组件 CSS,由 SSR 渲染自行处理)
|
|
546
|
+
* 4. 通过 ssrLoadModule 获取编译后 CSS(SCSS→CSS)
|
|
547
|
+
* 5. 注入 <style data-vite-dev-id> 标签到 <head>
|
|
548
|
+
*
|
|
549
|
+
* data-vite-dev-id 确保 Vite HMR 客户端复用已有标签,避免重复注入。
|
|
550
|
+
*/
|
|
551
|
+
transformIndexHtml: {
|
|
552
|
+
order: "pre";
|
|
553
|
+
handler(html: string, ctx: any): Promise<{
|
|
554
|
+
tag: string;
|
|
555
|
+
attrs: Record<string, string>;
|
|
556
|
+
children: string;
|
|
557
|
+
injectTo: string;
|
|
558
|
+
}[] | undefined>;
|
|
559
|
+
};
|
|
491
560
|
configureServer(server: any): () => Promise<void>;
|
|
492
561
|
configurePreviewServer(server: any): () => Promise<void>;
|
|
493
562
|
closeBundle(): Promise<void>;
|
|
494
563
|
};
|
|
495
564
|
|
|
496
|
-
export { type Adapter, type AdapterContext, BasePage, type FinesoftFrontViteOptions, Framework, FrameworkConfig, type InjectSSROptions, PrefetchedIntent, type RuntimeInfo, type SSRAppOptions, type SSRContext, type SSRModule, type SSRRenderConfig, type SSRRenderOptions, type SSRRenderResult, SSR_PLACEHOLDERS, type ServerConfig, type ServerInstance, type StartServerOptions, autoAdapter, cloudflareAdapter, createSSRApp, createSSRRender, createServer, detectRuntime, finesoftFrontViteConfig, injectCSRShell, injectSSRContent, netlifyAdapter, nodeAdapter, parseAcceptLanguage, resolveAdapter, resolveRoot, serializeServerData, ssrRender, startServer, staticAdapter, vercelAdapter };
|
|
565
|
+
export { type Adapter, type AdapterContext, BasePage, type FinesoftFrontViteOptions, Framework, FrameworkConfig, type InjectSSROptions, PrefetchedIntent, type ProxyAuthConfig, type ProxyRouteConfig, type RuntimeInfo, type SSRAppOptions, type SSRContext, type SSRModule, type SSRRenderConfig, type SSRRenderOptions, type SSRRenderResult, SSR_PLACEHOLDERS, type ServerConfig, type ServerInstance, type StartServerOptions, autoAdapter, cloudflareAdapter, createSSRApp, createSSRRender, createServer, detectRuntime, finesoftFrontViteConfig, generateProxyCode, injectCSRShell, injectSSRContent, netlifyAdapter, nodeAdapter, parseAcceptLanguage, registerProxyRoutes, resolveAdapter, resolveRoot, serializeServerData, ssrRender, startServer, staticAdapter, vercelAdapter };
|
package/dist/index.js
CHANGED
|
@@ -66,6 +66,120 @@ import {
|
|
|
66
66
|
parseAcceptLanguage
|
|
67
67
|
} from "./chunk-PSPVIVC2.js";
|
|
68
68
|
|
|
69
|
+
// ../server/src/proxy.ts
|
|
70
|
+
function sanitizeProxyPath(raw) {
|
|
71
|
+
if (raw.startsWith("//")) return null;
|
|
72
|
+
return raw.startsWith("/") ? raw : `/${raw}`;
|
|
73
|
+
}
|
|
74
|
+
function validateConfig(config) {
|
|
75
|
+
if (!config.prefix.startsWith("/")) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
`[proxy] prefix must start with "/": "${config.prefix}"`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
if (!config.target.startsWith("https://")) {
|
|
81
|
+
throw new Error(`[proxy] target must use HTTPS: "${config.target}"`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function registerProxyRoutes(app, configs) {
|
|
85
|
+
for (const config of configs) {
|
|
86
|
+
validateConfig(config);
|
|
87
|
+
const methods = config.methods ?? ["all"];
|
|
88
|
+
const pattern = `${config.prefix}/*`;
|
|
89
|
+
const handler = async (c) => {
|
|
90
|
+
const subPath = sanitizeProxyPath(
|
|
91
|
+
c.req.path.replace(config.prefix, "")
|
|
92
|
+
);
|
|
93
|
+
if (!subPath) return c.text("Invalid path", 400);
|
|
94
|
+
const targetUrl = new URL(subPath, config.target);
|
|
95
|
+
const reqUrl = new URL(c.req.url);
|
|
96
|
+
reqUrl.searchParams.forEach(
|
|
97
|
+
(v, k) => targetUrl.searchParams.set(k, v)
|
|
98
|
+
);
|
|
99
|
+
const headers = { ...config.headers };
|
|
100
|
+
if (config.auth) {
|
|
101
|
+
const token = process.env[config.auth.envKey] ?? "";
|
|
102
|
+
if (token) {
|
|
103
|
+
headers.Authorization = config.auth.type === "bearer" ? `Bearer ${token}` : `Basic ${token}`;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
const resp = await fetch(targetUrl.toString(), {
|
|
108
|
+
headers,
|
|
109
|
+
redirect: config.followRedirects ? "follow" : "manual"
|
|
110
|
+
});
|
|
111
|
+
const body = await resp.text();
|
|
112
|
+
const respHeaders = {
|
|
113
|
+
"Content-Type": resp.headers.get("Content-Type") ?? "application/json"
|
|
114
|
+
};
|
|
115
|
+
if (config.cache) {
|
|
116
|
+
respHeaders["Cache-Control"] = config.cache;
|
|
117
|
+
}
|
|
118
|
+
return c.newResponse(body, resp.status, respHeaders);
|
|
119
|
+
} catch (e) {
|
|
120
|
+
console.error(`[Proxy ${config.prefix}]`, e);
|
|
121
|
+
return c.json({ error: "Proxy request failed" }, 502);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
for (const method of methods) {
|
|
125
|
+
app[method](pattern, handler);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function generateProxyCode(configs) {
|
|
130
|
+
if (!configs || configs.length === 0) return "";
|
|
131
|
+
for (const config of configs) {
|
|
132
|
+
validateConfig(config);
|
|
133
|
+
}
|
|
134
|
+
const blocks = [];
|
|
135
|
+
blocks.push(`
|
|
136
|
+
// \u2500\u2500\u2500 \u6846\u67B6\u58F0\u660E\u5F0F\u4EE3\u7406\u8DEF\u7531 \u2500\u2500\u2500
|
|
137
|
+
function _sanitizeProxyPath(raw) {
|
|
138
|
+
if (raw.startsWith("//")) return null;
|
|
139
|
+
return raw.startsWith("/") ? raw : "/" + raw;
|
|
140
|
+
}
|
|
141
|
+
`);
|
|
142
|
+
for (const config of configs) {
|
|
143
|
+
const methods = config.methods ?? ["all"];
|
|
144
|
+
const pattern = `"${config.prefix}/*"`;
|
|
145
|
+
const headersJson = JSON.stringify(config.headers ?? {});
|
|
146
|
+
const cacheStr = config.cache ? JSON.stringify(config.cache) : "null";
|
|
147
|
+
const redirect = config.followRedirects ? '"follow"' : '"manual"';
|
|
148
|
+
let authCode = "";
|
|
149
|
+
if (config.auth) {
|
|
150
|
+
const envKey = JSON.stringify(config.auth.envKey);
|
|
151
|
+
const prefix = config.auth.type === "bearer" ? "Bearer " : "Basic ";
|
|
152
|
+
authCode = `
|
|
153
|
+
const _token = (typeof process !== "undefined" && process.env && process.env[${envKey}]) || "";
|
|
154
|
+
if (_token) _headers.Authorization = "${prefix}" + _token;`;
|
|
155
|
+
}
|
|
156
|
+
const handlerCode = `async (c) => {
|
|
157
|
+
const _sub = _sanitizeProxyPath(c.req.path.replace(${JSON.stringify(
|
|
158
|
+
config.prefix
|
|
159
|
+
)}, ""));
|
|
160
|
+
if (!_sub) return c.text("Invalid path", 400);
|
|
161
|
+
const _target = new URL(_sub, ${JSON.stringify(config.target)});
|
|
162
|
+
const _reqUrl = new URL(c.req.url);
|
|
163
|
+
_reqUrl.searchParams.forEach((v, k) => _target.searchParams.set(k, v));
|
|
164
|
+
const _headers = ${headersJson};${authCode}
|
|
165
|
+
try {
|
|
166
|
+
const _resp = await fetch(_target.toString(), { headers: _headers, redirect: ${redirect} });
|
|
167
|
+
const _body = await _resp.text();
|
|
168
|
+
const _rh = { "Content-Type": _resp.headers.get("Content-Type") || "application/json" };
|
|
169
|
+
if (${cacheStr}) _rh["Cache-Control"] = ${cacheStr};
|
|
170
|
+
return c.newResponse(_body, _resp.status, _rh);
|
|
171
|
+
} catch (_e) {
|
|
172
|
+
console.error("[Proxy ${config.prefix}]", _e);
|
|
173
|
+
return c.json({ error: "Proxy request failed" }, 502);
|
|
174
|
+
}
|
|
175
|
+
}`;
|
|
176
|
+
for (const method of methods) {
|
|
177
|
+
blocks.push(`app.${method}(${pattern}, ${handlerCode});`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return blocks.join("\n");
|
|
181
|
+
}
|
|
182
|
+
|
|
69
183
|
// ../server/src/adapters/shared.ts
|
|
70
184
|
var BUILD_TOOL_EXTERNALS = [
|
|
71
185
|
"vite",
|
|
@@ -148,6 +262,7 @@ function matchRenderMode(url) {
|
|
|
148
262
|
}
|
|
149
263
|
|
|
150
264
|
const app = new Hono();
|
|
265
|
+
${generateProxyCode(ctx.proxies ?? [])}
|
|
151
266
|
${setupCall}
|
|
152
267
|
${opts.platformMiddleware ?? ""}
|
|
153
268
|
|
|
@@ -1003,6 +1118,7 @@ async function createServer(config = {}) {
|
|
|
1003
1118
|
defaultLocale,
|
|
1004
1119
|
port = Number(process.env.PORT) || 3e3,
|
|
1005
1120
|
setup,
|
|
1121
|
+
proxies,
|
|
1006
1122
|
ssr
|
|
1007
1123
|
} = config;
|
|
1008
1124
|
const root = rootOverride ?? process.cwd();
|
|
@@ -1039,6 +1155,9 @@ async function createServer(config = {}) {
|
|
|
1039
1155
|
});
|
|
1040
1156
|
}
|
|
1041
1157
|
const app = new Hono2();
|
|
1158
|
+
if (proxies?.length) {
|
|
1159
|
+
registerProxyRoutes(app, proxies);
|
|
1160
|
+
}
|
|
1042
1161
|
if (setup) {
|
|
1043
1162
|
await setup(app);
|
|
1044
1163
|
}
|
|
@@ -1091,6 +1210,7 @@ function finesoftFrontViteConfig(options = {}) {
|
|
|
1091
1210
|
let resolvedCommand;
|
|
1092
1211
|
let resolvedResolve;
|
|
1093
1212
|
let resolvedCss;
|
|
1213
|
+
const CSS_EXTENSIONS = /\.(css|scss|less|sass|styl|stylus|pcss|postcss)($|\?)/;
|
|
1094
1214
|
return {
|
|
1095
1215
|
name: "finesoft-front",
|
|
1096
1216
|
config(userConfig, env) {
|
|
@@ -1110,6 +1230,82 @@ function finesoftFrontViteConfig(options = {}) {
|
|
|
1110
1230
|
resolvedCss = config.css;
|
|
1111
1231
|
root = config.root;
|
|
1112
1232
|
},
|
|
1233
|
+
/**
|
|
1234
|
+
* Dev 模式 CSS 内联 — 消除 SSR 首屏布局抖动
|
|
1235
|
+
*
|
|
1236
|
+
* Vite dev 模式下,global.scss 等非组件 CSS 通过 JS 模块系统异步加载,
|
|
1237
|
+
* 导致 SSR HTML 初次渲染缺少布局关键样式(box-sizing、flex 布局、padding-top 等)。
|
|
1238
|
+
*
|
|
1239
|
+
* 此 hook 在 HTML 模板变换阶段(SSR 渲染之前):
|
|
1240
|
+
* 1. 找到浏览器入口脚本(排除 /@vite/client 等内部脚本)
|
|
1241
|
+
* 2. 编译入口脚本,填充 Vite 模块图
|
|
1242
|
+
* 3. 遍历模块图收集所有 CSS 依赖(排除 .svelte 组件 CSS,由 SSR 渲染自行处理)
|
|
1243
|
+
* 4. 通过 ssrLoadModule 获取编译后 CSS(SCSS→CSS)
|
|
1244
|
+
* 5. 注入 <style data-vite-dev-id> 标签到 <head>
|
|
1245
|
+
*
|
|
1246
|
+
* data-vite-dev-id 确保 Vite HMR 客户端复用已有标签,避免重复注入。
|
|
1247
|
+
*/
|
|
1248
|
+
transformIndexHtml: {
|
|
1249
|
+
order: "pre",
|
|
1250
|
+
async handler(html, ctx) {
|
|
1251
|
+
const server = ctx.server;
|
|
1252
|
+
if (!server) return;
|
|
1253
|
+
const urlPath = (ctx.originalUrl || ctx.path || "").split(
|
|
1254
|
+
"?"
|
|
1255
|
+
)[0];
|
|
1256
|
+
if (/\.\w+$/.test(urlPath) && !urlPath.endsWith(".html")) {
|
|
1257
|
+
return;
|
|
1258
|
+
}
|
|
1259
|
+
const scripts = [
|
|
1260
|
+
...html.matchAll(
|
|
1261
|
+
/<script\b[^>]*\bsrc=["']([^"']+)["'][^>]*>/g
|
|
1262
|
+
)
|
|
1263
|
+
];
|
|
1264
|
+
const appEntry = scripts.find((m) => !m[1].startsWith("/@"));
|
|
1265
|
+
if (!appEntry) return;
|
|
1266
|
+
const browserEntry = appEntry[1];
|
|
1267
|
+
try {
|
|
1268
|
+
await server.transformRequest(browserEntry);
|
|
1269
|
+
} catch {
|
|
1270
|
+
return;
|
|
1271
|
+
}
|
|
1272
|
+
const cssUrls = [];
|
|
1273
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1274
|
+
function walk(mod) {
|
|
1275
|
+
if (!mod?.url || visited.has(mod.url)) return;
|
|
1276
|
+
visited.add(mod.url);
|
|
1277
|
+
if (CSS_EXTENSIONS.test(mod.url) && !mod.url.includes(".svelte")) {
|
|
1278
|
+
cssUrls.push(mod.url);
|
|
1279
|
+
}
|
|
1280
|
+
if (mod.importedModules) {
|
|
1281
|
+
for (const imported of mod.importedModules) {
|
|
1282
|
+
walk(imported);
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
const mg = server.moduleGraph;
|
|
1287
|
+
const browserMod = await mg.getModuleByUrl(browserEntry);
|
|
1288
|
+
if (browserMod) walk(browserMod);
|
|
1289
|
+
if (cssUrls.length === 0) return;
|
|
1290
|
+
const tags = [];
|
|
1291
|
+
for (const url of cssUrls) {
|
|
1292
|
+
try {
|
|
1293
|
+
const mod = await server.ssrLoadModule(url);
|
|
1294
|
+
const css = mod?.default;
|
|
1295
|
+
if (typeof css === "string" && css.length > 0) {
|
|
1296
|
+
tags.push({
|
|
1297
|
+
tag: "style",
|
|
1298
|
+
attrs: { "data-vite-dev-id": url },
|
|
1299
|
+
children: css,
|
|
1300
|
+
injectTo: "head"
|
|
1301
|
+
});
|
|
1302
|
+
}
|
|
1303
|
+
} catch {
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
return tags;
|
|
1307
|
+
}
|
|
1308
|
+
},
|
|
1113
1309
|
// ─── Dev ───────────────────────────────────────────────
|
|
1114
1310
|
configureServer(server) {
|
|
1115
1311
|
return async () => {
|
|
@@ -1123,6 +1319,9 @@ function finesoftFrontViteConfig(options = {}) {
|
|
|
1123
1319
|
"@hono/node-server"
|
|
1124
1320
|
);
|
|
1125
1321
|
const app = new HonoClass();
|
|
1322
|
+
if (options.proxies?.length) {
|
|
1323
|
+
registerProxyRoutes(app, options.proxies);
|
|
1324
|
+
}
|
|
1126
1325
|
if (typeof options.setup === "function") {
|
|
1127
1326
|
await options.setup(app);
|
|
1128
1327
|
} else if (typeof options.setup === "string") {
|
|
@@ -1176,6 +1375,9 @@ function finesoftFrontViteConfig(options = {}) {
|
|
|
1176
1375
|
);
|
|
1177
1376
|
const app = new HonoClass();
|
|
1178
1377
|
const isrCache = /* @__PURE__ */ new Map();
|
|
1378
|
+
if (options.proxies?.length) {
|
|
1379
|
+
registerProxyRoutes(app, options.proxies);
|
|
1380
|
+
}
|
|
1179
1381
|
if (typeof options.setup === "function") {
|
|
1180
1382
|
await options.setup(app);
|
|
1181
1383
|
} else if (typeof options.setup === "string") {
|
|
@@ -1330,6 +1532,7 @@ function finesoftFrontViteConfig(options = {}) {
|
|
|
1330
1532
|
defaultLocale,
|
|
1331
1533
|
templateHtml,
|
|
1332
1534
|
renderModes: options.renderModes,
|
|
1535
|
+
proxies: options.proxies,
|
|
1333
1536
|
resolvedResolve,
|
|
1334
1537
|
resolvedCss,
|
|
1335
1538
|
vite,
|
|
@@ -1386,6 +1589,7 @@ export {
|
|
|
1386
1589
|
deserializeServerData,
|
|
1387
1590
|
detectRuntime,
|
|
1388
1591
|
finesoftFrontViteConfig,
|
|
1592
|
+
generateProxyCode,
|
|
1389
1593
|
generateUuid,
|
|
1390
1594
|
getBaseUrl,
|
|
1391
1595
|
injectCSRShell,
|
|
@@ -1407,6 +1611,7 @@ export {
|
|
|
1407
1611
|
registerActionHandlers,
|
|
1408
1612
|
registerExternalUrlHandler,
|
|
1409
1613
|
registerFlowActionHandler,
|
|
1614
|
+
registerProxyRoutes,
|
|
1410
1615
|
removeHost,
|
|
1411
1616
|
removeQueryParams,
|
|
1412
1617
|
removeScheme,
|