@finesoft/front 0.1.20 → 0.1.21
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-Z3EFLAP2.js → app-BPO26FAD.js} +2 -2
- package/dist/{chunk-SN3OO3DU.js → chunk-IITKGRCO.js} +10 -2
- package/dist/chunk-IITKGRCO.js.map +1 -0
- package/dist/index.cjs +121 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +114 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-SN3OO3DU.js.map +0 -1
- /package/dist/{app-Z3EFLAP2.js.map → app-BPO26FAD.js.map} +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createSSRApp
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-IITKGRCO.js";
|
|
4
4
|
import "./chunk-5ZECBECP.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-BPO26FAD.js.map
|
|
@@ -19,7 +19,15 @@ function createSSRApp(options) {
|
|
|
19
19
|
defaultLocale
|
|
20
20
|
} = options;
|
|
21
21
|
const app = new Hono();
|
|
22
|
+
const ISR_CACHE_MAX = 1e3;
|
|
22
23
|
const isrCache = /* @__PURE__ */ new Map();
|
|
24
|
+
function isrSet(key, val) {
|
|
25
|
+
if (isrCache.size >= ISR_CACHE_MAX) {
|
|
26
|
+
const first = isrCache.keys().next().value;
|
|
27
|
+
if (first !== void 0) isrCache.delete(first);
|
|
28
|
+
}
|
|
29
|
+
isrCache.set(key, val);
|
|
30
|
+
}
|
|
23
31
|
async function readTemplate(url) {
|
|
24
32
|
if (!isProduction && vite) {
|
|
25
33
|
const { readFileSync: readFileSync2 } = await import(
|
|
@@ -100,7 +108,7 @@ function createSSRApp(options) {
|
|
|
100
108
|
serializedData
|
|
101
109
|
});
|
|
102
110
|
if (renderMode === "prerender") {
|
|
103
|
-
|
|
111
|
+
isrSet(cacheKey, finalHtml);
|
|
104
112
|
}
|
|
105
113
|
return c.html(finalHtml);
|
|
106
114
|
} catch (e) {
|
|
@@ -117,4 +125,4 @@ function createSSRApp(options) {
|
|
|
117
125
|
export {
|
|
118
126
|
createSSRApp
|
|
119
127
|
};
|
|
120
|
-
//# sourceMappingURL=chunk-
|
|
128
|
+
//# sourceMappingURL=chunk-IITKGRCO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../server/src/app.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 { parseAcceptLanguage } from \"./locale\";\n\nexport interface SSRModule {\n\trender: (\n\t\turl: string,\n\t\tlocale: string,\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}\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} = options;\n\n\tconst app = new Hono();\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(ssrProductionModule) 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(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(url, locale);\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"],"mappings":";;;;;;;;;AAQA,SAAS,YAAY;AAmCd,SAAS,aAAa,SAA8B;AAC1D,QAAM;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,eAAe;AAAA,IACf;AAAA,IACA;AAAA,IACA;AAAA,EACD,IAAI;AAEJ,QAAM,MAAM,IAAI,KAAK;AAGrB,QAAM,gBAAgB;AACtB,QAAM,WAAW,oBAAI,IAAoB;AACzC,WAAS,OAAO,KAAa,KAAa;AACzC,QAAI,SAAS,QAAQ,eAAe;AACnC,YAAM,QAAQ,SAAS,KAAK,EAAE,KAAK,EAAE;AACrC,UAAI,UAAU,OAAW,UAAS,OAAO,KAAK;AAAA,IAC/C;AACA,aAAS,IAAI,KAAK,GAAG;AAAA,EACtB;AAEA,iBAAe,aAAa,KAA8B;AACzD,QAAI,CAAC,gBAAgB,MAAM;AAC1B,YAAM,EAAE,cAAAA,cAAa,IAAI,MAAM;AAAA;AAAA,QAA0B;AAAA,MAAS;AAClE,YAAM,EAAE,SAAAC,SAAQ,IAAI,MAAM;AAAA;AAAA,QAA0B;AAAA,MAAW;AAC/D,YAAM,MAAMD,cAAaC,SAAQ,MAAM,YAAY,GAAG,OAAO;AAC7D,aAAO,KAAK,mBAAmB,KAAK,GAAG;AAAA,IACxC;AAEA,UAAM,SAAS,OAAQ,WAAmB,SAAS;AACnD,QAAI,QAAQ;AACX,aAAQ,WAAmB,KAAK;AAAA,QAC/B,IAAI,IAAI,6BAA6B,YAAY,GAAG;AAAA,MACrD;AAAA,IACD;AAEA,UAAM,EAAE,aAAa,IAAI,MAAM;AAAA;AAAA,MAA0B;AAAA,IAAS;AAClE,UAAM,EAAE,QAAQ,IAAI,MAAM;AAAA;AAAA,MAA0B;AAAA,IAAW;AAC/D,WAAO,aAAa,QAAQ,MAAM,wBAAwB,GAAG,OAAO;AAAA,EACrE;AAEA,iBAAe,gBAAoC;AAClD,QAAI,CAAC,gBAAgB,MAAM;AAC1B,aAAQ,MAAM,KAAK,cAAc,YAAY;AAAA,IAC9C;AACA,QAAI,qBAAqB;AACxB,aAAO,OAAO;AAAA,IACf;AACA,UAAM,EAAE,QAAQ,IAAI,MAAM;AAAA;AAAA,MAA0B;AAAA,IAAW;AAC/D,UAAM,EAAE,cAAc,IAAI,MAAM;AAAA;AAAA,MAA0B;AAAA,IAAU;AACpE,UAAM,UAAU,cAAc,QAAQ,MAAM,oBAAoB,CAAC,EAAE;AACnE,WAAO,OAAO;AAAA,EACf;AAEA,MAAI,IAAI,KAAK,OAAO,MAAM;AACzB,UAAM,MACL,EAAE,IAAI,QACL,EAAE,IAAI,IAAI,SAAS,GAAG,IAAI,MAAM,EAAE,IAAI,IAAI,MAAM,GAAG,EAAE,CAAC,IAAI;AAE5D,QAAI;AAEH,YAAM,WAAW;AACjB,YAAM,SAAS,SAAS,IAAI,QAAQ;AACpC,UAAI,OAAQ,QAAO,EAAE,KAAK,MAAM;AAEhC,YAAM,WAAW,MAAM,aAAa,GAAG;AACvC,YAAM,EAAE,QAAQ,oBAAoB,IAAI,MAAM,cAAc;AAE5D,YAAM,SAAS;AAAA,QACd,EAAE,IAAI,OAAO,iBAAiB;AAAA,QAC9B;AAAA,QACA;AAAA,MACD;AAEA,YAAM;AAAA,QACL,MAAM;AAAA,QACN;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACD,IAAI,MAAM,OAAO,KAAK,MAAM;AAG5B,UAAI,eAAe,OAAO;AACzB,eAAO,EAAE,KAAK,eAAe,UAAU,MAAM,CAAC;AAAA,MAC/C;AAEA,YAAM,iBAAiB,oBAAoB,UAAU;AAErD,YAAM,YAAY,iBAAiB;AAAA,QAClC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,MAAM;AAAA,QACN;AAAA,MACD,CAAC;AAGD,UAAI,eAAe,aAAa;AAC/B,eAAO,UAAU,SAAS;AAAA,MAC3B;AAEA,aAAO,EAAE,KAAK,SAAS;AAAA,IACxB,SAAS,GAAG;AACX,UAAI,CAAC,gBAAgB,MAAM;AAC1B,aAAK,iBAAiB,CAAU;AAAA,MACjC;AACA,cAAQ,MAAM,eAAe,CAAC;AAC9B,aAAO,EAAE,KAAK,yBAAyB,GAAG;AAAA,IAC3C;AAAA,EACD,CAAC;AAED,SAAO;AACR;","names":["readFileSync","resolve"]}
|
package/dist/index.cjs
CHANGED
|
@@ -1149,7 +1149,15 @@ function createSSRApp(options) {
|
|
|
1149
1149
|
defaultLocale
|
|
1150
1150
|
} = options;
|
|
1151
1151
|
const app = new import_hono.Hono();
|
|
1152
|
+
const ISR_CACHE_MAX = 1e3;
|
|
1152
1153
|
const isrCache = /* @__PURE__ */ new Map();
|
|
1154
|
+
function isrSet(key, val) {
|
|
1155
|
+
if (isrCache.size >= ISR_CACHE_MAX) {
|
|
1156
|
+
const first = isrCache.keys().next().value;
|
|
1157
|
+
if (first !== void 0) isrCache.delete(first);
|
|
1158
|
+
}
|
|
1159
|
+
isrCache.set(key, val);
|
|
1160
|
+
}
|
|
1153
1161
|
async function readTemplate(url) {
|
|
1154
1162
|
if (!isProduction && vite) {
|
|
1155
1163
|
const { readFileSync: readFileSync2 } = await import(
|
|
@@ -1230,7 +1238,7 @@ function createSSRApp(options) {
|
|
|
1230
1238
|
serializedData
|
|
1231
1239
|
});
|
|
1232
1240
|
if (renderMode === "prerender") {
|
|
1233
|
-
|
|
1241
|
+
isrSet(cacheKey, finalHtml);
|
|
1234
1242
|
}
|
|
1235
1243
|
return c.html(finalHtml);
|
|
1236
1244
|
} catch (e) {
|
|
@@ -1675,6 +1683,19 @@ function generateSSREntry(ctx, opts) {
|
|
|
1675
1683
|
const locales = JSON.stringify(ctx.locales);
|
|
1676
1684
|
const defaultLocale = JSON.stringify(ctx.defaultLocale);
|
|
1677
1685
|
const renderModes = JSON.stringify(ctx.renderModes ?? {});
|
|
1686
|
+
const cacheImpl = opts.platformCache ? opts.platformCache : `
|
|
1687
|
+
const ISR_CACHE_MAX = 1000;
|
|
1688
|
+
const _isrMap = new Map();
|
|
1689
|
+
async function platformCacheGet(url) {
|
|
1690
|
+
return _isrMap.get(url) ?? null;
|
|
1691
|
+
}
|
|
1692
|
+
async function platformCacheSet(url, html) {
|
|
1693
|
+
if (_isrMap.size >= ISR_CACHE_MAX) {
|
|
1694
|
+
const first = _isrMap.keys().next().value;
|
|
1695
|
+
_isrMap.delete(first);
|
|
1696
|
+
}
|
|
1697
|
+
_isrMap.set(url, html);
|
|
1698
|
+
}`;
|
|
1678
1699
|
return `
|
|
1679
1700
|
import { Hono } from "hono";
|
|
1680
1701
|
${opts.platformImport}
|
|
@@ -1685,6 +1706,7 @@ const TEMPLATE = ${JSON.stringify(ctx.templateHtml)};
|
|
|
1685
1706
|
const LOCALES = ${locales};
|
|
1686
1707
|
const DEFAULT_LOCALE = ${defaultLocale};
|
|
1687
1708
|
const RENDER_MODES = ${renderModes};
|
|
1709
|
+
${cacheImpl}
|
|
1688
1710
|
|
|
1689
1711
|
function parseAcceptLanguage(header) {
|
|
1690
1712
|
if (!header) return DEFAULT_LOCALE;
|
|
@@ -1727,10 +1749,9 @@ function matchRenderMode(url) {
|
|
|
1727
1749
|
return null;
|
|
1728
1750
|
}
|
|
1729
1751
|
|
|
1730
|
-
const isrCache = new Map();
|
|
1731
|
-
|
|
1732
1752
|
const app = new Hono();
|
|
1733
1753
|
${setupCall}
|
|
1754
|
+
${opts.platformMiddleware ?? ""}
|
|
1734
1755
|
|
|
1735
1756
|
app.get("*", async (c) => {
|
|
1736
1757
|
const url = c.req.path + (c.req.url.includes("?") ? "?" + c.req.url.split("?")[1] : "");
|
|
@@ -1744,7 +1765,7 @@ app.get("*", async (c) => {
|
|
|
1744
1765
|
}
|
|
1745
1766
|
|
|
1746
1767
|
// ISR \u7F13\u5B58\u547D\u4E2D
|
|
1747
|
-
const cached =
|
|
1768
|
+
const cached = await platformCacheGet(url);
|
|
1748
1769
|
if (cached) return c.html(cached);
|
|
1749
1770
|
|
|
1750
1771
|
const { html: appHtml, head, css, serverData, renderMode } = await render(url, locale);
|
|
@@ -1759,7 +1780,8 @@ app.get("*", async (c) => {
|
|
|
1759
1780
|
|
|
1760
1781
|
// Prerender ISR \u7F13\u5B58\uFF08\u5305\u62EC Vite \u914D\u7F6E\u8986\u76D6\u548C\u8DEF\u7531\u7EA7\uFF09
|
|
1761
1782
|
if (renderMode === "prerender" || overrideMode === "prerender") {
|
|
1762
|
-
|
|
1783
|
+
await platformCacheSet(url, finalHtml);
|
|
1784
|
+
${opts.platformPrerenderResponseHook ?? ""}
|
|
1763
1785
|
}
|
|
1764
1786
|
|
|
1765
1787
|
return c.html(finalHtml);
|
|
@@ -1895,7 +1917,29 @@ function cloudflareAdapter() {
|
|
|
1895
1917
|
fs.rmSync(outputDir, { recursive: true, force: true });
|
|
1896
1918
|
const entrySource = generateSSREntry(ctx, {
|
|
1897
1919
|
platformImport: ``,
|
|
1898
|
-
platformExport: `export default app
|
|
1920
|
+
platformExport: `export default app;`,
|
|
1921
|
+
// Cloudflare Cache API — 持久化 ISR 缓存到 CDN 边缘节点
|
|
1922
|
+
platformCache: `
|
|
1923
|
+
const ISR_CACHE_TTL = 3600; // 1 hour
|
|
1924
|
+
async function platformCacheGet(url) {
|
|
1925
|
+
try {
|
|
1926
|
+
const cache = caches.default;
|
|
1927
|
+
const cacheKey = new Request("https://isr-cache/" + encodeURIComponent(url));
|
|
1928
|
+
const resp = await cache.match(cacheKey);
|
|
1929
|
+
if (resp) return await resp.text();
|
|
1930
|
+
} catch {}
|
|
1931
|
+
return null;
|
|
1932
|
+
}
|
|
1933
|
+
async function platformCacheSet(url, html) {
|
|
1934
|
+
try {
|
|
1935
|
+
const cache = caches.default;
|
|
1936
|
+
const cacheKey = new Request("https://isr-cache/" + encodeURIComponent(url));
|
|
1937
|
+
const resp = new Response(html, {
|
|
1938
|
+
headers: { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "public, max-age=" + ISR_CACHE_TTL },
|
|
1939
|
+
});
|
|
1940
|
+
await cache.put(cacheKey, resp);
|
|
1941
|
+
} catch {}
|
|
1942
|
+
}`
|
|
1899
1943
|
});
|
|
1900
1944
|
const tempEntry = path.resolve(root, ".cf-entry.tmp.mjs");
|
|
1901
1945
|
fs.writeFileSync(tempEntry, entrySource);
|
|
@@ -1941,7 +1985,25 @@ function netlifyAdapter() {
|
|
|
1941
1985
|
});
|
|
1942
1986
|
const entrySource = generateSSREntry(ctx, {
|
|
1943
1987
|
platformImport: `import { handle } from "hono/netlify";`,
|
|
1944
|
-
platformExport: `export default handle(app)
|
|
1988
|
+
platformExport: `export default handle(app);
|
|
1989
|
+
export const config = { path: "/*", preferStatic: true };`,
|
|
1990
|
+
// Netlify CDN 缓存 — 使用 Netlify-CDN-Cache-Control 头做 ISR
|
|
1991
|
+
platformCache: `
|
|
1992
|
+
const ISR_SWR_TTL = 3600;
|
|
1993
|
+
const ISR_CACHE_MAX = 1000;
|
|
1994
|
+
const _isrMap = new Map();
|
|
1995
|
+
async function platformCacheGet(url) {
|
|
1996
|
+
return _isrMap.get(url) ?? null;
|
|
1997
|
+
}
|
|
1998
|
+
async function platformCacheSet(url, html) {
|
|
1999
|
+
if (_isrMap.size >= ISR_CACHE_MAX) {
|
|
2000
|
+
const first = _isrMap.keys().next().value;
|
|
2001
|
+
_isrMap.delete(first);
|
|
2002
|
+
}
|
|
2003
|
+
_isrMap.set(url, html);
|
|
2004
|
+
}`,
|
|
2005
|
+
platformPrerenderResponseHook: `c.header("Cache-Control", "public, max-age=0, must-revalidate");
|
|
2006
|
+
c.header("Netlify-CDN-Cache-Control", "public, max-age=" + ISR_SWR_TTL + ", stale-while-revalidate=" + ISR_SWR_TTL + ", durable");`
|
|
1945
2007
|
});
|
|
1946
2008
|
const tempEntry = path.resolve(root, ".netlify-entry.tmp.mjs");
|
|
1947
2009
|
fs.writeFileSync(tempEntry, entrySource);
|
|
@@ -1981,7 +2043,31 @@ function nodeAdapter() {
|
|
|
1981
2043
|
async build(ctx) {
|
|
1982
2044
|
const { fs, path, root } = ctx;
|
|
1983
2045
|
const entrySource = generateSSREntry(ctx, {
|
|
1984
|
-
platformImport: `import { serve } from "@hono/node-server"
|
|
2046
|
+
platformImport: `import { serve } from "@hono/node-server";
|
|
2047
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
2048
|
+
import { resolve, dirname } from "node:path";
|
|
2049
|
+
import { fileURLToPath } from "node:url";`,
|
|
2050
|
+
platformMiddleware: `
|
|
2051
|
+
// \u9884\u6E32\u67D3\u6587\u4EF6\u4E2D\u95F4\u4EF6\uFF1A\u68C0\u67E5 dist/prerender/ \u4E0B\u662F\u5426\u6709\u5BF9\u5E94\u7684\u9759\u6001 HTML
|
|
2052
|
+
const __entry_dirname = dirname(fileURLToPath(import.meta.url));
|
|
2053
|
+
const prerenderDir = resolve(__entry_dirname, "../prerender");
|
|
2054
|
+
|
|
2055
|
+
app.use("*", async (c, next) => {
|
|
2056
|
+
const urlPath = c.req.path;
|
|
2057
|
+
const candidates = [
|
|
2058
|
+
resolve(prerenderDir, "." + urlPath, "index.html"),
|
|
2059
|
+
resolve(prerenderDir, "." + urlPath + ".html"),
|
|
2060
|
+
];
|
|
2061
|
+
if (urlPath === "/") candidates.unshift(resolve(prerenderDir, "index.html"));
|
|
2062
|
+
for (const f of candidates) {
|
|
2063
|
+
if (existsSync(f)) {
|
|
2064
|
+
const html = readFileSync(f, "utf-8");
|
|
2065
|
+
return c.html(html);
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
await next();
|
|
2069
|
+
});
|
|
2070
|
+
`,
|
|
1985
2071
|
platformExport: `
|
|
1986
2072
|
const port = +(process.env.PORT || 3000);
|
|
1987
2073
|
serve({ fetch: app.fetch, port }, (info) => {
|
|
@@ -2243,7 +2329,7 @@ function vercelAdapter() {
|
|
|
2243
2329
|
version: 3,
|
|
2244
2330
|
routes: [
|
|
2245
2331
|
{ handle: "filesystem" },
|
|
2246
|
-
{ src: "/(.*)", dest: "/ssr" }
|
|
2332
|
+
{ src: "/(.*)", dest: "/ssr/$1" }
|
|
2247
2333
|
]
|
|
2248
2334
|
},
|
|
2249
2335
|
null,
|
|
@@ -2260,6 +2346,32 @@ function vercelAdapter() {
|
|
|
2260
2346
|
fs.mkdirSync(path.resolve(filePath, ".."), { recursive: true });
|
|
2261
2347
|
fs.writeFileSync(filePath, html);
|
|
2262
2348
|
}
|
|
2349
|
+
if (prerendered.length > 0) {
|
|
2350
|
+
const configPath = path.resolve(
|
|
2351
|
+
root,
|
|
2352
|
+
".vercel/output/config.json"
|
|
2353
|
+
);
|
|
2354
|
+
const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
2355
|
+
config.overrides = config.overrides ?? {};
|
|
2356
|
+
for (const { url } of prerendered) {
|
|
2357
|
+
const key = url === "/" ? "index.html" : `${url.replace(/^\//, "")}/index.html`;
|
|
2358
|
+
config.overrides[key] = {
|
|
2359
|
+
path: url === "/" ? "/" : url,
|
|
2360
|
+
contentType: "text/html; charset=utf-8"
|
|
2361
|
+
};
|
|
2362
|
+
}
|
|
2363
|
+
const isrRoutes = prerendered.map(({ url }) => ({
|
|
2364
|
+
src: `^${url.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}/?$`,
|
|
2365
|
+
dest: url,
|
|
2366
|
+
has: [{ type: "header", key: "x-vercel-isr" }]
|
|
2367
|
+
}));
|
|
2368
|
+
config.routes = [
|
|
2369
|
+
...isrRoutes,
|
|
2370
|
+
{ handle: "filesystem" },
|
|
2371
|
+
{ src: "/(.*)", dest: "/ssr/$1" }
|
|
2372
|
+
];
|
|
2373
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
2374
|
+
}
|
|
2263
2375
|
console.log(" Vercel output \u2192 .vercel/output/\n");
|
|
2264
2376
|
}
|
|
2265
2377
|
};
|