@finesoft/front 0.1.19 → 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-BPO26FAD.js +10 -0
- package/dist/browser.cjs +7 -5
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.d.cts +12 -2
- package/dist/browser.d.ts +12 -2
- package/dist/browser.js +2 -2
- package/dist/{chunk-2VETWTN5.js → chunk-5ZECBECP.js} +18 -3
- package/dist/chunk-5ZECBECP.js.map +1 -0
- package/dist/{chunk-RVKDILGM.js → chunk-BUYWNNNQ.js} +8 -6
- package/dist/chunk-BUYWNNNQ.js.map +1 -0
- package/dist/{chunk-T2AQHAYK.js → chunk-H3RNYNSD.js} +2 -2
- package/dist/{chunk-Z4MHYAS3.js → chunk-IITKGRCO.js} +23 -3
- package/dist/chunk-IITKGRCO.js.map +1 -0
- package/dist/index.cjs +410 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +48 -2
- package/dist/index.d.ts +48 -2
- package/dist/index.js +373 -31
- package/dist/index.js.map +1 -1
- package/dist/{src-7D236CLJ.js → src-KUWACLPD.js} +5 -3
- package/package.json +1 -1
- package/dist/app-MYBG3TGV.js +0 -10
- package/dist/chunk-2VETWTN5.js.map +0 -1
- package/dist/chunk-RVKDILGM.js.map +0 -1
- package/dist/chunk-Z4MHYAS3.js.map +0 -1
- /package/dist/{app-MYBG3TGV.js.map → app-BPO26FAD.js.map} +0 -0
- /package/dist/{chunk-T2AQHAYK.js.map → chunk-H3RNYNSD.js.map} +0 -0
- /package/dist/{src-7D236CLJ.js.map → src-KUWACLPD.js.map} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -395,10 +395,10 @@ var init_router = __esm({
|
|
|
395
395
|
Router = class {
|
|
396
396
|
routes = [];
|
|
397
397
|
/** 添加路由规则 */
|
|
398
|
-
add(pattern, intentId) {
|
|
398
|
+
add(pattern, intentId, renderMode) {
|
|
399
399
|
const paramNames = [];
|
|
400
400
|
const regexStr = pattern.replace(
|
|
401
|
-
/\/:(\w+)(\?)?/g,
|
|
401
|
+
/\/:([\w]+)(\?)?/g,
|
|
402
402
|
(_, name, optional) => {
|
|
403
403
|
paramNames.push(name);
|
|
404
404
|
return optional ? "(?:/([^/]+))?" : "/([^/]+)";
|
|
@@ -408,7 +408,8 @@ var init_router = __esm({
|
|
|
408
408
|
pattern,
|
|
409
409
|
intentId,
|
|
410
410
|
regex: new RegExp(`^${regexStr}/?$`),
|
|
411
|
-
paramNames
|
|
411
|
+
paramNames,
|
|
412
|
+
renderMode
|
|
412
413
|
});
|
|
413
414
|
return this;
|
|
414
415
|
}
|
|
@@ -429,7 +430,8 @@ var init_router = __esm({
|
|
|
429
430
|
}
|
|
430
431
|
return {
|
|
431
432
|
intent: { id: route.intentId, params },
|
|
432
|
-
action: makeFlowAction(urlOrPath)
|
|
433
|
+
action: makeFlowAction(urlOrPath),
|
|
434
|
+
renderMode: route.renderMode
|
|
433
435
|
};
|
|
434
436
|
}
|
|
435
437
|
}
|
|
@@ -817,7 +819,7 @@ function defineRoutes(framework, definitions) {
|
|
|
817
819
|
framework.registerIntent(def.controller);
|
|
818
820
|
registeredIntents.add(def.intentId);
|
|
819
821
|
}
|
|
820
|
-
framework.router.add(def.path, def.intentId);
|
|
822
|
+
framework.router.add(def.path, def.intentId, def.renderMode);
|
|
821
823
|
}
|
|
822
824
|
}
|
|
823
825
|
var init_define_routes = __esm({
|
|
@@ -973,6 +975,16 @@ async function ssrRender(options) {
|
|
|
973
975
|
const parsed = new URL(url, "http://localhost");
|
|
974
976
|
const fullPath = parsed.pathname + parsed.search;
|
|
975
977
|
const match = framework.routeUrl(fullPath);
|
|
978
|
+
if (match?.renderMode === "csr") {
|
|
979
|
+
framework.dispose();
|
|
980
|
+
return {
|
|
981
|
+
html: "",
|
|
982
|
+
head: "",
|
|
983
|
+
css: "",
|
|
984
|
+
serverData: [],
|
|
985
|
+
renderMode: "csr"
|
|
986
|
+
};
|
|
987
|
+
}
|
|
976
988
|
let page;
|
|
977
989
|
let serverData = [];
|
|
978
990
|
if (match) {
|
|
@@ -991,7 +1003,8 @@ async function ssrRender(options) {
|
|
|
991
1003
|
html: result.html,
|
|
992
1004
|
head: result.head,
|
|
993
1005
|
css: result.css,
|
|
994
|
-
serverData
|
|
1006
|
+
serverData,
|
|
1007
|
+
renderMode: match?.renderMode
|
|
995
1008
|
};
|
|
996
1009
|
}
|
|
997
1010
|
var init_render = __esm({
|
|
@@ -1029,6 +1042,9 @@ ${cssTag}`).replace(SSR_PLACEHOLDERS.BODY, html).replace(
|
|
|
1029
1042
|
`<script id="serialized-server-data" type="application/json">${serializedData}</script>`
|
|
1030
1043
|
);
|
|
1031
1044
|
}
|
|
1045
|
+
function injectCSRShell(template, locale) {
|
|
1046
|
+
return template.replace(SSR_PLACEHOLDERS.LANG, locale).replace(SSR_PLACEHOLDERS.HEAD, "").replace(SSR_PLACEHOLDERS.BODY, "").replace(SSR_PLACEHOLDERS.DATA, "");
|
|
1047
|
+
}
|
|
1032
1048
|
var SSR_PLACEHOLDERS;
|
|
1033
1049
|
var init_inject = __esm({
|
|
1034
1050
|
"../ssr/src/inject.ts"() {
|
|
@@ -1071,6 +1087,7 @@ __export(src_exports, {
|
|
|
1071
1087
|
Framework: () => Framework,
|
|
1072
1088
|
SSR_PLACEHOLDERS: () => SSR_PLACEHOLDERS,
|
|
1073
1089
|
createSSRRender: () => createSSRRender,
|
|
1090
|
+
injectCSRShell: () => injectCSRShell,
|
|
1074
1091
|
injectSSRContent: () => injectSSRContent,
|
|
1075
1092
|
serializeServerData: () => serializeServerData,
|
|
1076
1093
|
ssrRender: () => ssrRender
|
|
@@ -1132,6 +1149,15 @@ function createSSRApp(options) {
|
|
|
1132
1149
|
defaultLocale
|
|
1133
1150
|
} = options;
|
|
1134
1151
|
const app = new import_hono.Hono();
|
|
1152
|
+
const ISR_CACHE_MAX = 1e3;
|
|
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
|
+
}
|
|
1135
1161
|
async function readTemplate(url) {
|
|
1136
1162
|
if (!isProduction && vite) {
|
|
1137
1163
|
const { readFileSync: readFileSync2 } = await import(
|
|
@@ -1182,6 +1208,9 @@ function createSSRApp(options) {
|
|
|
1182
1208
|
app.get("*", async (c) => {
|
|
1183
1209
|
const url = c.req.path + (c.req.url.includes("?") ? "?" + c.req.url.split("?")[1] : "");
|
|
1184
1210
|
try {
|
|
1211
|
+
const cacheKey = url;
|
|
1212
|
+
const cached = isrCache.get(cacheKey);
|
|
1213
|
+
if (cached) return c.html(cached);
|
|
1185
1214
|
const template = await readTemplate(url);
|
|
1186
1215
|
const { render, serializeServerData: serializeServerData2 } = await loadSSRModule();
|
|
1187
1216
|
const locale = parseAcceptLanguage(
|
|
@@ -1193,8 +1222,12 @@ function createSSRApp(options) {
|
|
|
1193
1222
|
html: appHtml,
|
|
1194
1223
|
head,
|
|
1195
1224
|
css,
|
|
1196
|
-
serverData
|
|
1225
|
+
serverData,
|
|
1226
|
+
renderMode
|
|
1197
1227
|
} = await render(url, locale);
|
|
1228
|
+
if (renderMode === "csr") {
|
|
1229
|
+
return c.html(injectCSRShell(template, locale));
|
|
1230
|
+
}
|
|
1198
1231
|
const serializedData = serializeServerData2(serverData);
|
|
1199
1232
|
const finalHtml = injectSSRContent({
|
|
1200
1233
|
template,
|
|
@@ -1204,6 +1237,9 @@ function createSSRApp(options) {
|
|
|
1204
1237
|
html: appHtml,
|
|
1205
1238
|
serializedData
|
|
1206
1239
|
});
|
|
1240
|
+
if (renderMode === "prerender") {
|
|
1241
|
+
isrSet(cacheKey, finalHtml);
|
|
1242
|
+
}
|
|
1207
1243
|
return c.html(finalHtml);
|
|
1208
1244
|
} catch (e) {
|
|
1209
1245
|
if (!isProduction && vite) {
|
|
@@ -1261,6 +1297,7 @@ __export(index_exports, {
|
|
|
1261
1297
|
finesoftFrontViteConfig: () => finesoftFrontViteConfig,
|
|
1262
1298
|
generateUuid: () => generateUuid,
|
|
1263
1299
|
getBaseUrl: () => getBaseUrl,
|
|
1300
|
+
injectCSRShell: () => injectCSRShell,
|
|
1264
1301
|
injectSSRContent: () => injectSSRContent,
|
|
1265
1302
|
isCompoundAction: () => isCompoundAction,
|
|
1266
1303
|
isExternalUrlAction: () => isExternalUrlAction,
|
|
@@ -1645,6 +1682,20 @@ function generateSSREntry(ctx, opts) {
|
|
|
1645
1682
|
const setupCall = ctx.setupPath ? `if (typeof _setupDefault === "function") await _setupDefault(app);` : ``;
|
|
1646
1683
|
const locales = JSON.stringify(ctx.locales);
|
|
1647
1684
|
const defaultLocale = JSON.stringify(ctx.defaultLocale);
|
|
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
|
+
}`;
|
|
1648
1699
|
return `
|
|
1649
1700
|
import { Hono } from "hono";
|
|
1650
1701
|
${opts.platformImport}
|
|
@@ -1654,6 +1705,8 @@ ${setupImport}
|
|
|
1654
1705
|
const TEMPLATE = ${JSON.stringify(ctx.templateHtml)};
|
|
1655
1706
|
const LOCALES = ${locales};
|
|
1656
1707
|
const DEFAULT_LOCALE = ${defaultLocale};
|
|
1708
|
+
const RENDER_MODES = ${renderModes};
|
|
1709
|
+
${cacheImpl}
|
|
1657
1710
|
|
|
1658
1711
|
function parseAcceptLanguage(header) {
|
|
1659
1712
|
if (!header) return DEFAULT_LOCALE;
|
|
@@ -1676,16 +1729,62 @@ function injectSSR(t, locale, head, css, html, data) {
|
|
|
1676
1729
|
.replace("<!--ssr-data-->", '<script id="serialized-server-data" type="application/json">' + data + "</script>");
|
|
1677
1730
|
}
|
|
1678
1731
|
|
|
1732
|
+
function injectCSRShell(t, locale) {
|
|
1733
|
+
return t
|
|
1734
|
+
.replace("<!--ssr-lang-->", locale)
|
|
1735
|
+
.replace("<!--ssr-head-->", "")
|
|
1736
|
+
.replace("<!--ssr-body-->", "")
|
|
1737
|
+
.replace("<!--ssr-data-->", "");
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
function matchRenderMode(url) {
|
|
1741
|
+
const path = url.split("?")[0];
|
|
1742
|
+
if (RENDER_MODES[path]) return RENDER_MODES[path];
|
|
1743
|
+
for (const [pattern, mode] of Object.entries(RENDER_MODES)) {
|
|
1744
|
+
if (pattern.includes("*")) {
|
|
1745
|
+
const re = new RegExp("^" + pattern.replace(/\\*/g, ".*") + "$");
|
|
1746
|
+
if (re.test(path)) return mode;
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
return null;
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1679
1752
|
const app = new Hono();
|
|
1680
1753
|
${setupCall}
|
|
1754
|
+
${opts.platformMiddleware ?? ""}
|
|
1681
1755
|
|
|
1682
1756
|
app.get("*", async (c) => {
|
|
1683
1757
|
const url = c.req.path + (c.req.url.includes("?") ? "?" + c.req.url.split("?")[1] : "");
|
|
1684
1758
|
try {
|
|
1685
1759
|
const locale = parseAcceptLanguage(c.req.header("accept-language"));
|
|
1686
|
-
|
|
1760
|
+
|
|
1761
|
+
// Vite \u914D\u7F6E\u7EA7\u522B\u8986\u76D6: CSR \u76F4\u63A5\u8FD4\u56DE\u7A7A\u58F3
|
|
1762
|
+
const overrideMode = matchRenderMode(url);
|
|
1763
|
+
if (overrideMode === "csr") {
|
|
1764
|
+
return c.html(injectCSRShell(TEMPLATE, locale));
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
// ISR \u7F13\u5B58\u547D\u4E2D
|
|
1768
|
+
const cached = await platformCacheGet(url);
|
|
1769
|
+
if (cached) return c.html(cached);
|
|
1770
|
+
|
|
1771
|
+
const { html: appHtml, head, css, serverData, renderMode } = await render(url, locale);
|
|
1772
|
+
|
|
1773
|
+
// \u8DEF\u7531\u7EA7 CSR
|
|
1774
|
+
if (renderMode === "csr") {
|
|
1775
|
+
return c.html(injectCSRShell(TEMPLATE, locale));
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1687
1778
|
const serializedData = serializeServerData(serverData);
|
|
1688
|
-
|
|
1779
|
+
const finalHtml = injectSSR(TEMPLATE, locale, head, css, appHtml, serializedData);
|
|
1780
|
+
|
|
1781
|
+
// Prerender ISR \u7F13\u5B58\uFF08\u5305\u62EC Vite \u914D\u7F6E\u8986\u76D6\u548C\u8DEF\u7531\u7EA7\uFF09
|
|
1782
|
+
if (renderMode === "prerender" || overrideMode === "prerender") {
|
|
1783
|
+
await platformCacheSet(url, finalHtml);
|
|
1784
|
+
${opts.platformPrerenderResponseHook ?? ""}
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
return c.html(finalHtml);
|
|
1689
1788
|
} catch (e) {
|
|
1690
1789
|
console.error("[SSR Error]", e);
|
|
1691
1790
|
return c.text("Internal Server Error", 500);
|
|
@@ -1724,6 +1823,89 @@ function copyStaticAssets(ctx, destDir, opts) {
|
|
|
1724
1823
|
fs.rmSync(path.join(destDir, "index.html"), { force: true });
|
|
1725
1824
|
}
|
|
1726
1825
|
}
|
|
1826
|
+
async function prerenderRoutes(ctx, routesExport = "src/lib/bootstrap.ts") {
|
|
1827
|
+
const { fs, path, root, vite } = ctx;
|
|
1828
|
+
const { pathToFileURL } = await import(
|
|
1829
|
+
/* @vite-ignore */
|
|
1830
|
+
"url"
|
|
1831
|
+
);
|
|
1832
|
+
await vite.build({
|
|
1833
|
+
root,
|
|
1834
|
+
build: {
|
|
1835
|
+
ssr: routesExport,
|
|
1836
|
+
outDir: path.resolve(root, "dist/server"),
|
|
1837
|
+
emptyOutDir: false,
|
|
1838
|
+
rollupOptions: {
|
|
1839
|
+
output: { entryFileNames: "_routes_prerender.mjs" }
|
|
1840
|
+
}
|
|
1841
|
+
},
|
|
1842
|
+
resolve: ctx.resolvedResolve
|
|
1843
|
+
});
|
|
1844
|
+
const routesPath = pathToFileURL(
|
|
1845
|
+
path.resolve(root, "dist/server/_routes_prerender.mjs")
|
|
1846
|
+
).href;
|
|
1847
|
+
const routesMod = await import(
|
|
1848
|
+
/* @vite-ignore */
|
|
1849
|
+
routesPath
|
|
1850
|
+
);
|
|
1851
|
+
const routes = routesMod.routes ?? routesMod.default ?? [];
|
|
1852
|
+
fs.rmSync(path.resolve(root, "dist/server/_routes_prerender.mjs"), {
|
|
1853
|
+
force: true
|
|
1854
|
+
});
|
|
1855
|
+
const prerenderPaths = /* @__PURE__ */ new Set();
|
|
1856
|
+
for (const r of routes) {
|
|
1857
|
+
if (r.renderMode === "prerender" && r.path && !r.path.includes(":")) {
|
|
1858
|
+
prerenderPaths.add(r.path);
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
if (ctx.renderModes) {
|
|
1862
|
+
for (const [pattern, mode] of Object.entries(ctx.renderModes)) {
|
|
1863
|
+
if (mode === "prerender" && !pattern.includes("*") && !pattern.includes(":")) {
|
|
1864
|
+
prerenderPaths.add(pattern);
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
if (prerenderPaths.size === 0) return [];
|
|
1869
|
+
const ssrPath = pathToFileURL(
|
|
1870
|
+
path.resolve(root, "dist/server/ssr.js")
|
|
1871
|
+
).href;
|
|
1872
|
+
const ssrModule = await import(
|
|
1873
|
+
/* @vite-ignore */
|
|
1874
|
+
ssrPath
|
|
1875
|
+
);
|
|
1876
|
+
const results = [];
|
|
1877
|
+
for (const routePath of prerenderPaths) {
|
|
1878
|
+
for (const locale of ctx.locales) {
|
|
1879
|
+
const url = locale === ctx.defaultLocale ? routePath : `/${locale}${routePath === "/" ? "" : routePath}`;
|
|
1880
|
+
try {
|
|
1881
|
+
const {
|
|
1882
|
+
html: appHtml,
|
|
1883
|
+
head,
|
|
1884
|
+
css,
|
|
1885
|
+
serverData
|
|
1886
|
+
} = await ssrModule.render(url, locale);
|
|
1887
|
+
const serializedData = ssrModule.serializeServerData(serverData);
|
|
1888
|
+
const finalHtml = ctx.templateHtml.replace("<!--ssr-lang-->", locale).replace(
|
|
1889
|
+
"<!--ssr-head-->",
|
|
1890
|
+
head + "\n<style>" + css + "</style>"
|
|
1891
|
+
).replace("<!--ssr-body-->", appHtml).replace(
|
|
1892
|
+
"<!--ssr-data-->",
|
|
1893
|
+
'<script id="serialized-server-data" type="application/json">' + serializedData + "</script>"
|
|
1894
|
+
);
|
|
1895
|
+
results.push({ url, html: finalHtml });
|
|
1896
|
+
} catch (e) {
|
|
1897
|
+
console.warn(` [prerender] Failed to render ${url}:`, e);
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
if (results.length > 0) {
|
|
1902
|
+
console.log(
|
|
1903
|
+
` Pre-rendered ${results.length} pages (${prerenderPaths.size} routes \xD7 ${ctx.locales.length} locales)
|
|
1904
|
+
`
|
|
1905
|
+
);
|
|
1906
|
+
}
|
|
1907
|
+
return results;
|
|
1908
|
+
}
|
|
1727
1909
|
|
|
1728
1910
|
// ../server/src/adapters/cloudflare.ts
|
|
1729
1911
|
function cloudflareAdapter() {
|
|
@@ -1735,7 +1917,29 @@ function cloudflareAdapter() {
|
|
|
1735
1917
|
fs.rmSync(outputDir, { recursive: true, force: true });
|
|
1736
1918
|
const entrySource = generateSSREntry(ctx, {
|
|
1737
1919
|
platformImport: ``,
|
|
1738
|
-
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
|
+
}`
|
|
1739
1943
|
});
|
|
1740
1944
|
const tempEntry = path.resolve(root, ".cf-entry.tmp.mjs");
|
|
1741
1945
|
fs.writeFileSync(tempEntry, entrySource);
|
|
@@ -1749,6 +1953,14 @@ function cloudflareAdapter() {
|
|
|
1749
1953
|
// 这些构建工具运行时不需要,且 fsevents 是 macOS .node 原生二进制无法打包
|
|
1750
1954
|
});
|
|
1751
1955
|
copyStaticAssets(ctx, path.resolve(outputDir, "assets"));
|
|
1956
|
+
const prerendered = await prerenderRoutes(ctx);
|
|
1957
|
+
for (const { url, html } of prerendered) {
|
|
1958
|
+
const filePath = url === "/" ? path.join(outputDir, "assets", "index.html") : path.join(outputDir, "assets", url, "index.html");
|
|
1959
|
+
fs.mkdirSync(path.resolve(filePath, ".."), {
|
|
1960
|
+
recursive: true
|
|
1961
|
+
});
|
|
1962
|
+
fs.writeFileSync(filePath, html);
|
|
1963
|
+
}
|
|
1752
1964
|
} finally {
|
|
1753
1965
|
fs.rmSync(tempEntry, { force: true });
|
|
1754
1966
|
}
|
|
@@ -1773,7 +1985,25 @@ function netlifyAdapter() {
|
|
|
1773
1985
|
});
|
|
1774
1986
|
const entrySource = generateSSREntry(ctx, {
|
|
1775
1987
|
platformImport: `import { handle } from "hono/netlify";`,
|
|
1776
|
-
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");`
|
|
1777
2007
|
});
|
|
1778
2008
|
const tempEntry = path.resolve(root, ".netlify-entry.tmp.mjs");
|
|
1779
2009
|
fs.writeFileSync(tempEntry, entrySource);
|
|
@@ -1792,6 +2022,13 @@ function netlifyAdapter() {
|
|
|
1792
2022
|
path.resolve(root, "dist/client/_redirects"),
|
|
1793
2023
|
redirects
|
|
1794
2024
|
);
|
|
2025
|
+
const prerendered = await prerenderRoutes(ctx);
|
|
2026
|
+
const clientDir = path.resolve(root, "dist/client");
|
|
2027
|
+
for (const { url, html } of prerendered) {
|
|
2028
|
+
const filePath = url === "/" ? path.join(clientDir, "index.html") : path.join(clientDir, url, "index.html");
|
|
2029
|
+
fs.mkdirSync(path.resolve(filePath, ".."), { recursive: true });
|
|
2030
|
+
fs.writeFileSync(filePath, html);
|
|
2031
|
+
}
|
|
1795
2032
|
console.log(
|
|
1796
2033
|
" Netlify output \u2192 .netlify/functions-internal/ssr/\n Publish dir: dist/client/\n"
|
|
1797
2034
|
);
|
|
@@ -1806,7 +2043,31 @@ function nodeAdapter() {
|
|
|
1806
2043
|
async build(ctx) {
|
|
1807
2044
|
const { fs, path, root } = ctx;
|
|
1808
2045
|
const entrySource = generateSSREntry(ctx, {
|
|
1809
|
-
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
|
+
`,
|
|
1810
2071
|
platformExport: `
|
|
1811
2072
|
const port = +(process.env.PORT || 3000);
|
|
1812
2073
|
serve({ fetch: app.fetch, port }, (info) => {
|
|
@@ -1825,6 +2086,18 @@ serve({ fetch: app.fetch, port }, (info) => {
|
|
|
1825
2086
|
} finally {
|
|
1826
2087
|
fs.rmSync(tempEntry, { force: true });
|
|
1827
2088
|
}
|
|
2089
|
+
const prerendered = await prerenderRoutes(ctx);
|
|
2090
|
+
if (prerendered.length > 0) {
|
|
2091
|
+
const prerenderDir = path.resolve(root, "dist/prerender");
|
|
2092
|
+
fs.mkdirSync(prerenderDir, { recursive: true });
|
|
2093
|
+
for (const { url, html } of prerendered) {
|
|
2094
|
+
const filePath = url === "/" ? path.join(prerenderDir, "index.html") : path.join(prerenderDir, url, "index.html");
|
|
2095
|
+
fs.mkdirSync(path.resolve(filePath, ".."), {
|
|
2096
|
+
recursive: true
|
|
2097
|
+
});
|
|
2098
|
+
fs.writeFileSync(filePath, html);
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
1828
2101
|
console.log(
|
|
1829
2102
|
" Node output \u2192 dist/server/index.mjs\n Run: node dist/server/index.mjs\n"
|
|
1830
2103
|
);
|
|
@@ -1853,7 +2126,7 @@ function staticAdapter(opts = {}) {
|
|
|
1853
2126
|
ssrPath
|
|
1854
2127
|
);
|
|
1855
2128
|
ctx.copyStaticAssets(outputDir, { excludeHtml: true });
|
|
1856
|
-
const routePaths = await
|
|
2129
|
+
const { paths: routePaths, defs: routeDefs } = await extractRoutesWithModes(ctx, opts);
|
|
1857
2130
|
const allUrls = [];
|
|
1858
2131
|
for (const routePath of routePaths) {
|
|
1859
2132
|
for (const locale of ctx.locales) {
|
|
@@ -1872,21 +2145,37 @@ function staticAdapter(opts = {}) {
|
|
|
1872
2145
|
ctx.locales,
|
|
1873
2146
|
ctx.defaultLocale
|
|
1874
2147
|
);
|
|
1875
|
-
const
|
|
1876
|
-
|
|
1877
|
-
head,
|
|
1878
|
-
css,
|
|
1879
|
-
serverData
|
|
1880
|
-
} = await ssrModule.render(url, locale);
|
|
1881
|
-
const serializedData = ssrModule.serializeServerData(serverData);
|
|
1882
|
-
const finalHtml = injectSSRForStatic(
|
|
1883
|
-
ctx.templateHtml,
|
|
1884
|
-
locale,
|
|
1885
|
-
head,
|
|
1886
|
-
css,
|
|
1887
|
-
appHtml,
|
|
1888
|
-
serializedData
|
|
2148
|
+
const routeDef = routeDefs.find(
|
|
2149
|
+
(r) => r.path === stripLocalePrefix(url, ctx.locales)
|
|
1889
2150
|
);
|
|
2151
|
+
const mode = resolveRenderMode(
|
|
2152
|
+
stripLocalePrefix(url, ctx.locales),
|
|
2153
|
+
routeDef?.renderMode,
|
|
2154
|
+
ctx.renderModes
|
|
2155
|
+
);
|
|
2156
|
+
let finalHtml;
|
|
2157
|
+
if (mode === "csr") {
|
|
2158
|
+
finalHtml = injectCSRShellForStatic(
|
|
2159
|
+
ctx.templateHtml,
|
|
2160
|
+
locale
|
|
2161
|
+
);
|
|
2162
|
+
} else {
|
|
2163
|
+
const {
|
|
2164
|
+
html: appHtml,
|
|
2165
|
+
head,
|
|
2166
|
+
css,
|
|
2167
|
+
serverData
|
|
2168
|
+
} = await ssrModule.render(url, locale);
|
|
2169
|
+
const serializedData = ssrModule.serializeServerData(serverData);
|
|
2170
|
+
finalHtml = injectSSRForStatic(
|
|
2171
|
+
ctx.templateHtml,
|
|
2172
|
+
locale,
|
|
2173
|
+
head,
|
|
2174
|
+
css,
|
|
2175
|
+
appHtml,
|
|
2176
|
+
serializedData
|
|
2177
|
+
);
|
|
2178
|
+
}
|
|
1890
2179
|
const filePath = url === "/" ? path.join(outputDir, "index.html") : path.join(outputDir, url, "index.html");
|
|
1891
2180
|
fs.mkdirSync(path.resolve(filePath, ".."), {
|
|
1892
2181
|
recursive: true
|
|
@@ -1901,9 +2190,10 @@ function staticAdapter(opts = {}) {
|
|
|
1901
2190
|
}
|
|
1902
2191
|
};
|
|
1903
2192
|
}
|
|
1904
|
-
async function
|
|
2193
|
+
async function extractRoutesWithModes(ctx, opts) {
|
|
1905
2194
|
const routesFile = opts.routesExport ?? "src/lib/bootstrap.ts";
|
|
1906
2195
|
const paths = [];
|
|
2196
|
+
const defs = [];
|
|
1907
2197
|
try {
|
|
1908
2198
|
const { pathToFileURL } = await import(
|
|
1909
2199
|
/* @vite-ignore */
|
|
@@ -1933,6 +2223,7 @@ async function extractRoutes(ctx, opts) {
|
|
|
1933
2223
|
for (const r of routes) {
|
|
1934
2224
|
if (r.path && !r.path.includes(":")) {
|
|
1935
2225
|
paths.push(r.path);
|
|
2226
|
+
defs.push({ path: r.path, renderMode: r.renderMode });
|
|
1936
2227
|
}
|
|
1937
2228
|
}
|
|
1938
2229
|
}
|
|
@@ -1952,7 +2243,7 @@ async function extractRoutes(ctx, opts) {
|
|
|
1952
2243
|
}
|
|
1953
2244
|
}
|
|
1954
2245
|
if (paths.length === 0) paths.push("/");
|
|
1955
|
-
return paths;
|
|
2246
|
+
return { paths, defs };
|
|
1956
2247
|
}
|
|
1957
2248
|
function inferLocale(url, locales, defaultLocale) {
|
|
1958
2249
|
const segments = url.split("/").filter(Boolean);
|
|
@@ -1967,6 +2258,29 @@ function injectSSRForStatic(template, locale, head, css, html, serializedData) {
|
|
|
1967
2258
|
'<script id="serialized-server-data" type="application/json">' + serializedData + "</script>"
|
|
1968
2259
|
);
|
|
1969
2260
|
}
|
|
2261
|
+
function injectCSRShellForStatic(template, locale) {
|
|
2262
|
+
return template.replace("<!--ssr-lang-->", locale).replace("<!--ssr-head-->", "").replace("<!--ssr-body-->", "").replace("<!--ssr-data-->", "");
|
|
2263
|
+
}
|
|
2264
|
+
function stripLocalePrefix(url, locales) {
|
|
2265
|
+
const segments = url.split("/").filter(Boolean);
|
|
2266
|
+
if (segments.length > 0 && locales.includes(segments[0])) {
|
|
2267
|
+
const rest = segments.slice(1).join("/");
|
|
2268
|
+
return rest ? `/${rest}` : "/";
|
|
2269
|
+
}
|
|
2270
|
+
return url;
|
|
2271
|
+
}
|
|
2272
|
+
function resolveRenderMode(routePath, routeRenderMode, renderModes) {
|
|
2273
|
+
if (renderModes) {
|
|
2274
|
+
if (renderModes[routePath]) return renderModes[routePath];
|
|
2275
|
+
for (const [pattern, mode] of Object.entries(renderModes)) {
|
|
2276
|
+
if (pattern.includes("*")) {
|
|
2277
|
+
const re = new RegExp("^" + pattern.replace(/\*/g, ".*") + "$");
|
|
2278
|
+
if (re.test(routePath)) return mode;
|
|
2279
|
+
}
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
return routeRenderMode ?? "ssr";
|
|
2283
|
+
}
|
|
1970
2284
|
|
|
1971
2285
|
// ../server/src/adapters/vercel.ts
|
|
1972
2286
|
function vercelAdapter() {
|
|
@@ -2015,7 +2329,7 @@ function vercelAdapter() {
|
|
|
2015
2329
|
version: 3,
|
|
2016
2330
|
routes: [
|
|
2017
2331
|
{ handle: "filesystem" },
|
|
2018
|
-
{ src: "/(.*)", dest: "/ssr" }
|
|
2332
|
+
{ src: "/(.*)", dest: "/ssr/$1" }
|
|
2019
2333
|
]
|
|
2020
2334
|
},
|
|
2021
2335
|
null,
|
|
@@ -2025,6 +2339,39 @@ function vercelAdapter() {
|
|
|
2025
2339
|
} finally {
|
|
2026
2340
|
fs.rmSync(tempEntry, { force: true });
|
|
2027
2341
|
}
|
|
2342
|
+
const prerendered = await prerenderRoutes(ctx);
|
|
2343
|
+
const staticDir = path.resolve(root, ".vercel/output/static");
|
|
2344
|
+
for (const { url, html } of prerendered) {
|
|
2345
|
+
const filePath = url === "/" ? path.join(staticDir, "index.html") : path.join(staticDir, url, "index.html");
|
|
2346
|
+
fs.mkdirSync(path.resolve(filePath, ".."), { recursive: true });
|
|
2347
|
+
fs.writeFileSync(filePath, html);
|
|
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
|
+
}
|
|
2028
2375
|
console.log(" Vercel output \u2192 .vercel/output/\n");
|
|
2029
2376
|
}
|
|
2030
2377
|
};
|
|
@@ -2297,6 +2644,18 @@ function resolveSetupFn(mod) {
|
|
|
2297
2644
|
const first = Object.values(mod).find((v) => typeof v === "function");
|
|
2298
2645
|
return first ?? null;
|
|
2299
2646
|
}
|
|
2647
|
+
function matchRenderModeConfig(url, renderModes) {
|
|
2648
|
+
if (!renderModes) return null;
|
|
2649
|
+
const path = url.split("?")[0];
|
|
2650
|
+
if (renderModes[path]) return renderModes[path];
|
|
2651
|
+
for (const [pattern, mode] of Object.entries(renderModes)) {
|
|
2652
|
+
if (pattern.includes("*")) {
|
|
2653
|
+
const re = new RegExp("^" + pattern.replace(/\*/g, ".*") + "$");
|
|
2654
|
+
if (re.test(path)) return mode;
|
|
2655
|
+
}
|
|
2656
|
+
}
|
|
2657
|
+
return null;
|
|
2658
|
+
}
|
|
2300
2659
|
function finesoftFrontViteConfig(options = {}) {
|
|
2301
2660
|
const ssrEntry = options.ssr?.entry ?? "src/ssr.ts";
|
|
2302
2661
|
let root = process.cwd();
|
|
@@ -2376,13 +2735,14 @@ function finesoftFrontViteConfig(options = {}) {
|
|
|
2376
2735
|
/* @vite-ignore */
|
|
2377
2736
|
"hono"
|
|
2378
2737
|
);
|
|
2379
|
-
const { injectSSRContent: injectSSRContent2 } = await Promise.resolve().then(() => (init_src2(), src_exports));
|
|
2738
|
+
const { injectSSRContent: injectSSRContent2, injectCSRShell: injectCSRShell2 } = await Promise.resolve().then(() => (init_src2(), src_exports));
|
|
2380
2739
|
const { parseAcceptLanguage: parseAcceptLanguage2 } = await Promise.resolve().then(() => (init_locale(), locale_exports));
|
|
2381
2740
|
const { getRequestListener } = await import(
|
|
2382
2741
|
/* @vite-ignore */
|
|
2383
2742
|
"@hono/node-server"
|
|
2384
2743
|
);
|
|
2385
2744
|
const app = new HonoClass();
|
|
2745
|
+
const isrCache = /* @__PURE__ */ new Map();
|
|
2386
2746
|
if (typeof options.setup === "function") {
|
|
2387
2747
|
await options.setup(app);
|
|
2388
2748
|
} else if (typeof options.setup === "string") {
|
|
@@ -2421,12 +2781,25 @@ function finesoftFrontViteConfig(options = {}) {
|
|
|
2421
2781
|
options.locales,
|
|
2422
2782
|
options.defaultLocale
|
|
2423
2783
|
);
|
|
2784
|
+
const overrideMode = matchRenderModeConfig(
|
|
2785
|
+
url,
|
|
2786
|
+
options.renderModes
|
|
2787
|
+
);
|
|
2788
|
+
if (overrideMode === "csr") {
|
|
2789
|
+
return c.html(injectCSRShell2(template, locale));
|
|
2790
|
+
}
|
|
2791
|
+
const cached = isrCache.get(url);
|
|
2792
|
+
if (cached) return c.html(cached);
|
|
2424
2793
|
const {
|
|
2425
2794
|
html: appHtml,
|
|
2426
2795
|
head,
|
|
2427
2796
|
css,
|
|
2428
|
-
serverData
|
|
2797
|
+
serverData,
|
|
2798
|
+
renderMode
|
|
2429
2799
|
} = await ssrModule.render(url, locale);
|
|
2800
|
+
if (renderMode === "csr") {
|
|
2801
|
+
return c.html(injectCSRShell2(template, locale));
|
|
2802
|
+
}
|
|
2430
2803
|
const serializedData = ssrModule.serializeServerData(serverData);
|
|
2431
2804
|
const finalHtml = injectSSRContent2({
|
|
2432
2805
|
template,
|
|
@@ -2436,6 +2809,9 @@ function finesoftFrontViteConfig(options = {}) {
|
|
|
2436
2809
|
html: appHtml,
|
|
2437
2810
|
serializedData
|
|
2438
2811
|
});
|
|
2812
|
+
if (renderMode === "prerender" || overrideMode === "prerender") {
|
|
2813
|
+
isrCache.set(url, finalHtml);
|
|
2814
|
+
}
|
|
2439
2815
|
return c.html(finalHtml);
|
|
2440
2816
|
} catch (e) {
|
|
2441
2817
|
console.error("[SSR Preview Error]", e);
|
|
@@ -2506,6 +2882,7 @@ function finesoftFrontViteConfig(options = {}) {
|
|
|
2506
2882
|
locales,
|
|
2507
2883
|
defaultLocale,
|
|
2508
2884
|
templateHtml,
|
|
2885
|
+
renderModes: options.renderModes,
|
|
2509
2886
|
resolvedResolve,
|
|
2510
2887
|
resolvedCss,
|
|
2511
2888
|
vite,
|
|
@@ -2565,6 +2942,7 @@ function finesoftFrontViteConfig(options = {}) {
|
|
|
2565
2942
|
finesoftFrontViteConfig,
|
|
2566
2943
|
generateUuid,
|
|
2567
2944
|
getBaseUrl,
|
|
2945
|
+
injectCSRShell,
|
|
2568
2946
|
injectSSRContent,
|
|
2569
2947
|
isCompoundAction,
|
|
2570
2948
|
isExternalUrlAction,
|