@finesoft/front 0.1.31 → 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.
Files changed (33) hide show
  1. package/dist/app-OOLQDVXA.js +10 -0
  2. package/dist/browser.cjs +13 -9
  3. package/dist/browser.cjs.map +1 -1
  4. package/dist/browser.d.cts +1 -1
  5. package/dist/browser.d.ts +1 -1
  6. package/dist/browser.js +2 -2
  7. package/dist/{chunk-ZMOE42LB.js → chunk-AYO3UUQC.js} +39 -37
  8. package/dist/chunk-AYO3UUQC.js.map +1 -0
  9. package/dist/{chunk-H3RNYNSD.js → chunk-OE5BU5MR.js} +2 -2
  10. package/dist/{chunk-BUYWNNNQ.js → chunk-OXKFPW4U.js} +14 -10
  11. package/dist/chunk-OXKFPW4U.js.map +1 -0
  12. package/dist/{chunk-FYP2ZYYV.js → chunk-PSPVIVC2.js} +2 -2
  13. package/dist/chunk-PSPVIVC2.js.map +1 -0
  14. package/dist/{chunk-M7VITIMR.js → chunk-UHQBKSHL.js} +3 -3
  15. package/dist/index.cjs +265 -49
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +75 -6
  18. package/dist/index.d.ts +75 -6
  19. package/dist/index.js +220 -12
  20. package/dist/index.js.map +1 -1
  21. package/dist/locale-CAZ4INCX.js +7 -0
  22. package/dist/{src-B5YAJXIM.js → src-MVACJWBF.js} +3 -3
  23. package/package.json +1 -1
  24. package/dist/app-IA6JQC7V.js +0 -10
  25. package/dist/chunk-BUYWNNNQ.js.map +0 -1
  26. package/dist/chunk-FYP2ZYYV.js.map +0 -1
  27. package/dist/chunk-ZMOE42LB.js.map +0 -1
  28. package/dist/locale-YK3THSI6.js +0 -7
  29. /package/dist/{app-IA6JQC7V.js.map → app-OOLQDVXA.js.map} +0 -0
  30. /package/dist/{chunk-H3RNYNSD.js.map → chunk-OE5BU5MR.js.map} +0 -0
  31. /package/dist/{chunk-M7VITIMR.js.map → chunk-UHQBKSHL.js.map} +0 -0
  32. /package/dist/{locale-YK3THSI6.js.map → locale-CAZ4INCX.js.map} +0 -0
  33. /package/dist/{src-B5YAJXIM.js.map → src-MVACJWBF.js.map} +0 -0
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
- * Adapter 接口定义
115
+ * 框架级声明式代理路由
116
116
  *
117
- * 每个适配器实现 Adapter 接口,在 vite build 的 closeBundle 阶段
118
- * 接收 AdapterContext 并生成目标平台的部署产物。
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
- /** 注册业务路由(在 SSR catch-all 之前调用) */
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
- * 注册业务路由(API 代理等)。
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
- * Adapter 接口定义
115
+ * 框架级声明式代理路由
116
116
  *
117
- * 每个适配器实现 Adapter 接口,在 vite build 的 closeBundle 阶段
118
- * 接收 AdapterContext 并生成目标平台的部署产物。
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
- /** 注册业务路由(在 SSR catch-all 之前调用) */
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
- * 注册业务路由(API 代理等)。
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
@@ -7,13 +7,13 @@ import {
7
7
  registerFlowActionHandler,
8
8
  startBrowserApp,
9
9
  tryScroll
10
- } from "./chunk-H3RNYNSD.js";
10
+ } from "./chunk-OE5BU5MR.js";
11
11
  import {
12
12
  MAX_SSR_DEPTH,
13
13
  SSR_DEPTH_HEADER,
14
14
  createInternalFetch,
15
15
  createSSRApp
16
- } from "./chunk-M7VITIMR.js";
16
+ } from "./chunk-UHQBKSHL.js";
17
17
  import {
18
18
  SSR_PLACEHOLDERS,
19
19
  createSSRRender,
@@ -21,7 +21,7 @@ import {
21
21
  injectSSRContent,
22
22
  serializeServerData,
23
23
  ssrRender
24
- } from "./chunk-ZMOE42LB.js";
24
+ } from "./chunk-AYO3UUQC.js";
25
25
  import {
26
26
  ACTION_KINDS,
27
27
  ActionDispatcher,
@@ -61,10 +61,124 @@ import {
61
61
  resetFilterCache,
62
62
  shouldLog,
63
63
  stableStringify
64
- } from "./chunk-BUYWNNNQ.js";
64
+ } from "./chunk-OXKFPW4U.js";
65
65
  import {
66
66
  parseAcceptLanguage
67
- } from "./chunk-FYP2ZYYV.js";
67
+ } from "./chunk-PSPVIVC2.js";
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
+ }
68
182
 
