@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.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
@@ -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,