@finesoft/front 0.1.45 → 0.1.47
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/README.md +135 -0
- package/dist/index.d.mts +3 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +236 -109
- package/dist/index.mjs.map +1 -1
- package/dist/{locale-iP9ABD4H.mjs → locale-CvU-U6aP.mjs} +13 -2
- package/dist/{locale-iP9ABD4H.mjs.map → locale-CvU-U6aP.mjs.map} +1 -1
- package/package.json +1 -1
- package/dist/app-FO_8yAY7.mjs +0 -192
- package/dist/app-FO_8yAY7.mjs.map +0 -1
- package/dist/rolldown-runtime-wcPFST8Q.mjs +0 -13
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
//#region \0rolldown/runtime.js
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __exportAll = (all, no_symbols) => {
|
|
4
|
+
let target = {};
|
|
5
|
+
for (var name in all) __defProp(target, name, {
|
|
6
|
+
get: all[name],
|
|
7
|
+
enumerable: true
|
|
8
|
+
});
|
|
9
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
10
|
+
return target;
|
|
11
|
+
};
|
|
12
|
+
//#endregion
|
|
2
13
|
//#region ../server/src/locale.ts
|
|
3
14
|
var locale_exports = /* @__PURE__ */ __exportAll({ parseAcceptLanguage: () => parseAcceptLanguage });
|
|
4
15
|
/**
|
|
@@ -24,4 +35,4 @@ function parseAcceptLanguage(header, supported, fallback) {
|
|
|
24
35
|
//#endregion
|
|
25
36
|
export { parseAcceptLanguage as n, locale_exports as t };
|
|
26
37
|
|
|
27
|
-
//# sourceMappingURL=locale-
|
|
38
|
+
//# sourceMappingURL=locale-CvU-U6aP.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"locale-
|
|
1
|
+
{"version":3,"file":"locale-CvU-U6aP.mjs","names":[],"sources":["../../server/src/locale.ts"],"sourcesContent":["/**\n * Accept-Language 解析\n */\n\nexport function parseAcceptLanguage(\n header: string | undefined,\n supported?: string[],\n fallback?: string,\n): string {\n const effectiveSupported = supported ?? [\"zh\", \"en\"];\n const effectiveFallback = fallback ?? effectiveSupported[0] ?? \"en\";\n if (!header) return effectiveFallback;\n const langs = header\n .split(\",\")\n .map((part) => {\n const [lang, q] = part.trim().split(\";q=\");\n return {\n lang: lang.trim().toLowerCase(),\n q: q ? parseFloat(q) || 0 : 1,\n };\n })\n .sort((a, b) => b.q - a.q);\n\n for (const { lang } of langs) {\n const prefix = lang.split(\"-\")[0];\n if (effectiveSupported.includes(prefix)) {\n return prefix;\n }\n }\n return effectiveFallback;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAIA,SAAgB,oBACZ,QACA,WACA,UACM;CACN,MAAM,qBAAqB,aAAa,CAAC,MAAM,KAAK;CACpD,MAAM,oBAAoB,YAAY,mBAAmB,MAAM;AAC/D,KAAI,CAAC,OAAQ,QAAO;CACpB,MAAM,QAAQ,OACT,MAAM,IAAI,CACV,KAAK,SAAS;EACX,MAAM,CAAC,MAAM,KAAK,KAAK,MAAM,CAAC,MAAM,MAAM;AAC1C,SAAO;GACH,MAAM,KAAK,MAAM,CAAC,aAAa;GAC/B,GAAG,IAAI,WAAW,EAAE,IAAI,IAAI;GAC/B;GACH,CACD,MAAM,GAAG,MAAM,EAAE,IAAI,EAAE,EAAE;AAE9B,MAAK,MAAM,EAAE,UAAU,OAAO;EAC1B,MAAM,SAAS,KAAK,MAAM,IAAI,CAAC;AAC/B,MAAI,mBAAmB,SAAS,OAAO,CACnC,QAAO;;AAGf,QAAO"}
|
package/package.json
CHANGED
package/dist/app-FO_8yAY7.mjs
DELETED
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
import { t as __exportAll } from "./rolldown-runtime-wcPFST8Q.mjs";
|
|
2
|
-
import { n as parseAcceptLanguage } from "./locale-iP9ABD4H.mjs";
|
|
3
|
-
import { Hono } from "hono";
|
|
4
|
-
//#region ../ssr/src/inject.ts
|
|
5
|
-
/**
|
|
6
|
-
* injectSSRContent — 将 SSR 渲染结果注入 HTML 模板
|
|
7
|
-
*/
|
|
8
|
-
/** SSR HTML 模板占位符常量 */
|
|
9
|
-
const SSR_PLACEHOLDERS = {
|
|
10
|
-
LANG: "<!--ssr-lang-->",
|
|
11
|
-
HEAD: "<!--ssr-head-->",
|
|
12
|
-
BODY: "<!--ssr-body-->",
|
|
13
|
-
DATA: "<!--ssr-data-->"
|
|
14
|
-
};
|
|
15
|
-
function injectSSRContent(options) {
|
|
16
|
-
const { template, locale, head, css, html, serializedData } = options;
|
|
17
|
-
const cssTag = css ? `<style>${css}</style>` : "";
|
|
18
|
-
return template.replace(SSR_PLACEHOLDERS.LANG, locale).replace(SSR_PLACEHOLDERS.HEAD, `${head}\n${cssTag}`).replace(SSR_PLACEHOLDERS.BODY, html).replace(SSR_PLACEHOLDERS.DATA, `<script id="serialized-server-data" type="application/json">${serializedData}<\/script>`);
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* CSR 空壳注入 — 只替换 lang,清空 body/head/data 占位符
|
|
22
|
-
* 用于 renderMode === "csr" 的路由
|
|
23
|
-
*/
|
|
24
|
-
function injectCSRShell(template, locale) {
|
|
25
|
-
return template.replace(SSR_PLACEHOLDERS.LANG, locale).replace(SSR_PLACEHOLDERS.HEAD, "").replace(SSR_PLACEHOLDERS.BODY, "").replace(SSR_PLACEHOLDERS.DATA, "");
|
|
26
|
-
}
|
|
27
|
-
//#endregion
|
|
28
|
-
//#region ../server/src/dynamic-import.ts
|
|
29
|
-
/**
|
|
30
|
-
* Dynamic import wrapper that is opaque to Vite's import analysis.
|
|
31
|
-
*
|
|
32
|
-
* When tsdown bundles `@finesoft/server` into `@finesoft/front`,
|
|
33
|
-
* it inlines temporary variables and strips `@vite-ignore` comments
|
|
34
|
-
* from `import()` calls with computed specifiers. This produces
|
|
35
|
-
* "dynamic import cannot be analyzed" warnings in the dev console.
|
|
36
|
-
*
|
|
37
|
-
* `new Function` hides the `import()` call from all static analysis,
|
|
38
|
-
* including Vite's `vite:import-analysis` plugin. The specifier is
|
|
39
|
-
* always an absolute file:// URL constructed by the framework itself,
|
|
40
|
-
* so this is safe for server-side use.
|
|
41
|
-
*/
|
|
42
|
-
const dynamicImport = new Function("u", "return import(u)");
|
|
43
|
-
//#endregion
|
|
44
|
-
//#region ../server/src/internal-fetch.ts
|
|
45
|
-
/**
|
|
46
|
-
* createInternalFetch — SSR 内部路由回环 fetch 包装器
|
|
47
|
-
*
|
|
48
|
-
* 将相对路径(/api/…)请求转为 Hono app.fetch 内存调用,
|
|
49
|
-
* 绝对 URL 和非字符串 input 走 globalThis.fetch(真实网络)。
|
|
50
|
-
*
|
|
51
|
-
* 递归深度保护采用请求头传递:每次 SSR 回环在请求头中写入深度值,
|
|
52
|
-
* SSR catch-all 读取深度判断是否超限。对比闭包计数器方案:
|
|
53
|
-
* - 并发安全:无共享可变状态
|
|
54
|
-
* - 跨渲染准确:深度随请求在 Hono 路由链中传递
|
|
55
|
-
*/
|
|
56
|
-
const SSR_DEPTH_HEADER = "x-ssr-depth";
|
|
57
|
-
/**
|
|
58
|
-
* 创建请求级 internal fetch
|
|
59
|
-
*
|
|
60
|
-
* @param appFetch - Hono app.fetch(父级路由)
|
|
61
|
-
* @param depth - 当前 SSR 深度(由 catch-all handler 从请求头读取后 +1 传入)
|
|
62
|
-
*/
|
|
63
|
-
function createInternalFetch(appFetch, depth = 1) {
|
|
64
|
-
return ((input, init) => {
|
|
65
|
-
if (typeof input === "string" && input.startsWith("/")) {
|
|
66
|
-
const request = new Request(`http://localhost${input}`, init);
|
|
67
|
-
request.headers.set(SSR_DEPTH_HEADER, String(depth));
|
|
68
|
-
return Promise.resolve(appFetch(request));
|
|
69
|
-
}
|
|
70
|
-
return globalThis.fetch(input, init);
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
//#endregion
|
|
74
|
-
//#region ../server/src/app.ts
|
|
75
|
-
/**
|
|
76
|
-
* createSSRApp — 创建 Hono SSR 应用
|
|
77
|
-
*
|
|
78
|
-
* 提供 SSR 通配路由,读取模板、加载 SSR 模块、渲染。
|
|
79
|
-
* 应用层可在此之上追加自定义路由(API 代理等)。
|
|
80
|
-
*/
|
|
81
|
-
var app_exports = /* @__PURE__ */ __exportAll({ createSSRApp: () => createSSRApp });
|
|
82
|
-
/**
|
|
83
|
-
* 匹配 Vite 配置级别的 renderMode 覆盖。
|
|
84
|
-
* 精确路径优先,然后 glob 模式。
|
|
85
|
-
*/
|
|
86
|
-
function matchRenderModeOverride(url, renderModes) {
|
|
87
|
-
if (!renderModes) return null;
|
|
88
|
-
const path = url.split("?")[0];
|
|
89
|
-
if (renderModes[path]) return renderModes[path];
|
|
90
|
-
for (const [pattern, mode] of Object.entries(renderModes)) if (pattern.includes("*")) {
|
|
91
|
-
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
92
|
-
if (new RegExp("^" + escaped.replace(/\*/g, ".*") + "$").test(path)) return mode;
|
|
93
|
-
}
|
|
94
|
-
return null;
|
|
95
|
-
}
|
|
96
|
-
function createSSRApp(options) {
|
|
97
|
-
const { root, vite, isProduction, ssrEntryPath = "/src/ssr.ts", ssrProductionModule, supportedLocales, defaultLocale, parentFetch, renderModes } = options;
|
|
98
|
-
const app = new Hono();
|
|
99
|
-
/** ISR 内存缓存(prerender 路由首次请求后缓存,LRU 驱逐) */
|
|
100
|
-
const ISR_CACHE_MAX = 1e3;
|
|
101
|
-
const isrCache = /* @__PURE__ */ new Map();
|
|
102
|
-
function isrSet(key, val) {
|
|
103
|
-
if (isrCache.size >= ISR_CACHE_MAX) {
|
|
104
|
-
const first = isrCache.keys().next().value;
|
|
105
|
-
if (first !== void 0) isrCache.delete(first);
|
|
106
|
-
}
|
|
107
|
-
isrCache.set(key, val);
|
|
108
|
-
}
|
|
109
|
-
/** 生产环境模板缓存(模板不变,避免每请求重复读盘) */
|
|
110
|
-
let templateCache;
|
|
111
|
-
async function readTemplate(url) {
|
|
112
|
-
if (!isProduction && vite) {
|
|
113
|
-
const { readFileSync } = await import(
|
|
114
|
-
/* @vite-ignore */
|
|
115
|
-
"node:fs"
|
|
116
|
-
);
|
|
117
|
-
const raw = readFileSync((await import(
|
|
118
|
-
/* @vite-ignore */
|
|
119
|
-
"node:path"
|
|
120
|
-
)).resolve(root, "index.html"), "utf-8");
|
|
121
|
-
return vite.transformIndexHtml(url, raw);
|
|
122
|
-
}
|
|
123
|
-
if (templateCache) return templateCache;
|
|
124
|
-
if (typeof globalThis.Deno !== "undefined") {
|
|
125
|
-
templateCache = globalThis.Deno.readTextFileSync(new URL("../dist/client/index.html", import.meta.url));
|
|
126
|
-
return templateCache;
|
|
127
|
-
}
|
|
128
|
-
const { readFileSync } = await import(
|
|
129
|
-
/* @vite-ignore */
|
|
130
|
-
"node:fs"
|
|
131
|
-
);
|
|
132
|
-
templateCache = readFileSync((await import(
|
|
133
|
-
/* @vite-ignore */
|
|
134
|
-
"node:path"
|
|
135
|
-
)).resolve(root, "dist/client/index.html"), "utf-8");
|
|
136
|
-
return templateCache;
|
|
137
|
-
}
|
|
138
|
-
async function loadSSRModule() {
|
|
139
|
-
if (!isProduction && vite) return await vite.ssrLoadModule(ssrEntryPath);
|
|
140
|
-
if (ssrProductionModule) return dynamicImport(ssrProductionModule);
|
|
141
|
-
const path = await import(
|
|
142
|
-
/* @vite-ignore */
|
|
143
|
-
"node:path"
|
|
144
|
-
);
|
|
145
|
-
const { pathToFileURL } = await import(
|
|
146
|
-
/* @vite-ignore */
|
|
147
|
-
"node:url"
|
|
148
|
-
);
|
|
149
|
-
const absPath = pathToFileURL(path.resolve(root, "dist/server/ssr.js")).href;
|
|
150
|
-
return dynamicImport(absPath);
|
|
151
|
-
}
|
|
152
|
-
app.get("*", async (c) => {
|
|
153
|
-
const ssrDepth = parseInt(c.req.header("x-ssr-depth") ?? "0", 10);
|
|
154
|
-
if (ssrDepth >= 5) return c.text("SSR recursion loop detected", 508);
|
|
155
|
-
const url = c.req.path + (c.req.url.includes("?") ? "?" + c.req.url.split("?")[1] : "");
|
|
156
|
-
try {
|
|
157
|
-
const template = await readTemplate(url);
|
|
158
|
-
const { render, serializeServerData } = await loadSSRModule();
|
|
159
|
-
const locale = parseAcceptLanguage(c.req.header("accept-language"), supportedLocales, defaultLocale);
|
|
160
|
-
const overrideMode = matchRenderModeOverride(url, renderModes);
|
|
161
|
-
if (overrideMode === "csr") return c.html(injectCSRShell(template, locale));
|
|
162
|
-
const cacheKey = `${locale}:${url}`;
|
|
163
|
-
const cached = isrCache.get(cacheKey);
|
|
164
|
-
if (cached) return c.html(cached);
|
|
165
|
-
const requestFetch = parentFetch ? createInternalFetch(parentFetch, ssrDepth + 1) : void 0;
|
|
166
|
-
const ssrContext = { request: c.req.raw };
|
|
167
|
-
if (requestFetch) ssrContext.fetch = requestFetch;
|
|
168
|
-
const { html: appHtml, head, css, serverData, renderMode, redirect: middlewareRedirect } = await render(url, locale, ssrContext);
|
|
169
|
-
if (middlewareRedirect) return c.redirect(middlewareRedirect.url, middlewareRedirect.status);
|
|
170
|
-
if (renderMode === "csr") return c.html(injectCSRShell(template, locale));
|
|
171
|
-
const finalHtml = injectSSRContent({
|
|
172
|
-
template,
|
|
173
|
-
locale,
|
|
174
|
-
head,
|
|
175
|
-
css,
|
|
176
|
-
html: appHtml,
|
|
177
|
-
serializedData: serializeServerData(serverData)
|
|
178
|
-
});
|
|
179
|
-
if (renderMode === "prerender" || overrideMode === "prerender") isrSet(cacheKey, finalHtml);
|
|
180
|
-
return c.html(finalHtml);
|
|
181
|
-
} catch (e) {
|
|
182
|
-
if (!isProduction && vite) vite.ssrFixStacktrace(e);
|
|
183
|
-
console.error("[SSR Error]", e);
|
|
184
|
-
return c.text("Internal Server Error", 500);
|
|
185
|
-
}
|
|
186
|
-
});
|
|
187
|
-
return app;
|
|
188
|
-
}
|
|
189
|
-
//#endregion
|
|
190
|
-
export { dynamicImport as a, injectSSRContent as c, createInternalFetch as i, createSSRApp as n, SSR_PLACEHOLDERS as o, SSR_DEPTH_HEADER as r, injectCSRShell as s, app_exports as t };
|
|
191
|
-
|
|
192
|
-
//# sourceMappingURL=app-FO_8yAY7.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"app-FO_8yAY7.mjs","names":[],"sources":["../../ssr/src/inject.ts","../../server/src/dynamic-import.ts","../../server/src/internal-fetch.ts","../../server/src/app.ts"],"sourcesContent":["/**\n * injectSSRContent — 将 SSR 渲染结果注入 HTML 模板\n */\n\n/** SSR HTML 模板占位符常量 */\nexport const SSR_PLACEHOLDERS = {\n LANG: \"<!--ssr-lang-->\",\n HEAD: \"<!--ssr-head-->\",\n BODY: \"<!--ssr-body-->\",\n DATA: \"<!--ssr-data-->\",\n} as const;\n\nexport interface InjectSSROptions {\n template: string;\n locale: string;\n head: string;\n css: string;\n html: string;\n serializedData: string;\n}\n\nexport function injectSSRContent(options: InjectSSROptions): string {\n const { template, locale, head, css, html, serializedData } = options;\n const cssTag = css ? `<style>${css}</style>` : \"\";\n\n return template\n .replace(SSR_PLACEHOLDERS.LANG, locale)\n .replace(SSR_PLACEHOLDERS.HEAD, `${head}\\n${cssTag}`)\n .replace(SSR_PLACEHOLDERS.BODY, html)\n .replace(\n SSR_PLACEHOLDERS.DATA,\n `<script id=\"serialized-server-data\" type=\"application/json\">${serializedData}</script>`,\n );\n}\n\n/**\n * CSR 空壳注入 — 只替换 lang,清空 body/head/data 占位符\n * 用于 renderMode === \"csr\" 的路由\n */\nexport function injectCSRShell(template: string, locale: string): string {\n return template\n .replace(SSR_PLACEHOLDERS.LANG, locale)\n .replace(SSR_PLACEHOLDERS.HEAD, \"\")\n .replace(SSR_PLACEHOLDERS.BODY, \"\")\n .replace(SSR_PLACEHOLDERS.DATA, \"\");\n}\n","/**\n * Dynamic import wrapper that is opaque to Vite's import analysis.\n *\n * When tsdown bundles `@finesoft/server` into `@finesoft/front`,\n * it inlines temporary variables and strips `@vite-ignore` comments\n * from `import()` calls with computed specifiers. This produces\n * \"dynamic import cannot be analyzed\" warnings in the dev console.\n *\n * `new Function` hides the `import()` call from all static analysis,\n * including Vite's `vite:import-analysis` plugin. The specifier is\n * always an absolute file:// URL constructed by the framework itself,\n * so this is safe for server-side use.\n */\n// oxlint-disable-next-line no-implied-eval -- intentional: hides import() from Vite static analysis\nexport const dynamicImport = new Function(\"u\", \"return import(u)\") as (\n specifier: string,\n) => Promise<any>;\n","/**\n * createInternalFetch — SSR 内部路由回环 fetch 包装器\n *\n * 将相对路径(/api/…)请求转为 Hono app.fetch 内存调用,\n * 绝对 URL 和非字符串 input 走 globalThis.fetch(真实网络)。\n *\n * 递归深度保护采用请求头传递:每次 SSR 回环在请求头中写入深度值,\n * SSR catch-all 读取深度判断是否超限。对比闭包计数器方案:\n * - 并发安全:无共享可变状态\n * - 跨渲染准确:深度随请求在 Hono 路由链中传递\n */\n\nexport const SSR_DEPTH_HEADER = \"x-ssr-depth\";\nexport const MAX_SSR_DEPTH = 5;\n\n/**\n * 创建请求级 internal fetch\n *\n * @param appFetch - Hono app.fetch(父级路由)\n * @param depth - 当前 SSR 深度(由 catch-all handler 从请求头读取后 +1 传入)\n */\nexport function createInternalFetch(\n appFetch: (request: Request) => Response | Promise<Response>,\n depth: number = 1,\n): typeof globalThis.fetch {\n return ((input: RequestInfo | URL, init?: RequestInit) => {\n // 只拦截相对路径(以 / 开头),其他一律走真实网络\n if (typeof input === \"string\" && input.startsWith(\"/\")) {\n const request = new Request(`http://localhost${input}`, init);\n request.headers.set(SSR_DEPTH_HEADER, String(depth));\n return Promise.resolve(appFetch(request));\n }\n\n // 绝对 URL / URL 对象 / Request 对象 → 走正常网络\n return globalThis.fetch(input, init);\n }) as typeof globalThis.fetch;\n}\n","/**\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 { dynamicImport } from \"./dynamic-import\";\nimport { createInternalFetch, MAX_SSR_DEPTH, SSR_DEPTH_HEADER } from \"./internal-fetch\";\nimport { parseAcceptLanguage } from \"./locale\";\n\n/**\n * 匹配 Vite 配置级别的 renderMode 覆盖。\n * 精确路径优先,然后 glob 模式。\n */\nfunction matchRenderModeOverride(url: string, renderModes?: Record<string, string>): string | null {\n if (!renderModes) return null;\n const path = url.split(\"?\")[0];\n if (renderModes[path]) return renderModes[path];\n for (const [pattern, mode] of Object.entries(renderModes)) {\n if (pattern.includes(\"*\")) {\n const escaped = pattern.replace(/[.+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const re = new RegExp(\"^\" + escaped.replace(/\\*/g, \".*\") + \"$\");\n if (re.test(path)) return mode;\n }\n }\n return null;\n}\n\nexport interface SSRModule {\n render: (\n url: string,\n locale: string,\n ssrContext?: {\n fetch?: typeof globalThis.fetch;\n request?: Request;\n },\n ) => Promise<{\n html: string;\n head: string;\n css: string;\n serverData: unknown;\n renderMode?: string;\n redirect?: { url: string; status: number };\n }>;\n serializeServerData: (data: unknown) => string;\n}\n\nexport interface SSRAppOptions {\n /** 项目根路径 */\n root: string;\n /** Vite dev server(仅开发模式) */\n vite?: ViteDevServer;\n /** 是否生产环境 */\n isProduction: boolean;\n /** SSR 入口文件路径(开发用,如 \"/src/ssr.ts\") */\n ssrEntryPath?: string;\n /** 生产环境 SSR 模块路径(如 \"../dist/server/ssr.js\") */\n ssrProductionModule?: string;\n /** 支持的语言列表 */\n supportedLocales?: string[];\n /** 默认语言 */\n defaultLocale?: string;\n /**\n * 父级 Hono app 的 fetch 函数,用于 SSR 内部路由回环。\n * SSR 渲染时,控制器的 fetch 请求(如 /api/apple/*)会通过此函数\n * 直接在进程内路由到代理 handler,避免网络自请求死锁。\n */\n parentFetch?: (request: Request) => Response | Promise<Response>;\n /**\n * 按路由覆盖渲染模式(精确路径或 glob 模式)。\n * 优先级高于路由级 renderMode。\n */\n renderModes?: Record<string, string>;\n}\n\nexport function createSSRApp(options: SSRAppOptions): Hono {\n const {\n root,\n vite,\n isProduction,\n ssrEntryPath = \"/src/ssr.ts\",\n ssrProductionModule,\n supportedLocales,\n defaultLocale,\n parentFetch,\n renderModes,\n } = options;\n\n const app = new Hono();\n\n /** ISR 内存缓存(prerender 路由首次请求后缓存,LRU 驱逐) */\n const ISR_CACHE_MAX = 1000;\n const isrCache = new Map<string, string>();\n function isrSet(key: string, val: string) {\n if (isrCache.size >= ISR_CACHE_MAX) {\n const first = isrCache.keys().next().value;\n if (first !== undefined) isrCache.delete(first);\n }\n isrCache.set(key, val);\n }\n\n /** 生产环境模板缓存(模板不变,避免每请求重复读盘) */\n let templateCache: string | undefined;\n\n async function readTemplate(url: string): Promise<string> {\n if (!isProduction && vite) {\n const { readFileSync } = await import(/* @vite-ignore */ \"node:fs\");\n const path = await import(/* @vite-ignore */ \"node:path\");\n const raw = readFileSync(path.resolve(root, \"index.html\"), \"utf-8\");\n return vite.transformIndexHtml(url, raw);\n }\n\n if (templateCache) return templateCache;\n\n const isDeno = typeof (globalThis as any).Deno !== \"undefined\";\n if (isDeno) {\n templateCache = (globalThis as any).Deno.readTextFileSync(\n new URL(\"../dist/client/index.html\", import.meta.url),\n );\n return templateCache!;\n }\n\n const { readFileSync } = await import(/* @vite-ignore */ \"node:fs\");\n const path = await import(/* @vite-ignore */ \"node:path\");\n templateCache = readFileSync(path.resolve(root, \"dist/client/index.html\"), \"utf-8\");\n return templateCache!;\n }\n\n async function loadSSRModule(): Promise<SSRModule> {\n if (!isProduction && vite) {\n return (await vite.ssrLoadModule(ssrEntryPath)) as SSRModule;\n }\n if (ssrProductionModule) {\n return dynamicImport(ssrProductionModule) as Promise<SSRModule>;\n }\n const path = await import(/* @vite-ignore */ \"node:path\");\n const { pathToFileURL } = await import(/* @vite-ignore */ \"node:url\");\n const absPath = pathToFileURL(path.resolve(root, \"dist/server/ssr.js\")).href;\n return dynamicImport(absPath) as Promise<SSRModule>;\n }\n\n app.get(\"*\", async (c) => {\n // 递归深度保护:从请求头读取 SSR 深度\n const ssrDepth = parseInt(c.req.header(SSR_DEPTH_HEADER) ?? \"0\", 10);\n if (ssrDepth >= MAX_SSR_DEPTH) {\n return c.text(\"SSR recursion loop detected\", 508);\n }\n\n const url = c.req.path + (c.req.url.includes(\"?\") ? \"?\" + c.req.url.split(\"?\")[1] : \"\");\n\n try {\n const template = await readTemplate(url);\n const { render, serializeServerData } = await loadSSRModule();\n\n const locale = parseAcceptLanguage(\n c.req.header(\"accept-language\"),\n supportedLocales,\n defaultLocale,\n );\n\n // Vite 配置级别覆盖:CSR 直接返回空壳\n const overrideMode = matchRenderModeOverride(url, renderModes);\n if (overrideMode === \"csr\") {\n return c.html(injectCSRShell(template, locale));\n }\n\n // ISR 缓存命中(key 含 locale,避免跨语言缓存污染)\n const cacheKey = `${locale}:${url}`;\n const cached = isrCache.get(cacheKey);\n if (cached) return c.html(cached);\n\n // 每请求创建 internalFetch,深度通过请求头传递\n const requestFetch = parentFetch\n ? createInternalFetch(parentFetch, ssrDepth + 1)\n : undefined;\n\n const ssrContext: {\n fetch?: typeof globalThis.fetch;\n request?: Request;\n } = { request: c.req.raw };\n if (requestFetch) ssrContext.fetch = requestFetch;\n\n const {\n html: appHtml,\n head,\n css,\n serverData,\n renderMode,\n redirect: middlewareRedirect,\n } = await render(url, locale, ssrContext);\n\n // 中间件要求重定向\n if (middlewareRedirect) {\n return c.redirect(middlewareRedirect.url, middlewareRedirect.status as 301 | 302);\n }\n\n // CSR 模式:返回空壳 HTML\n if (renderMode === \"csr\") {\n return c.html(injectCSRShell(template, locale));\n }\n\n const serializedData = serializeServerData(serverData);\n\n const finalHtml = injectSSRContent({\n template,\n locale,\n head,\n css,\n html: appHtml,\n serializedData,\n });\n\n // Prerender ISR 缓存(包括 Vite 配置覆盖和路由级)\n if (renderMode === \"prerender\" || overrideMode === \"prerender\") {\n isrSet(cacheKey, finalHtml);\n }\n\n return c.html(finalHtml);\n } catch (e) {\n if (!isProduction && vite) {\n vite.ssrFixStacktrace(e as Error);\n }\n console.error(\"[SSR Error]\", e);\n return c.text(\"Internal Server Error\", 500);\n }\n });\n\n return app;\n}\n"],"mappings":"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"}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
//#region \0rolldown/runtime.js
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __exportAll = (all, no_symbols) => {
|
|
4
|
-
let target = {};
|
|
5
|
-
for (var name in all) __defProp(target, name, {
|
|
6
|
-
get: all[name],
|
|
7
|
-
enumerable: true
|
|
8
|
-
});
|
|
9
|
-
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
10
|
-
return target;
|
|
11
|
-
};
|
|
12
|
-
//#endregion
|
|
13
|
-
export { __exportAll as t };
|