69
183
  // ../server/src/adapters/shared.ts
70
184
  var BUILD_TOOL_EXTERNALS = [
@@ -109,7 +223,7 @@ function parseAcceptLanguage(header) {
109
223
  if (!header) return DEFAULT_LOCALE;
110
224
  const langs = header.split(",").map(p => {
111
225
  const [l, q] = p.trim().split(";q=");
112
- return { l: l.trim().toLowerCase(), q: q ? +q : 1 };
226
+ return { l: l.trim().toLowerCase(), q: q ? (+q || 0) : 1 };
113
227
  }).sort((a, b) => b.q - a.q);
114
228
  for (const { l } of langs) {
115
229
  const prefix = l.split("-")[0];
@@ -139,7 +253,8 @@ function matchRenderMode(url) {
139
253
  if (RENDER_MODES[path]) return RENDER_MODES[path];
140
254
  for (const [pattern, mode] of Object.entries(RENDER_MODES)) {
141
255
  if (pattern.includes("*")) {
142
- const re = new RegExp("^" + pattern.replace(/\\*/g, ".*") + "$");
256
+ const escaped = pattern.replace(/[.+?^\${}()|[\\]\\\\]/g, "\\\\$&");
257
+ const re = new RegExp("^" + escaped.replace(/\\*/g, ".*") + "$");
143
258
  if (re.test(path)) return mode;
144
259
  }
145
260
  }
@@ -147,6 +262,7 @@ function matchRenderMode(url) {
147
262
  }
148
263
 
149
264
  const app = new Hono();
265
+ ${generateProxyCode(ctx.proxies ?? [])}
150
266
  ${setupCall}
151
267
  ${opts.platformMiddleware ?? ""}
152
268
 
@@ -699,7 +815,8 @@ function resolveRenderMode(routePath, routeRenderMode, renderModes) {
699
815
  if (renderModes[routePath]) return renderModes[routePath];
700
816
  for (const [pattern, mode] of Object.entries(renderModes)) {
701
817
  if (pattern.includes("*")) {
702
- const re = new RegExp("^" + pattern.replace(/\*/g, ".*") + "$");
818
+ const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
819
+ const re = new RegExp("^" + escaped.replace(/\*/g, ".*") + "$");
703
820
  if (re.test(routePath)) return mode;
704
821
  }
705
822
  }
@@ -1001,6 +1118,7 @@ async function createServer(config = {}) {
1001
1118
  defaultLocale,
1002
1119
  port = Number(process.env.PORT) || 3e3,
1003
1120
  setup,
1121
+ proxies,
1004
1122
  ssr
1005
1123
  } = config;
1006
1124
  const root = rootOverride ?? process.cwd();
@@ -1037,6 +1155,9 @@ async function createServer(config = {}) {
1037
1155
  });
1038
1156
  }
1039
1157
  const app = new Hono2();
1158
+ if (proxies?.length) {
1159
+ registerProxyRoutes(app, proxies);
1160
+ }
1040
1161
  if (setup) {
1041
1162
  await setup(app);
1042
1163
  }
@@ -1076,7 +1197,8 @@ function matchRenderModeConfig(url, renderModes) {
1076
1197
  if (renderModes[path]) return renderModes[path];
1077
1198
  for (const [pattern, mode] of Object.entries(renderModes)) {
1078
1199
  if (pattern.includes("*")) {
1079
- const re = new RegExp("^" + pattern.replace(/\*/g, ".*") + "$");
1200
+ const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
1201
+ const re = new RegExp("^" + escaped.replace(/\*/g, ".*") + "$");
1080
1202
  if (re.test(path)) return mode;
1081
1203
  }
1082
1204
  }
@@ -1088,6 +1210,7 @@ function finesoftFrontViteConfig(options = {}) {
1088
1210
  let resolvedCommand;
1089
1211
  let resolvedResolve;
1090
1212
  let resolvedCss;
1213
+ const CSS_EXTENSIONS = /\.(css|scss|less|sass|styl|stylus|pcss|postcss)($|\?)/;
1091
1214
  return {
1092
1215
  name: "finesoft-front",
1093
1216
  config(userConfig, env) {
@@ -1107,6 +1230,82 @@ function finesoftFrontViteConfig(options = {}) {
1107
1230
  resolvedCss = config.css;
1108
1231
  root = config.root;
1109
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
+ },
1110
1309
  // ─── Dev ───────────────────────────────────────────────
1111
1310
  configureServer(server) {
1112
1311
  return async () => {
@@ -1114,12 +1313,15 @@ function finesoftFrontViteConfig(options = {}) {
1114
1313
  /* @vite-ignore */
1115
1314
  "hono"
1116
1315
  );
1117
- const { createSSRApp: createSSRApp2 } = await import("./app-IA6JQC7V.js");
1316
+ const { createSSRApp: createSSRApp2 } = await import("./app-OOLQDVXA.js");
1118
1317
  const { getRequestListener } = await import(
1119
1318
  /* @vite-ignore */
1120
1319
  "@hono/node-server"
1121
1320
  );
1122
1321
  const app = new HonoClass();
1322
+ if (options.proxies?.length) {
1323
+ registerProxyRoutes(app, options.proxies);
1324
+ }
1123
1325
  if (typeof options.setup === "function") {
1124
1326
  await options.setup(app);
1125
1327
  } else if (typeof options.setup === "string") {
@@ -1164,15 +1366,18 @@ function finesoftFrontViteConfig(options = {}) {
1164
1366
  );
1165
1367
  const { injectSSRContent: injectSSRContent2, injectCSRShell: injectCSRShell2 } = await import(
1166
1368
  /* @vite-ignore */
1167
- "./src-B5YAJXIM.js"
1369
+ "./src-MVACJWBF.js"
1168
1370
  );
1169
- const { parseAcceptLanguage: parseAcceptLanguage2 } = await import("./locale-YK3THSI6.js");
1371
+ const { parseAcceptLanguage: parseAcceptLanguage2 } = await import("./locale-CAZ4INCX.js");
1170
1372
  const { getRequestListener } = await import(
1171
1373
  /* @vite-ignore */
1172
1374
  "@hono/node-server"
1173
1375
  );
1174
1376
  const app = new HonoClass();
1175
1377
  const isrCache = /* @__PURE__ */ new Map();
1378
+ if (options.proxies?.length) {
1379
+ registerProxyRoutes(app, options.proxies);
1380
+ }
1176
1381
  if (typeof options.setup === "function") {
1177
1382
  await options.setup(app);
1178
1383
  } else if (typeof options.setup === "string") {
@@ -1327,6 +1532,7 @@ function finesoftFrontViteConfig(options = {}) {
1327
1532
  defaultLocale,
1328
1533
  templateHtml,
1329
1534
  renderModes: options.renderModes,
1535
+ proxies: options.proxies,
1330
1536
  resolvedResolve,
1331
1537
  resolvedCss,
1332
1538
  vite,
@@ -1383,6 +1589,7 @@ export {
1383
1589
  deserializeServerData,
1384
1590
  detectRuntime,
1385
1591
  finesoftFrontViteConfig,
1592
+ generateProxyCode,
1386
1593
  generateUuid,
1387
1594
  getBaseUrl,
1388
1595
  injectCSRShell,
@@ -1404,6 +1611,7 @@ export {
1404
1611
  registerActionHandlers,
1405
1612
  registerExternalUrlHandler,
1406
1613
  registerFlowActionHandler,
1614
+ registerProxyRoutes,
1407
1615
  removeHost,
1408
1616
  removeQueryParams,
1409
1617
  removeScheme,