@finesoft/front 0.1.28 → 0.1.29
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/{app-BPO26FAD.js → app-QERC5VUP.js} +3 -3
- package/dist/{chunk-5ZECBECP.js → chunk-N4AEOYUR.js} +14 -5
- package/dist/chunk-N4AEOYUR.js.map +1 -0
- package/dist/{chunk-IITKGRCO.js → chunk-WD72JAMS.js} +45 -6
- package/dist/chunk-WD72JAMS.js.map +1 -0
- package/dist/index.cjs +82 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -4
- package/dist/index.d.ts +19 -4
- package/dist/index.js +26 -7
- package/dist/index.js.map +1 -1
- package/dist/{src-KUWACLPD.js → src-LYKZSLF7.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-5ZECBECP.js.map +0 -1
- package/dist/chunk-IITKGRCO.js.map +0 -1
- /package/dist/{app-BPO26FAD.js.map → app-QERC5VUP.js.map} +0 -0
- /package/dist/{src-KUWACLPD.js.map → src-LYKZSLF7.js.map} +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createSSRApp
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
3
|
+
} from "./chunk-WD72JAMS.js";
|
|
4
|
+
import "./chunk-N4AEOYUR.js";
|
|
5
5
|
import "./chunk-BUYWNNNQ.js";
|
|
6
6
|
import "./chunk-FYP2ZYYV.js";
|
|
7
7
|
export {
|
|
8
8
|
createSSRApp
|
|
9
9
|
};
|
|
10
|
-
//# sourceMappingURL=app-
|
|
10
|
+
//# sourceMappingURL=app-QERC5VUP.js.map
|
|
@@ -4,8 +4,16 @@ import {
|
|
|
4
4
|
|
|
5
5
|
// ../ssr/src/render.ts
|
|
6
6
|
async function ssrRender(options) {
|
|
7
|
-
const {
|
|
8
|
-
|
|
7
|
+
const {
|
|
8
|
+
url,
|
|
9
|
+
frameworkConfig,
|
|
10
|
+
bootstrap,
|
|
11
|
+
getErrorPage,
|
|
12
|
+
renderApp,
|
|
13
|
+
ssrContext
|
|
14
|
+
} = options;
|
|
15
|
+
const mergedConfig = ssrContext?.fetch ? { ...frameworkConfig, fetch: ssrContext.fetch } : frameworkConfig;
|
|
16
|
+
const framework = Framework.create(mergedConfig);
|
|
9
17
|
bootstrap(framework);
|
|
10
18
|
const parsed = new URL(url, "http://localhost");
|
|
11
19
|
const fullPath = parsed.pathname + parsed.search;
|
|
@@ -46,12 +54,13 @@ async function ssrRender(options) {
|
|
|
46
54
|
// ../ssr/src/create-render.ts
|
|
47
55
|
function createSSRRender(config) {
|
|
48
56
|
const { bootstrap, getErrorPage, renderApp, frameworkConfig } = config;
|
|
49
|
-
return (url, locale) => ssrRender({
|
|
57
|
+
return (url, locale, ssrContext) => ssrRender({
|
|
50
58
|
url,
|
|
51
59
|
frameworkConfig: frameworkConfig ?? {},
|
|
52
60
|
bootstrap,
|
|
53
61
|
getErrorPage,
|
|
54
|
-
renderApp: (page) => renderApp(page, locale)
|
|
62
|
+
renderApp: (page) => renderApp(page, locale),
|
|
63
|
+
ssrContext
|
|
55
64
|
});
|
|
56
65
|
}
|
|
57
66
|
|
|
@@ -100,4 +109,4 @@ export {
|
|
|
100
109
|
injectCSRShell,
|
|
101
110
|
serializeServerData
|
|
102
111
|
};
|
|
103
|
-
//# sourceMappingURL=chunk-
|
|
112
|
+
//# sourceMappingURL=chunk-N4AEOYUR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../ssr/src/render.ts","../../ssr/src/create-render.ts","../../ssr/src/inject.ts","../../ssr/src/server-data.ts"],"sourcesContent":["/**\n * ssrRender — 通用 SSR 渲染管线\n *\n * 1. 创建 Framework + 注册 Controllers\n * 2. routeUrl → Intent\n * 3. dispatch → Page 数据\n * 4. 调用应用层提供的渲染函数\n */\n\nimport {\n\tFramework,\n\ttype BasePage,\n\ttype FrameworkConfig,\n\ttype PrefetchedIntent,\n} from \"@finesoft/core\";\n\nexport interface SSRRenderOptions {\n\t/** 请求 URL */\n\turl: string;\n\t/** Framework 配置(含路由注册等) */\n\tframeworkConfig: FrameworkConfig;\n\t/** 注册 controllers 和路由的引导函数 */\n\tbootstrap: (framework: Framework) => void;\n\t/** 获取错误页面 */\n\tgetErrorPage: (status: number, message: string) => BasePage;\n\t/** 应用层渲染函数(如 Svelte SSR render) */\n\trenderApp: (page: BasePage, framework: Framework) => SSRAppResult;\n\t/** 可选的 SSR 请求上下文(如自定义 fetch) */\n\tssrContext?: SSRContext;\n}\n\n/** SSR 请求级上下文 */\nexport interface SSRContext {\n\t/** 自定义 fetch(如 Hono 内部路由回环) */\n\tfetch?: typeof globalThis.fetch;\n}\n\nexport interface SSRAppResult {\n\thtml: string;\n\thead: string;\n\tcss: string;\n}\n\nexport interface SSRRenderResult {\n\thtml: string;\n\thead: string;\n\tcss: string;\n\tserverData: PrefetchedIntent[];\n\t/** 该路由的渲染模式(由 Router 返回) */\n\trenderMode?: string;\n}\n\nexport async function ssrRender(\n\toptions: SSRRenderOptions,\n): Promise<SSRRenderResult> {\n\tconst {\n\t\turl,\n\t\tframeworkConfig,\n\t\tbootstrap,\n\t\tgetErrorPage,\n\t\trenderApp,\n\t\tssrContext,\n\t} = options;\n\n\t// 将 SSR 上下文中的 fetch 合并到 frameworkConfig,注入 DI 容器\n\tconst mergedConfig: FrameworkConfig = ssrContext?.fetch\n\t\t? { ...frameworkConfig, fetch: ssrContext.fetch }\n\t\t: frameworkConfig;\n\n\tconst framework = Framework.create(mergedConfig);\n\tbootstrap(framework);\n\n\tconst parsed = new URL(url, \"http://localhost\");\n\tconst fullPath = parsed.pathname + parsed.search;\n\tconst match = framework.routeUrl(fullPath);\n\n\t// CSR 模式:跳过服务端渲染,返回空内容由客户端 JS 渲染\n\tif (match?.renderMode === \"csr\") {\n\t\tframework.dispose();\n\t\treturn {\n\t\t\thtml: \"\",\n\t\t\thead: \"\",\n\t\t\tcss: \"\",\n\t\t\tserverData: [],\n\t\t\trenderMode: \"csr\",\n\t\t};\n\t}\n\n\tlet page: BasePage;\n\tlet serverData: PrefetchedIntent[] = [];\n\n\tif (match) {\n\t\ttry {\n\t\t\tpage = (await framework.dispatch(match.intent)) as BasePage;\n\t\t\tserverData = [{ intent: match.intent, data: page }];\n\t\t} catch {\n\t\t\tpage = getErrorPage(500, \"Internal error\");\n\t\t}\n\t} else {\n\t\tpage = getErrorPage(404, \"Page not found\");\n\t}\n\n\tconst result = renderApp(page, framework);\n\n\tframework.dispose();\n\n\treturn {\n\t\thtml: result.html,\n\t\thead: result.head,\n\t\tcss: result.css,\n\t\tserverData,\n\t\trenderMode: match?.renderMode,\n\t};\n}\n","/**\n * createSSRRender — 工厂函数,返回可直接被 SSR 服务器调用的 render 函数\n *\n * 将一次性配置(bootstrap / getErrorPage / renderApp)绑定后,\n * 返回 `(url, locale) => Promise<SSRRenderResult>` 签名,\n * 与 @finesoft/server 的 SSRModule 接口对齐。\n */\n\nimport type { BasePage, Framework, FrameworkConfig } from \"@finesoft/core\";\nimport {\n\tssrRender,\n\ttype SSRAppResult,\n\ttype SSRContext,\n\ttype SSRRenderResult,\n} from \"./render\";\n\nexport interface SSRRenderConfig {\n\t/** 注册 controllers 和路由的引导函数 */\n\tbootstrap: (framework: Framework) => void;\n\n\t/** 获取错误页面 */\n\tgetErrorPage: (status: number, message: string) => BasePage;\n\n\t/**\n\t * 应用层渲染函数\n\t *\n\t * @param page - 当前页面数据\n\t * @param locale - 当前语言\n\t * @returns SSR 渲染结果 { html, head, css }\n\t */\n\trenderApp: (page: BasePage, locale: string) => SSRAppResult;\n\n\t/** Framework 构造配置(可选) */\n\tframeworkConfig?: FrameworkConfig;\n}\n\n/**\n * 创建 render 函数\n *\n * @returns `render(url, locale, ssrContext?)` — 供 @finesoft/server SSRModule 使用\n */\nexport function createSSRRender(\n\tconfig: SSRRenderConfig,\n): (\n\turl: string,\n\tlocale: string,\n\tssrContext?: SSRContext,\n) => Promise<SSRRenderResult> {\n\tconst { bootstrap, getErrorPage, renderApp, frameworkConfig } = config;\n\n\treturn (url: string, locale: string, ssrContext?: SSRContext) =>\n\t\tssrRender({\n\t\t\turl,\n\t\t\tframeworkConfig: frameworkConfig ?? {},\n\t\t\tbootstrap,\n\t\t\tgetErrorPage,\n\t\t\trenderApp: (page) => renderApp(page, locale),\n\t\t\tssrContext,\n\t\t});\n}\n","/**\n * injectSSRContent — 将 SSR 渲染结果注入 HTML 模板\n */\n\n/** SSR HTML 模板占位符常量 */\nexport const SSR_PLACEHOLDERS = {\n\tLANG: \"<!--ssr-lang-->\",\n\tHEAD: \"<!--ssr-head-->\",\n\tBODY: \"<!--ssr-body-->\",\n\tDATA: \"<!--ssr-data-->\",\n} as const;\n\nexport interface InjectSSROptions {\n\ttemplate: string;\n\tlocale: string;\n\thead: string;\n\tcss: string;\n\thtml: string;\n\tserializedData: string;\n}\n\nexport function injectSSRContent(options: InjectSSROptions): string {\n\tconst { template, locale, head, css, html, serializedData } = options;\n\tconst cssTag = css ? `<style>${css}</style>` : \"\";\n\n\treturn template\n\t\t.replace(SSR_PLACEHOLDERS.LANG, locale)\n\t\t.replace(SSR_PLACEHOLDERS.HEAD, `${head}\\n${cssTag}`)\n\t\t.replace(SSR_PLACEHOLDERS.BODY, html)\n\t\t.replace(\n\t\t\tSSR_PLACEHOLDERS.DATA,\n\t\t\t`<script id=\"serialized-server-data\" type=\"application/json\">${serializedData}</script>`,\n\t\t);\n}\n\n/**\n * CSR 空壳注入 — 只替换 lang,清空 body/head/data 占位符\n * 用于 renderMode === \"csr\" 的路由\n */\nexport function injectCSRShell(template: string, locale: string): string {\n\treturn template\n\t\t.replace(SSR_PLACEHOLDERS.LANG, locale)\n\t\t.replace(SSR_PLACEHOLDERS.HEAD, \"\")\n\t\t.replace(SSR_PLACEHOLDERS.BODY, \"\")\n\t\t.replace(SSR_PLACEHOLDERS.DATA, \"\");\n}\n","/**\n * serializeServerData — 将 PrefetchedIntents 数据序列化为安全的 JSON\n *\n * 返回值可安全嵌入 <script> 标签。\n */\n\nimport type { PrefetchedIntent } from \"@finesoft/core\";\n\nconst HTML_REPLACEMENTS: Record<string, string> = {\n\t\"<\": \"\\\\u003C\",\n\t\">\": \"\\\\u003E\",\n\t\"/\": \"\\\\u002F\",\n\t\"\\u2028\": \"\\\\u2028\",\n\t\"\\u2029\": \"\\\\u2029\",\n};\n\nconst HTML_ESCAPE_PATTERN = /[<>/\\u2028\\u2029]/g;\n\nexport function serializeServerData(data: PrefetchedIntent[]): string {\n\tconst json = JSON.stringify(data);\n\treturn json.replace(\n\t\tHTML_ESCAPE_PATTERN,\n\t\t(match) => HTML_REPLACEMENTS[match] ?? match,\n\t);\n}\n"],"mappings":";;;;;AAoDA,eAAsB,UACrB,SAC2B;AAC3B,QAAM;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD,IAAI;AAGJ,QAAM,eAAgC,YAAY,QAC/C,EAAE,GAAG,iBAAiB,OAAO,WAAW,MAAM,IAC9C;AAEH,QAAM,YAAY,UAAU,OAAO,YAAY;AAC/C,YAAU,SAAS;AAEnB,QAAM,SAAS,IAAI,IAAI,KAAK,kBAAkB;AAC9C,QAAM,WAAW,OAAO,WAAW,OAAO;AAC1C,QAAM,QAAQ,UAAU,SAAS,QAAQ;AAGzC,MAAI,OAAO,eAAe,OAAO;AAChC,cAAU,QAAQ;AAClB,WAAO;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,MACN,KAAK;AAAA,MACL,YAAY,CAAC;AAAA,MACb,YAAY;AAAA,IACb;AAAA,EACD;AAEA,MAAI;AACJ,MAAI,aAAiC,CAAC;AAEtC,MAAI,OAAO;AACV,QAAI;AACH,aAAQ,MAAM,UAAU,SAAS,MAAM,MAAM;AAC7C,mBAAa,CAAC,EAAE,QAAQ,MAAM,QAAQ,MAAM,KAAK,CAAC;AAAA,IACnD,QAAQ;AACP,aAAO,aAAa,KAAK,gBAAgB;AAAA,IAC1C;AAAA,EACD,OAAO;AACN,WAAO,aAAa,KAAK,gBAAgB;AAAA,EAC1C;AAEA,QAAM,SAAS,UAAU,MAAM,SAAS;AAExC,YAAU,QAAQ;AAElB,SAAO;AAAA,IACN,MAAM,OAAO;AAAA,IACb,MAAM,OAAO;AAAA,IACb,KAAK,OAAO;AAAA,IACZ;AAAA,IACA,YAAY,OAAO;AAAA,EACpB;AACD;;;ACxEO,SAAS,gBACf,QAK6B;AAC7B,QAAM,EAAE,WAAW,cAAc,WAAW,gBAAgB,IAAI;AAEhE,SAAO,CAAC,KAAa,QAAgB,eACpC,UAAU;AAAA,IACT;AAAA,IACA,iBAAiB,mBAAmB,CAAC;AAAA,IACrC;AAAA,IACA;AAAA,IACA,WAAW,CAAC,SAAS,UAAU,MAAM,MAAM;AAAA,IAC3C;AAAA,EACD,CAAC;AACH;;;ACtDO,IAAM,mBAAmB;AAAA,EAC/B,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AACP;AAWO,SAAS,iBAAiB,SAAmC;AACnE,QAAM,EAAE,UAAU,QAAQ,MAAM,KAAK,MAAM,eAAe,IAAI;AAC9D,QAAM,SAAS,MAAM,UAAU,GAAG,aAAa;AAE/C,SAAO,SACL,QAAQ,iBAAiB,MAAM,MAAM,EACrC,QAAQ,iBAAiB,MAAM,GAAG,IAAI;AAAA,EAAK,MAAM,EAAE,EACnD,QAAQ,iBAAiB,MAAM,IAAI,EACnC;AAAA,IACA,iBAAiB;AAAA,IACjB,+DAA+D,cAAc;AAAA,EAC9E;AACF;AAMO,SAAS,eAAe,UAAkB,QAAwB;AACxE,SAAO,SACL,QAAQ,iBAAiB,MAAM,MAAM,EACrC,QAAQ,iBAAiB,MAAM,EAAE,EACjC,QAAQ,iBAAiB,MAAM,EAAE,EACjC,QAAQ,iBAAiB,MAAM,EAAE;AACpC;;;ACrCA,IAAM,oBAA4C;AAAA,EACjD,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,UAAU;AAAA,EACV,UAAU;AACX;AAEA,IAAM,sBAAsB;AAErB,SAAS,oBAAoB,MAAkC;AACrE,QAAM,OAAO,KAAK,UAAU,IAAI;AAChC,SAAO,KAAK;AAAA,IACX;AAAA,IACA,CAAC,UAAU,kBAAkB,KAAK,KAAK;AAAA,EACxC;AACD;","names":[]}
|
|
@@ -1,13 +1,39 @@
|
|
|
1
1
|
import {
|
|
2
2
|
injectCSRShell,
|
|
3
3
|
injectSSRContent
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-N4AEOYUR.js";
|
|
5
5
|
import {
|
|
6
6
|
parseAcceptLanguage
|
|
7
7
|
} from "./chunk-FYP2ZYYV.js";
|
|
8
8
|
|
|
9
9
|
// ../server/src/app.ts
|
|
10
10
|
import { Hono } from "hono";
|
|
11
|
+
|
|
12
|
+
// ../server/src/internal-fetch.ts
|
|
13
|
+
var MAX_INTERNAL_FETCH_DEPTH = 5;
|
|
14
|
+
function createInternalFetch(appFetch) {
|
|
15
|
+
let depth = 0;
|
|
16
|
+
return ((input, init) => {
|
|
17
|
+
if (typeof input === "string" && input.startsWith("/")) {
|
|
18
|
+
if (depth >= MAX_INTERNAL_FETCH_DEPTH) {
|
|
19
|
+
return Promise.resolve(
|
|
20
|
+
new Response("Internal fetch loop detected", {
|
|
21
|
+
status: 508
|
|
22
|
+
})
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
depth++;
|
|
26
|
+
return appFetch(
|
|
27
|
+
new Request(`http://localhost${input}`, init)
|
|
28
|
+
).finally(() => {
|
|
29
|
+
depth--;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
return globalThis.fetch(input, init);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ../server/src/app.ts
|
|
11
37
|
function createSSRApp(options) {
|
|
12
38
|
const {
|
|
13
39
|
root,
|
|
@@ -16,9 +42,11 @@ function createSSRApp(options) {
|
|
|
16
42
|
ssrEntryPath = "/src/ssr.ts",
|
|
17
43
|
ssrProductionModule,
|
|
18
44
|
supportedLocales,
|
|
19
|
-
defaultLocale
|
|
45
|
+
defaultLocale,
|
|
46
|
+
parentFetch
|
|
20
47
|
} = options;
|
|
21
48
|
const app = new Hono();
|
|
49
|
+
const internalFetch = parentFetch ? createInternalFetch(parentFetch) : void 0;
|
|
22
50
|
const ISR_CACHE_MAX = 1e3;
|
|
23
51
|
const isrCache = /* @__PURE__ */ new Map();
|
|
24
52
|
function isrSet(key, val) {
|
|
@@ -62,7 +90,10 @@ function createSSRApp(options) {
|
|
|
62
90
|
return await vite.ssrLoadModule(ssrEntryPath);
|
|
63
91
|
}
|
|
64
92
|
if (ssrProductionModule) {
|
|
65
|
-
return import(
|
|
93
|
+
return import(
|
|
94
|
+
/* @vite-ignore */
|
|
95
|
+
ssrProductionModule
|
|
96
|
+
);
|
|
66
97
|
}
|
|
67
98
|
const { resolve } = await import(
|
|
68
99
|
/* @vite-ignore */
|
|
@@ -73,7 +104,10 @@ function createSSRApp(options) {
|
|
|
73
104
|
"url"
|
|
74
105
|
);
|
|
75
106
|
const absPath = pathToFileURL(resolve(root, "dist/server/ssr.js")).href;
|
|
76
|
-
return import(
|
|
107
|
+
return import(
|
|
108
|
+
/* @vite-ignore */
|
|
109
|
+
absPath
|
|
110
|
+
);
|
|
77
111
|
}
|
|
78
112
|
app.get("*", async (c) => {
|
|
79
113
|
const url = c.req.path + (c.req.url.includes("?") ? "?" + c.req.url.split("?")[1] : "");
|
|
@@ -94,7 +128,11 @@ function createSSRApp(options) {
|
|
|
94
128
|
css,
|
|
95
129
|
serverData,
|
|
96
130
|
renderMode
|
|
97
|
-
} = await render(
|
|
131
|
+
} = await render(
|
|
132
|
+
url,
|
|
133
|
+
locale,
|
|
134
|
+
internalFetch ? { fetch: internalFetch } : void 0
|
|
135
|
+
);
|
|
98
136
|
if (renderMode === "csr") {
|
|
99
137
|
return c.html(injectCSRShell(template, locale));
|
|
100
138
|
}
|
|
@@ -123,6 +161,7 @@ function createSSRApp(options) {
|
|
|
123
161
|
}
|
|
124
162
|
|
|
125
163
|
export {
|
|
164
|
+
createInternalFetch,
|
|
126
165
|
createSSRApp
|
|
127
166
|
};
|
|
128
|
-
//# sourceMappingURL=chunk-
|
|
167
|
+
//# sourceMappingURL=chunk-WD72JAMS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../server/src/app.ts","../../server/src/internal-fetch.ts"],"sourcesContent":["/**\n * createSSRApp — 创建 Hono SSR 应用\n *\n * 提供 SSR 通配路由,读取模板、加载 SSR 模块、渲染。\n * 应用层可在此之上追加自定义路由(API 代理等)。\n */\n\nimport { injectCSRShell, injectSSRContent } from \"@finesoft/ssr\";\nimport { Hono } from \"hono\";\nimport type { ViteDevServer } from \"vite\";\nimport { createInternalFetch } from \"./internal-fetch\";\nimport { parseAcceptLanguage } from \"./locale\";\n\nexport interface SSRModule {\n\trender: (\n\t\turl: string,\n\t\tlocale: string,\n\t\tssrContext?: { fetch?: typeof globalThis.fetch },\n\t) => Promise<{\n\t\thtml: string;\n\t\thead: string;\n\t\tcss: string;\n\t\tserverData: unknown;\n\t\trenderMode?: string;\n\t}>;\n\tserializeServerData: (data: unknown) => string;\n}\n\nexport interface SSRAppOptions {\n\t/** 项目根路径 */\n\troot: string;\n\t/** Vite dev server(仅开发模式) */\n\tvite?: ViteDevServer;\n\t/** 是否生产环境 */\n\tisProduction: boolean;\n\t/** SSR 入口文件路径(开发用,如 \"/src/ssr.ts\") */\n\tssrEntryPath?: string;\n\t/** 生产环境 SSR 模块路径(如 \"../dist/server/ssr.js\") */\n\tssrProductionModule?: string;\n\t/** 支持的语言列表 */\n\tsupportedLocales?: string[];\n\t/** 默认语言 */\n\tdefaultLocale?: string;\n\t/**\n\t * 父级 Hono app 的 fetch 函数,用于 SSR 内部路由回环。\n\t * SSR 渲染时,控制器的 fetch 请求(如 /api/apple/*)会通过此函数\n\t * 直接在进程内路由到代理 handler,避免网络自请求死锁。\n\t */\n\tparentFetch?: (request: Request) => Promise<Response>;\n}\n\nexport function createSSRApp(options: SSRAppOptions): Hono {\n\tconst {\n\t\troot,\n\t\tvite,\n\t\tisProduction,\n\t\tssrEntryPath = \"/src/ssr.ts\",\n\t\tssrProductionModule,\n\t\tsupportedLocales,\n\t\tdefaultLocale,\n\t\tparentFetch,\n\t} = options;\n\n\tconst app = new Hono();\n\n\tconst internalFetch = parentFetch\n\t\t? createInternalFetch(parentFetch)\n\t\t: undefined;\n\n\t/** ISR 内存缓存(prerender 路由首次请求后缓存,LRU 驱逐) */\n\tconst ISR_CACHE_MAX = 1000;\n\tconst isrCache = new Map<string, string>();\n\tfunction isrSet(key: string, val: string) {\n\t\tif (isrCache.size >= ISR_CACHE_MAX) {\n\t\t\tconst first = isrCache.keys().next().value;\n\t\t\tif (first !== undefined) isrCache.delete(first);\n\t\t}\n\t\tisrCache.set(key, val);\n\t}\n\n\tasync function readTemplate(url: string): Promise<string> {\n\t\tif (!isProduction && vite) {\n\t\t\tconst { readFileSync } = await import(/* @vite-ignore */ \"node:fs\");\n\t\t\tconst { resolve } = await import(/* @vite-ignore */ \"node:path\");\n\t\t\tconst raw = readFileSync(resolve(root, \"index.html\"), \"utf-8\");\n\t\t\treturn vite.transformIndexHtml(url, raw);\n\t\t}\n\n\t\tconst isDeno = typeof (globalThis as any).Deno !== \"undefined\";\n\t\tif (isDeno) {\n\t\t\treturn (globalThis as any).Deno.readTextFileSync(\n\t\t\t\tnew URL(\"../dist/client/index.html\", import.meta.url),\n\t\t\t);\n\t\t}\n\n\t\tconst { readFileSync } = await import(/* @vite-ignore */ \"node:fs\");\n\t\tconst { resolve } = await import(/* @vite-ignore */ \"node:path\");\n\t\treturn readFileSync(resolve(root, \"dist/client/index.html\"), \"utf-8\");\n\t}\n\n\tasync function loadSSRModule(): Promise<SSRModule> {\n\t\tif (!isProduction && vite) {\n\t\t\treturn (await vite.ssrLoadModule(ssrEntryPath)) as SSRModule;\n\t\t}\n\t\tif (ssrProductionModule) {\n\t\t\treturn import(\n\t\t\t\t/* @vite-ignore */ ssrProductionModule\n\t\t\t) as Promise<SSRModule>;\n\t\t}\n\t\tconst { resolve } = await import(/* @vite-ignore */ \"node:path\");\n\t\tconst { pathToFileURL } = await import(/* @vite-ignore */ \"node:url\");\n\t\tconst absPath = pathToFileURL(resolve(root, \"dist/server/ssr.js\")).href;\n\t\treturn import(/* @vite-ignore */ absPath) as Promise<SSRModule>;\n\t}\n\n\tapp.get(\"*\", async (c) => {\n\t\tconst url =\n\t\t\tc.req.path +\n\t\t\t(c.req.url.includes(\"?\") ? \"?\" + c.req.url.split(\"?\")[1] : \"\");\n\n\t\ttry {\n\t\t\t// ISR 缓存命中\n\t\t\tconst cacheKey = url;\n\t\t\tconst cached = isrCache.get(cacheKey);\n\t\t\tif (cached) return c.html(cached);\n\n\t\t\tconst template = await readTemplate(url);\n\t\t\tconst { render, serializeServerData } = await loadSSRModule();\n\n\t\t\tconst locale = parseAcceptLanguage(\n\t\t\t\tc.req.header(\"accept-language\"),\n\t\t\t\tsupportedLocales,\n\t\t\t\tdefaultLocale,\n\t\t\t);\n\n\t\t\tconst {\n\t\t\t\thtml: appHtml,\n\t\t\t\thead,\n\t\t\t\tcss,\n\t\t\t\tserverData,\n\t\t\t\trenderMode,\n\t\t\t} = await render(\n\t\t\t\turl,\n\t\t\t\tlocale,\n\t\t\t\tinternalFetch ? { fetch: internalFetch } : undefined,\n\t\t\t);\n\n\t\t\t// CSR 模式:返回空壳 HTML\n\t\t\tif (renderMode === \"csr\") {\n\t\t\t\treturn c.html(injectCSRShell(template, locale));\n\t\t\t}\n\n\t\t\tconst serializedData = serializeServerData(serverData);\n\n\t\t\tconst finalHtml = injectSSRContent({\n\t\t\t\ttemplate,\n\t\t\t\tlocale,\n\t\t\t\thead,\n\t\t\t\tcss,\n\t\t\t\thtml: appHtml,\n\t\t\t\tserializedData,\n\t\t\t});\n\n\t\t\t// Prerender ISR 缓存\n\t\t\tif (renderMode === \"prerender\") {\n\t\t\t\tisrSet(cacheKey, finalHtml);\n\t\t\t}\n\n\t\t\treturn c.html(finalHtml);\n\t\t} catch (e) {\n\t\t\tif (!isProduction && vite) {\n\t\t\t\tvite.ssrFixStacktrace(e as Error);\n\t\t\t}\n\t\t\tconsole.error(\"[SSR Error]\", e);\n\t\t\treturn c.text(\"Internal Server Error\", 500);\n\t\t}\n\t});\n\n\treturn app;\n}\n","/**\n * createInternalFetch — SSR 内部路由回环 fetch 包装器\n *\n * 将相对路径(/api/…)请求转为 Hono app.fetch 内存调用,\n * 绝对 URL 和非字符串 input 走 globalThis.fetch(真实网络)。\n *\n * 内置递归深度保护:防止控制器误请求非 API 路径触发 catch-all SSR\n * 导致无限递归。\n */\n\nconst MAX_INTERNAL_FETCH_DEPTH = 5;\n\nexport function createInternalFetch(\n\tappFetch: (request: Request) => Promise<Response>,\n): typeof globalThis.fetch {\n\tlet depth = 0;\n\n\treturn ((input: RequestInfo | URL, init?: RequestInit) => {\n\t\t// 只拦截相对路径(以 / 开头),其他一律走真实网络\n\t\tif (typeof input === \"string\" && input.startsWith(\"/\")) {\n\t\t\tif (depth >= MAX_INTERNAL_FETCH_DEPTH) {\n\t\t\t\treturn Promise.resolve(\n\t\t\t\t\tnew Response(\"Internal fetch loop detected\", {\n\t\t\t\t\t\tstatus: 508,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\t\t\tdepth++;\n\t\t\treturn appFetch(\n\t\t\t\tnew Request(`http://localhost${input}`, init),\n\t\t\t).finally(() => {\n\t\t\t\tdepth--;\n\t\t\t});\n\t\t}\n\n\t\t// 绝对 URL / URL 对象 / Request 对象 → 走正常网络\n\t\treturn globalThis.fetch(input, init);\n\t}) as typeof globalThis.fetch;\n}\n"],"mappings":"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","names":["readFileSync","resolve"]}
|
package/dist/index.cjs
CHANGED
|
@@ -969,8 +969,16 @@ var init_src = __esm({
|
|
|
969
969
|
|
|
970
970
|
// ../ssr/src/render.ts
|
|
971
971
|
async function ssrRender(options) {
|
|
972
|
-
const {
|
|
973
|
-
|
|
972
|
+
const {
|
|
973
|
+
url,
|
|
974
|
+
frameworkConfig,
|
|
975
|
+
bootstrap,
|
|
976
|
+
getErrorPage,
|
|
977
|
+
renderApp,
|
|
978
|
+
ssrContext
|
|
979
|
+
} = options;
|
|
980
|
+
const mergedConfig = ssrContext?.fetch ? { ...frameworkConfig, fetch: ssrContext.fetch } : frameworkConfig;
|
|
981
|
+
const framework = Framework.create(mergedConfig);
|
|
974
982
|
bootstrap(framework);
|
|
975
983
|
const parsed = new URL(url, "http://localhost");
|
|
976
984
|
const fullPath = parsed.pathname + parsed.search;
|
|
@@ -1017,12 +1025,13 @@ var init_render = __esm({
|
|
|
1017
1025
|
// ../ssr/src/create-render.ts
|
|
1018
1026
|
function createSSRRender(config) {
|
|
1019
1027
|
const { bootstrap, getErrorPage, renderApp, frameworkConfig } = config;
|
|
1020
|
-
return (url, locale) => ssrRender({
|
|
1028
|
+
return (url, locale, ssrContext) => ssrRender({
|
|
1021
1029
|
url,
|
|
1022
1030
|
frameworkConfig: frameworkConfig ?? {},
|
|
1023
1031
|
bootstrap,
|
|
1024
1032
|
getErrorPage,
|
|
1025
|
-
renderApp: (page) => renderApp(page, locale)
|
|
1033
|
+
renderApp: (page) => renderApp(page, locale),
|
|
1034
|
+
ssrContext
|
|
1026
1035
|
});
|
|
1027
1036
|
}
|
|
1028
1037
|
var init_create_render = __esm({
|
|
@@ -1103,6 +1112,36 @@ var init_src2 = __esm({
|
|
|
1103
1112
|
}
|
|
1104
1113
|
});
|
|
1105
1114
|
|
|
1115
|
+
// ../server/src/internal-fetch.ts
|
|
1116
|
+
function createInternalFetch(appFetch) {
|
|
1117
|
+
let depth = 0;
|
|
1118
|
+
return ((input, init) => {
|
|
1119
|
+
if (typeof input === "string" && input.startsWith("/")) {
|
|
1120
|
+
if (depth >= MAX_INTERNAL_FETCH_DEPTH) {
|
|
1121
|
+
return Promise.resolve(
|
|
1122
|
+
new Response("Internal fetch loop detected", {
|
|
1123
|
+
status: 508
|
|
1124
|
+
})
|
|
1125
|
+
);
|
|
1126
|
+
}
|
|
1127
|
+
depth++;
|
|
1128
|
+
return appFetch(
|
|
1129
|
+
new Request(`http://localhost${input}`, init)
|
|
1130
|
+
).finally(() => {
|
|
1131
|
+
depth--;
|
|
1132
|
+
});
|
|
1133
|
+
}
|
|
1134
|
+
return globalThis.fetch(input, init);
|
|
1135
|
+
});
|
|
1136
|
+
}
|
|
1137
|
+
var MAX_INTERNAL_FETCH_DEPTH;
|
|
1138
|
+
var init_internal_fetch = __esm({
|
|
1139
|
+
"../server/src/internal-fetch.ts"() {
|
|
1140
|
+
"use strict";
|
|
1141
|
+
MAX_INTERNAL_FETCH_DEPTH = 5;
|
|
1142
|
+
}
|
|
1143
|
+
});
|
|
1144
|
+
|
|
1106
1145
|
// ../server/src/locale.ts
|
|
1107
1146
|
var locale_exports = {};
|
|
1108
1147
|
__export(locale_exports, {
|
|
@@ -1146,9 +1185,11 @@ function createSSRApp(options) {
|
|
|
1146
1185
|
ssrEntryPath = "/src/ssr.ts",
|
|
1147
1186
|
ssrProductionModule,
|
|
1148
1187
|
supportedLocales,
|
|
1149
|
-
defaultLocale
|
|
1188
|
+
defaultLocale,
|
|
1189
|
+
parentFetch
|
|
1150
1190
|
} = options;
|
|
1151
1191
|
const app = new import_hono.Hono();
|
|
1192
|
+
const internalFetch = parentFetch ? createInternalFetch(parentFetch) : void 0;
|
|
1152
1193
|
const ISR_CACHE_MAX = 1e3;
|
|
1153
1194
|
const isrCache = /* @__PURE__ */ new Map();
|
|
1154
1195
|
function isrSet(key, val) {
|
|
@@ -1192,7 +1233,10 @@ function createSSRApp(options) {
|
|
|
1192
1233
|
return await vite.ssrLoadModule(ssrEntryPath);
|
|
1193
1234
|
}
|
|
1194
1235
|
if (ssrProductionModule) {
|
|
1195
|
-
return import(
|
|
1236
|
+
return import(
|
|
1237
|
+
/* @vite-ignore */
|
|
1238
|
+
ssrProductionModule
|
|
1239
|
+
);
|
|
1196
1240
|
}
|
|
1197
1241
|
const { resolve } = await import(
|
|
1198
1242
|
/* @vite-ignore */
|
|
@@ -1203,7 +1247,10 @@ function createSSRApp(options) {
|
|
|
1203
1247
|
"url"
|
|
1204
1248
|
);
|
|
1205
1249
|
const absPath = pathToFileURL(resolve(root, "dist/server/ssr.js")).href;
|
|
1206
|
-
return import(
|
|
1250
|
+
return import(
|
|
1251
|
+
/* @vite-ignore */
|
|
1252
|
+
absPath
|
|
1253
|
+
);
|
|
1207
1254
|
}
|
|
1208
1255
|
app.get("*", async (c) => {
|
|
1209
1256
|
const url = c.req.path + (c.req.url.includes("?") ? "?" + c.req.url.split("?")[1] : "");
|
|
@@ -1224,7 +1271,11 @@ function createSSRApp(options) {
|
|
|
1224
1271
|
css,
|
|
1225
1272
|
serverData,
|
|
1226
1273
|
renderMode
|
|
1227
|
-
} = await render(
|
|
1274
|
+
} = await render(
|
|
1275
|
+
url,
|
|
1276
|
+
locale,
|
|
1277
|
+
internalFetch ? { fetch: internalFetch } : void 0
|
|
1278
|
+
);
|
|
1228
1279
|
if (renderMode === "csr") {
|
|
1229
1280
|
return c.html(injectCSRShell(template, locale));
|
|
1230
1281
|
}
|
|
@@ -1257,6 +1308,7 @@ var init_app = __esm({
|
|
|
1257
1308
|
"use strict";
|
|
1258
1309
|
init_src2();
|
|
1259
1310
|
import_hono = require("hono");
|
|
1311
|
+
init_internal_fetch();
|
|
1260
1312
|
init_locale();
|
|
1261
1313
|
import_meta = {};
|
|
1262
1314
|
}
|
|
@@ -1753,6 +1805,20 @@ const app = new Hono();
|
|
|
1753
1805
|
${setupCall}
|
|
1754
1806
|
${opts.platformMiddleware ?? ""}
|
|
1755
1807
|
|
|
1808
|
+
// \u5185\u90E8 fetch \u56DE\u73AF\uFF1ASSR \u63A7\u5236\u5668\u7684 fetch \u8BF7\u6C42\u76F4\u63A5\u8D70 Hono \u5185\u5B58\u8DEF\u7531
|
|
1809
|
+
// \u53EA\u62E6\u622A\u76F8\u5BF9\u8DEF\u5F84\uFF08/api/* \u7B49\uFF09\uFF0C\u7EDD\u5BF9 URL \u8D70\u6B63\u5E38\u7F51\u7EDC\uFF1B\u542B\u9012\u5F52\u4FDD\u62A4
|
|
1810
|
+
let _fetchDepth = 0;
|
|
1811
|
+
function _internalFetch(input, init) {
|
|
1812
|
+
if (typeof input === "string" && input.startsWith("/")) {
|
|
1813
|
+
if (_fetchDepth >= 5) {
|
|
1814
|
+
return Promise.resolve(new Response("Internal fetch loop detected", { status: 508 }));
|
|
1815
|
+
}
|
|
1816
|
+
_fetchDepth++;
|
|
1817
|
+
return app.fetch(new Request("http://localhost" + input, init)).finally(() => { _fetchDepth--; });
|
|
1818
|
+
}
|
|
1819
|
+
return globalThis.fetch(input, init);
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1756
1822
|
app.get("*", async (c) => {
|
|
1757
1823
|
const url = c.req.path + (c.req.url.includes("?") ? "?" + c.req.url.split("?")[1] : "");
|
|
1758
1824
|
try {
|
|
@@ -1768,7 +1834,7 @@ app.get("*", async (c) => {
|
|
|
1768
1834
|
const cached = await platformCacheGet(url);
|
|
1769
1835
|
if (cached) return c.html(cached);
|
|
1770
1836
|
|
|
1771
|
-
const { html: appHtml, head, css, serverData, renderMode } = await render(url, locale);
|
|
1837
|
+
const { html: appHtml, head, css, serverData, renderMode } = await render(url, locale, { fetch: _internalFetch });
|
|
1772
1838
|
|
|
1773
1839
|
// \u8DEF\u7531\u7EA7 CSR
|
|
1774
1840
|
if (renderMode === "csr") {
|
|
@@ -2630,6 +2696,7 @@ async function createServer(config = {}) {
|
|
|
2630
2696
|
isProduction: runtime.isProduction,
|
|
2631
2697
|
supportedLocales: locales,
|
|
2632
2698
|
defaultLocale,
|
|
2699
|
+
parentFetch: app.fetch.bind(app),
|
|
2633
2700
|
...ssr
|
|
2634
2701
|
});
|
|
2635
2702
|
app.route("/", ssrApp);
|
|
@@ -2650,6 +2717,7 @@ async function createServer(config = {}) {
|
|
|
2650
2717
|
init_locale();
|
|
2651
2718
|
|
|
2652
2719
|
// ../server/src/vite-plugin.ts
|
|
2720
|
+
init_internal_fetch();
|
|
2653
2721
|
function resolveSetupFn(mod) {
|
|
2654
2722
|
if (typeof mod.default === "function") return mod.default;
|
|
2655
2723
|
if (typeof mod.setup === "function") return mod.setup;
|
|
@@ -2719,7 +2787,8 @@ function finesoftFrontViteConfig(options = {}) {
|
|
|
2719
2787
|
isProduction: false,
|
|
2720
2788
|
ssrEntryPath: "/" + ssrEntry,
|
|
2721
2789
|
supportedLocales: options.locales,
|
|
2722
|
-
defaultLocale: options.defaultLocale
|
|
2790
|
+
defaultLocale: options.defaultLocale,
|
|
2791
|
+
parentFetch: app.fetch.bind(app)
|
|
2723
2792
|
});
|
|
2724
2793
|
app.route("/", ssrApp);
|
|
2725
2794
|
const listener = getRequestListener(app.fetch);
|
|
@@ -2808,7 +2877,9 @@ function finesoftFrontViteConfig(options = {}) {
|
|
|
2808
2877
|
css,
|
|
2809
2878
|
serverData,
|
|
2810
2879
|
renderMode
|
|
2811
|
-
} = await ssrModule.render(url, locale
|
|
2880
|
+
} = await ssrModule.render(url, locale, {
|
|
2881
|
+
fetch: createInternalFetch(app.fetch.bind(app))
|
|
2882
|
+
});
|
|
2812
2883
|
if (renderMode === "csr") {
|
|
2813
2884
|
return c.html(injectCSRShell2(template, locale));
|
|
2814
2885
|
}
|