@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.js
CHANGED
|
@@ -7,17 +7,18 @@ import {
|
|
|
7
7
|
registerFlowActionHandler,
|
|
8
8
|
startBrowserApp,
|
|
9
9
|
tryScroll
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-H3RNYNSD.js";
|
|
11
11
|
import {
|
|
12
12
|
createSSRApp
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-IITKGRCO.js";
|
|
14
14
|
import {
|
|
15
15
|
SSR_PLACEHOLDERS,
|
|
16
16
|
createSSRRender,
|
|
17
|
+
injectCSRShell,
|
|
17
18
|
injectSSRContent,
|
|
18
19
|
serializeServerData,
|
|
19
20
|
ssrRender
|
|
20
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-5ZECBECP.js";
|
|
21
22
|
import {
|
|
22
23
|
ACTION_KINDS,
|
|
23
24
|
ActionDispatcher,
|
|
@@ -57,7 +58,7 @@ import {
|
|
|
57
58
|
resetFilterCache,
|
|
58
59
|
shouldLog,
|
|
59
60
|
stableStringify
|
|
60
|
-
} from "./chunk-
|
|
61
|
+
} from "./chunk-BUYWNNNQ.js";
|
|
61
62
|
import {
|
|
62
63
|
parseAcceptLanguage
|
|
63
64
|
} from "./chunk-FYP2ZYYV.js";
|
|
@@ -75,6 +76,20 @@ function generateSSREntry(ctx, opts) {
|
|
|
75
76
|
const setupCall = ctx.setupPath ? `if (typeof _setupDefault === "function") await _setupDefault(app);` : ``;
|
|
76
77
|
const locales = JSON.stringify(ctx.locales);
|
|
77
78
|
const defaultLocale = JSON.stringify(ctx.defaultLocale);
|
|
79
|
+
const renderModes = JSON.stringify(ctx.renderModes ?? {});
|
|
80
|
+
const cacheImpl = opts.platformCache ? opts.platformCache : `
|
|
81
|
+
const ISR_CACHE_MAX = 1000;
|
|
82
|
+
const _isrMap = new Map();
|
|
83
|
+
async function platformCacheGet(url) {
|
|
84
|
+
return _isrMap.get(url) ?? null;
|
|
85
|
+
}
|
|
86
|
+
async function platformCacheSet(url, html) {
|
|
87
|
+
if (_isrMap.size >= ISR_CACHE_MAX) {
|
|
88
|
+
const first = _isrMap.keys().next().value;
|
|
89
|
+
_isrMap.delete(first);
|
|
90
|
+
}
|
|
91
|
+
_isrMap.set(url, html);
|
|
92
|
+
}`;
|
|
78
93
|
return `
|
|
79
94
|
import { Hono } from "hono";
|
|
80
95
|
${opts.platformImport}
|
|
@@ -84,6 +99,8 @@ ${setupImport}
|
|
|
84
99
|
const TEMPLATE = ${JSON.stringify(ctx.templateHtml)};
|
|
85
100
|
const LOCALES = ${locales};
|
|
86
101
|
const DEFAULT_LOCALE = ${defaultLocale};
|
|
102
|
+
const RENDER_MODES = ${renderModes};
|
|
103
|
+
${cacheImpl}
|
|
87
104
|
|
|
88
105
|
function parseAcceptLanguage(header) {
|
|
89
106
|
if (!header) return DEFAULT_LOCALE;
|
|
@@ -106,16 +123,62 @@ function injectSSR(t, locale, head, css, html, data) {
|
|
|
106
123
|
.replace("<!--ssr-data-->", '<script id="serialized-server-data" type="application/json">' + data + "</script>");
|
|
107
124
|
}
|
|
108
125
|
|
|
126
|
+
function injectCSRShell(t, locale) {
|
|
127
|
+
return t
|
|
128
|
+
.replace("<!--ssr-lang-->", locale)
|
|
129
|
+
.replace("<!--ssr-head-->", "")
|
|
130
|
+
.replace("<!--ssr-body-->", "")
|
|
131
|
+
.replace("<!--ssr-data-->", "");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function matchRenderMode(url) {
|
|
135
|
+
const path = url.split("?")[0];
|
|
136
|
+
if (RENDER_MODES[path]) return RENDER_MODES[path];
|
|
137
|
+
for (const [pattern, mode] of Object.entries(RENDER_MODES)) {
|
|
138
|
+
if (pattern.includes("*")) {
|
|
139
|
+
const re = new RegExp("^" + pattern.replace(/\\*/g, ".*") + "$");
|
|
140
|
+
if (re.test(path)) return mode;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
|
|
109
146
|
const app = new Hono();
|
|
110
147
|
${setupCall}
|
|
148
|
+
${opts.platformMiddleware ?? ""}
|
|
111
149
|
|
|
112
150
|
app.get("*", async (c) => {
|
|
113
151
|
const url = c.req.path + (c.req.url.includes("?") ? "?" + c.req.url.split("?")[1] : "");
|
|
114
152
|
try {
|
|
115
153
|
const locale = parseAcceptLanguage(c.req.header("accept-language"));
|
|
116
|
-
|
|
154
|
+
|
|
155
|
+
// Vite \u914D\u7F6E\u7EA7\u522B\u8986\u76D6: CSR \u76F4\u63A5\u8FD4\u56DE\u7A7A\u58F3
|
|
156
|
+
const overrideMode = matchRenderMode(url);
|
|
157
|
+
if (overrideMode === "csr") {
|
|
158
|
+
return c.html(injectCSRShell(TEMPLATE, locale));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ISR \u7F13\u5B58\u547D\u4E2D
|
|
162
|
+
const cached = await platformCacheGet(url);
|
|
163
|
+
if (cached) return c.html(cached);
|
|
164
|
+
|
|
165
|
+
const { html: appHtml, head, css, serverData, renderMode } = await render(url, locale);
|
|
166
|
+
|
|
167
|
+
// \u8DEF\u7531\u7EA7 CSR
|
|
168
|
+
if (renderMode === "csr") {
|
|
169
|
+
return c.html(injectCSRShell(TEMPLATE, locale));
|
|
170
|
+
}
|
|
171
|
+
|
|
117
172
|
const serializedData = serializeServerData(serverData);
|
|
118
|
-
|
|
173
|
+
const finalHtml = injectSSR(TEMPLATE, locale, head, css, appHtml, serializedData);
|
|
174
|
+
|
|
175
|
+
// Prerender ISR \u7F13\u5B58\uFF08\u5305\u62EC Vite \u914D\u7F6E\u8986\u76D6\u548C\u8DEF\u7531\u7EA7\uFF09
|
|
176
|
+
if (renderMode === "prerender" || overrideMode === "prerender") {
|
|
177
|
+
await platformCacheSet(url, finalHtml);
|
|
178
|
+
${opts.platformPrerenderResponseHook ?? ""}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return c.html(finalHtml);
|
|
119
182
|
} catch (e) {
|
|
120
183
|
console.error("[SSR Error]", e);
|
|
121
184
|
return c.text("Internal Server Error", 500);
|
|
@@ -154,6 +217,89 @@ function copyStaticAssets(ctx, destDir, opts) {
|
|
|
154
217
|
fs.rmSync(path.join(destDir, "index.html"), { force: true });
|
|
155
218
|
}
|
|
156
219
|
}
|
|
220
|
+
async function prerenderRoutes(ctx, routesExport = "src/lib/bootstrap.ts") {
|
|
221
|
+
const { fs, path, root, vite } = ctx;
|
|
222
|
+
const { pathToFileURL } = await import(
|
|
223
|
+
/* @vite-ignore */
|
|
224
|
+
"url"
|
|
225
|
+
);
|
|
226
|
+
await vite.build({
|
|
227
|
+
root,
|
|
228
|
+
build: {
|
|
229
|
+
ssr: routesExport,
|
|
230
|
+
outDir: path.resolve(root, "dist/server"),
|
|
231
|
+
emptyOutDir: false,
|
|
232
|
+
rollupOptions: {
|
|
233
|
+
output: { entryFileNames: "_routes_prerender.mjs" }
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
resolve: ctx.resolvedResolve
|
|
237
|
+
});
|
|
238
|
+
const routesPath = pathToFileURL(
|
|
239
|
+
path.resolve(root, "dist/server/_routes_prerender.mjs")
|
|
240
|
+
).href;
|
|
241
|
+
const routesMod = await import(
|
|
242
|
+
/* @vite-ignore */
|
|
243
|
+
routesPath
|
|
244
|
+
);
|
|
245
|
+
const routes = routesMod.routes ?? routesMod.default ?? [];
|
|
246
|
+
fs.rmSync(path.resolve(root, "dist/server/_routes_prerender.mjs"), {
|
|
247
|
+
force: true
|
|
248
|
+
});
|
|
249
|
+
const prerenderPaths = /* @__PURE__ */ new Set();
|
|
250
|
+
for (const r of routes) {
|
|
251
|
+
if (r.renderMode === "prerender" && r.path && !r.path.includes(":")) {
|
|
252
|
+
prerenderPaths.add(r.path);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
if (ctx.renderModes) {
|
|
256
|
+
for (const [pattern, mode] of Object.entries(ctx.renderModes)) {
|
|
257
|
+
if (mode === "prerender" && !pattern.includes("*") && !pattern.includes(":")) {
|
|
258
|
+
prerenderPaths.add(pattern);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (prerenderPaths.size === 0) return [];
|
|
263
|
+
const ssrPath = pathToFileURL(
|
|
264
|
+
path.resolve(root, "dist/server/ssr.js")
|
|
265
|
+
).href;
|
|
266
|
+
const ssrModule = await import(
|
|
267
|
+
/* @vite-ignore */
|
|
268
|
+
ssrPath
|
|
269
|
+
);
|
|
270
|
+
const results = [];
|
|
271
|
+
for (const routePath of prerenderPaths) {
|
|
272
|
+
for (const locale of ctx.locales) {
|
|
273
|
+
const url = locale === ctx.defaultLocale ? routePath : `/${locale}${routePath === "/" ? "" : routePath}`;
|
|
274
|
+
try {
|
|
275
|
+
const {
|
|
276
|
+
html: appHtml,
|
|
277
|
+
head,
|
|
278
|
+
css,
|
|
279
|
+
serverData
|
|
280
|
+
} = await ssrModule.render(url, locale);
|
|
281
|
+
const serializedData = ssrModule.serializeServerData(serverData);
|
|
282
|
+
const finalHtml = ctx.templateHtml.replace("<!--ssr-lang-->", locale).replace(
|
|
283
|
+
"<!--ssr-head-->",
|
|
284
|
+
head + "\n<style>" + css + "</style>"
|
|
285
|
+
).replace("<!--ssr-body-->", appHtml).replace(
|
|
286
|
+
"<!--ssr-data-->",
|
|
287
|
+
'<script id="serialized-server-data" type="application/json">' + serializedData + "</script>"
|
|
288
|
+
);
|
|
289
|
+
results.push({ url, html: finalHtml });
|
|
290
|
+
} catch (e) {
|
|
291
|
+
console.warn(` [prerender] Failed to render ${url}:`, e);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (results.length > 0) {
|
|
296
|
+
console.log(
|
|
297
|
+
` Pre-rendered ${results.length} pages (${prerenderPaths.size} routes \xD7 ${ctx.locales.length} locales)
|
|
298
|
+
`
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
return results;
|
|
302
|
+
}
|
|
157
303
|
|
|
158
304
|
// ../server/src/adapters/cloudflare.ts
|
|
159
305
|
function cloudflareAdapter() {
|
|
@@ -165,7 +311,29 @@ function cloudflareAdapter() {
|
|
|
165
311
|
fs.rmSync(outputDir, { recursive: true, force: true });
|
|
166
312
|
const entrySource = generateSSREntry(ctx, {
|
|
167
313
|
platformImport: ``,
|
|
168
|
-
platformExport: `export default app
|
|
314
|
+
platformExport: `export default app;`,
|
|
315
|
+
// Cloudflare Cache API — 持久化 ISR 缓存到 CDN 边缘节点
|
|
316
|
+
platformCache: `
|
|
317
|
+
const ISR_CACHE_TTL = 3600; // 1 hour
|
|
318
|
+
async function platformCacheGet(url) {
|
|
319
|
+
try {
|
|
320
|
+
const cache = caches.default;
|
|
321
|
+
const cacheKey = new Request("https://isr-cache/" + encodeURIComponent(url));
|
|
322
|
+
const resp = await cache.match(cacheKey);
|
|
323
|
+
if (resp) return await resp.text();
|
|
324
|
+
} catch {}
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
async function platformCacheSet(url, html) {
|
|
328
|
+
try {
|
|
329
|
+
const cache = caches.default;
|
|
330
|
+
const cacheKey = new Request("https://isr-cache/" + encodeURIComponent(url));
|
|
331
|
+
const resp = new Response(html, {
|
|
332
|
+
headers: { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "public, max-age=" + ISR_CACHE_TTL },
|
|
333
|
+
});
|
|
334
|
+
await cache.put(cacheKey, resp);
|
|
335
|
+
} catch {}
|
|
336
|
+
}`
|
|
169
337
|
});
|
|
170
338
|
const tempEntry = path.resolve(root, ".cf-entry.tmp.mjs");
|
|
171
339
|
fs.writeFileSync(tempEntry, entrySource);
|
|
@@ -179,6 +347,14 @@ function cloudflareAdapter() {
|
|
|
179
347
|
// 这些构建工具运行时不需要,且 fsevents 是 macOS .node 原生二进制无法打包
|
|
180
348
|
});
|
|
181
349
|
copyStaticAssets(ctx, path.resolve(outputDir, "assets"));
|
|
350
|
+
const prerendered = await prerenderRoutes(ctx);
|
|
351
|
+
for (const { url, html } of prerendered) {
|
|
352
|
+
const filePath = url === "/" ? path.join(outputDir, "assets", "index.html") : path.join(outputDir, "assets", url, "index.html");
|
|
353
|
+
fs.mkdirSync(path.resolve(filePath, ".."), {
|
|
354
|
+
recursive: true
|
|
355
|
+
});
|
|
356
|
+
fs.writeFileSync(filePath, html);
|
|
357
|
+
}
|
|
182
358
|
} finally {
|
|
183
359
|
fs.rmSync(tempEntry, { force: true });
|
|
184
360
|
}
|
|
@@ -203,7 +379,25 @@ function netlifyAdapter() {
|
|
|
203
379
|
});
|
|
204
380
|
const entrySource = generateSSREntry(ctx, {
|
|
205
381
|
platformImport: `import { handle } from "hono/netlify";`,
|
|
206
|
-
platformExport: `export default handle(app)
|
|
382
|
+
platformExport: `export default handle(app);
|
|
383
|
+
export const config = { path: "/*", preferStatic: true };`,
|
|
384
|
+
// Netlify CDN 缓存 — 使用 Netlify-CDN-Cache-Control 头做 ISR
|
|
385
|
+
platformCache: `
|
|
386
|
+
const ISR_SWR_TTL = 3600;
|
|
387
|
+
const ISR_CACHE_MAX = 1000;
|
|
388
|
+
const _isrMap = new Map();
|
|
389
|
+
async function platformCacheGet(url) {
|
|
390
|
+
return _isrMap.get(url) ?? null;
|
|
391
|
+
}
|
|
392
|
+
async function platformCacheSet(url, html) {
|
|
393
|
+
if (_isrMap.size >= ISR_CACHE_MAX) {
|
|
394
|
+
const first = _isrMap.keys().next().value;
|
|
395
|
+
_isrMap.delete(first);
|
|
396
|
+
}
|
|
397
|
+
_isrMap.set(url, html);
|
|
398
|
+
}`,
|
|
399
|
+
platformPrerenderResponseHook: `c.header("Cache-Control", "public, max-age=0, must-revalidate");
|
|
400
|
+
c.header("Netlify-CDN-Cache-Control", "public, max-age=" + ISR_SWR_TTL + ", stale-while-revalidate=" + ISR_SWR_TTL + ", durable");`
|
|
207
401
|
});
|
|
208
402
|
const tempEntry = path.resolve(root, ".netlify-entry.tmp.mjs");
|
|
209
403
|
fs.writeFileSync(tempEntry, entrySource);
|
|
@@ -222,6 +416,13 @@ function netlifyAdapter() {
|
|
|
222
416
|
path.resolve(root, "dist/client/_redirects"),
|
|
223
417
|
redirects
|
|
224
418
|
);
|
|
419
|
+
const prerendered = await prerenderRoutes(ctx);
|
|
420
|
+
const clientDir = path.resolve(root, "dist/client");
|
|
421
|
+
for (const { url, html } of prerendered) {
|
|
422
|
+
const filePath = url === "/" ? path.join(clientDir, "index.html") : path.join(clientDir, url, "index.html");
|
|
423
|
+
fs.mkdirSync(path.resolve(filePath, ".."), { recursive: true });
|
|
424
|
+
fs.writeFileSync(filePath, html);
|
|
425
|
+
}
|
|
225
426
|
console.log(
|
|
226
427
|
" Netlify output \u2192 .netlify/functions-internal/ssr/\n Publish dir: dist/client/\n"
|
|
227
428
|
);
|
|
@@ -236,7 +437,31 @@ function nodeAdapter() {
|
|
|
236
437
|
async build(ctx) {
|
|
237
438
|
const { fs, path, root } = ctx;
|
|
238
439
|
const entrySource = generateSSREntry(ctx, {
|
|
239
|
-
platformImport: `import { serve } from "@hono/node-server"
|
|
440
|
+
platformImport: `import { serve } from "@hono/node-server";
|
|
441
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
442
|
+
import { resolve, dirname } from "node:path";
|
|
443
|
+
import { fileURLToPath } from "node:url";`,
|
|
444
|
+
platformMiddleware: `
|
|
445
|
+
// \u9884\u6E32\u67D3\u6587\u4EF6\u4E2D\u95F4\u4EF6\uFF1A\u68C0\u67E5 dist/prerender/ \u4E0B\u662F\u5426\u6709\u5BF9\u5E94\u7684\u9759\u6001 HTML
|
|
446
|
+
const __entry_dirname = dirname(fileURLToPath(import.meta.url));
|
|
447
|
+
const prerenderDir = resolve(__entry_dirname, "../prerender");
|
|
448
|
+
|
|
449
|
+
app.use("*", async (c, next) => {
|
|
450
|
+
const urlPath = c.req.path;
|
|
451
|
+
const candidates = [
|
|
452
|
+
resolve(prerenderDir, "." + urlPath, "index.html"),
|
|
453
|
+
resolve(prerenderDir, "." + urlPath + ".html"),
|
|
454
|
+
];
|
|
455
|
+
if (urlPath === "/") candidates.unshift(resolve(prerenderDir, "index.html"));
|
|
456
|
+
for (const f of candidates) {
|
|
457
|
+
if (existsSync(f)) {
|
|
458
|
+
const html = readFileSync(f, "utf-8");
|
|
459
|
+
return c.html(html);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
await next();
|
|
463
|
+
});
|
|
464
|
+
`,
|
|
240
465
|
platformExport: `
|
|
241
466
|
const port = +(process.env.PORT || 3000);
|
|
242
467
|
serve({ fetch: app.fetch, port }, (info) => {
|
|
@@ -255,6 +480,18 @@ serve({ fetch: app.fetch, port }, (info) => {
|
|
|
255
480
|
} finally {
|
|
256
481
|
fs.rmSync(tempEntry, { force: true });
|
|
257
482
|
}
|
|
483
|
+
const prerendered = await prerenderRoutes(ctx);
|
|
484
|
+
if (prerendered.length > 0) {
|
|
485
|
+
const prerenderDir = path.resolve(root, "dist/prerender");
|
|
486
|
+
fs.mkdirSync(prerenderDir, { recursive: true });
|
|
487
|
+
for (const { url, html } of prerendered) {
|
|
488
|
+
const filePath = url === "/" ? path.join(prerenderDir, "index.html") : path.join(prerenderDir, url, "index.html");
|
|
489
|
+
fs.mkdirSync(path.resolve(filePath, ".."), {
|
|
490
|
+
recursive: true
|
|
491
|
+
});
|
|
492
|
+
fs.writeFileSync(filePath, html);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
258
495
|
console.log(
|
|
259
496
|
" Node output \u2192 dist/server/index.mjs\n Run: node dist/server/index.mjs\n"
|
|
260
497
|
);
|
|
@@ -283,7 +520,7 @@ function staticAdapter(opts = {}) {
|
|
|
283
520
|
ssrPath
|
|
284
521
|
);
|
|
285
522
|
ctx.copyStaticAssets(outputDir, { excludeHtml: true });
|
|
286
|
-
const routePaths = await
|
|
523
|
+
const { paths: routePaths, defs: routeDefs } = await extractRoutesWithModes(ctx, opts);
|
|
287
524
|
const allUrls = [];
|
|
288
525
|
for (const routePath of routePaths) {
|
|
289
526
|
for (const locale of ctx.locales) {
|
|
@@ -302,21 +539,37 @@ function staticAdapter(opts = {}) {
|
|
|
302
539
|
ctx.locales,
|
|
303
540
|
ctx.defaultLocale
|
|
304
541
|
);
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
const finalHtml = injectSSRForStatic(
|
|
313
|
-
ctx.templateHtml,
|
|
314
|
-
locale,
|
|
315
|
-
head,
|
|
316
|
-
css,
|
|
317
|
-
appHtml,
|
|
318
|
-
serializedData
|
|
542
|
+
const routeDef = routeDefs.find(
|
|
543
|
+
(r) => r.path === stripLocalePrefix(url, ctx.locales)
|
|
544
|
+
);
|
|
545
|
+
const mode = resolveRenderMode(
|
|
546
|
+
stripLocalePrefix(url, ctx.locales),
|
|
547
|
+
routeDef?.renderMode,
|
|
548
|
+
ctx.renderModes
|
|
319
549
|
);
|
|
550
|
+
let finalHtml;
|
|
551
|
+
if (mode === "csr") {
|
|
552
|
+
finalHtml = injectCSRShellForStatic(
|
|
553
|
+
ctx.templateHtml,
|
|
554
|
+
locale
|
|
555
|
+
);
|
|
556
|
+
} else {
|
|
557
|
+
const {
|
|
558
|
+
html: appHtml,
|
|
559
|
+
head,
|
|
560
|
+
css,
|
|
561
|
+
serverData
|
|
562
|
+
} = await ssrModule.render(url, locale);
|
|
563
|
+
const serializedData = ssrModule.serializeServerData(serverData);
|
|
564
|
+
finalHtml = injectSSRForStatic(
|
|
565
|
+
ctx.templateHtml,
|
|
566
|
+
locale,
|
|
567
|
+
head,
|
|
568
|
+
css,
|
|
569
|
+
appHtml,
|
|
570
|
+
serializedData
|
|
571
|
+
);
|
|
572
|
+
}
|
|
320
573
|
const filePath = url === "/" ? path.join(outputDir, "index.html") : path.join(outputDir, url, "index.html");
|
|
321
574
|
fs.mkdirSync(path.resolve(filePath, ".."), {
|
|
322
575
|
recursive: true
|
|
@@ -331,9 +584,10 @@ function staticAdapter(opts = {}) {
|
|
|
331
584
|
}
|
|
332
585
|
};
|
|
333
586
|
}
|
|
334
|
-
async function
|
|
587
|
+
async function extractRoutesWithModes(ctx, opts) {
|
|
335
588
|
const routesFile = opts.routesExport ?? "src/lib/bootstrap.ts";
|
|
336
589
|
const paths = [];
|
|
590
|
+
const defs = [];
|
|
337
591
|
try {
|
|
338
592
|
const { pathToFileURL } = await import(
|
|
339
593
|
/* @vite-ignore */
|
|
@@ -363,6 +617,7 @@ async function extractRoutes(ctx, opts) {
|
|
|
363
617
|
for (const r of routes) {
|
|
364
618
|
if (r.path && !r.path.includes(":")) {
|
|
365
619
|
paths.push(r.path);
|
|
620
|
+
defs.push({ path: r.path, renderMode: r.renderMode });
|
|
366
621
|
}
|
|
367
622
|
}
|
|
368
623
|
}
|
|
@@ -382,7 +637,7 @@ async function extractRoutes(ctx, opts) {
|
|
|
382
637
|
}
|
|
383
638
|
}
|
|
384
639
|
if (paths.length === 0) paths.push("/");
|
|
385
|
-
return paths;
|
|
640
|
+
return { paths, defs };
|
|
386
641
|
}
|
|
387
642
|
function inferLocale(url, locales, defaultLocale) {
|
|
388
643
|
const segments = url.split("/").filter(Boolean);
|
|
@@ -397,6 +652,29 @@ function injectSSRForStatic(template, locale, head, css, html, serializedData) {
|
|
|
397
652
|
'<script id="serialized-server-data" type="application/json">' + serializedData + "</script>"
|
|
398
653
|
);
|
|
399
654
|
}
|
|
655
|
+
function injectCSRShellForStatic(template, locale) {
|
|
656
|
+
return template.replace("<!--ssr-lang-->", locale).replace("<!--ssr-head-->", "").replace("<!--ssr-body-->", "").replace("<!--ssr-data-->", "");
|
|
657
|
+
}
|
|
658
|
+
function stripLocalePrefix(url, locales) {
|
|
659
|
+
const segments = url.split("/").filter(Boolean);
|
|
660
|
+
if (segments.length > 0 && locales.includes(segments[0])) {
|
|
661
|
+
const rest = segments.slice(1).join("/");
|
|
662
|
+
return rest ? `/${rest}` : "/";
|
|
663
|
+
}
|
|
664
|
+
return url;
|
|
665
|
+
}
|
|
666
|
+
function resolveRenderMode(routePath, routeRenderMode, renderModes) {
|
|
667
|
+
if (renderModes) {
|
|
668
|
+
if (renderModes[routePath]) return renderModes[routePath];
|
|
669
|
+
for (const [pattern, mode] of Object.entries(renderModes)) {
|
|
670
|
+
if (pattern.includes("*")) {
|
|
671
|
+
const re = new RegExp("^" + pattern.replace(/\*/g, ".*") + "$");
|
|
672
|
+
if (re.test(routePath)) return mode;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
return routeRenderMode ?? "ssr";
|
|
677
|
+
}
|
|
400
678
|
|
|
401
679
|
// ../server/src/adapters/vercel.ts
|
|
402
680
|
function vercelAdapter() {
|
|
@@ -445,7 +723,7 @@ function vercelAdapter() {
|
|
|
445
723
|
version: 3,
|
|
446
724
|
routes: [
|
|
447
725
|
{ handle: "filesystem" },
|
|
448
|
-
{ src: "/(.*)", dest: "/ssr" }
|
|
726
|
+
{ src: "/(.*)", dest: "/ssr/$1" }
|
|
449
727
|
]
|
|
450
728
|
},
|
|
451
729
|
null,
|
|
@@ -455,6 +733,39 @@ function vercelAdapter() {
|
|
|
455
733
|
} finally {
|
|
456
734
|
fs.rmSync(tempEntry, { force: true });
|
|
457
735
|
}
|
|
736
|
+
const prerendered = await prerenderRoutes(ctx);
|
|
737
|
+
const staticDir = path.resolve(root, ".vercel/output/static");
|
|
738
|
+
for (const { url, html } of prerendered) {
|
|
739
|
+
const filePath = url === "/" ? path.join(staticDir, "index.html") : path.join(staticDir, url, "index.html");
|
|
740
|
+
fs.mkdirSync(path.resolve(filePath, ".."), { recursive: true });
|
|
741
|
+
fs.writeFileSync(filePath, html);
|
|
742
|
+
}
|
|
743
|
+
if (prerendered.length > 0) {
|
|
744
|
+
const configPath = path.resolve(
|
|
745
|
+
root,
|
|
746
|
+
".vercel/output/config.json"
|
|
747
|
+
);
|
|
748
|
+
const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
749
|
+
config.overrides = config.overrides ?? {};
|
|
750
|
+
for (const { url } of prerendered) {
|
|
751
|
+
const key = url === "/" ? "index.html" : `${url.replace(/^\//, "")}/index.html`;
|
|
752
|
+
config.overrides[key] = {
|
|
753
|
+
path: url === "/" ? "/" : url,
|
|
754
|
+
contentType: "text/html; charset=utf-8"
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
const isrRoutes = prerendered.map(({ url }) => ({
|
|
758
|
+
src: `^${url.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}/?$`,
|
|
759
|
+
dest: url,
|
|
760
|
+
has: [{ type: "header", key: "x-vercel-isr" }]
|
|
761
|
+
}));
|
|
762
|
+
config.routes = [
|
|
763
|
+
...isrRoutes,
|
|
764
|
+
{ handle: "filesystem" },
|
|
765
|
+
{ src: "/(.*)", dest: "/ssr/$1" }
|
|
766
|
+
];
|
|
767
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
768
|
+
}
|
|
458
769
|
console.log(" Vercel output \u2192 .vercel/output/\n");
|
|
459
770
|
}
|
|
460
771
|
};
|
|
@@ -720,6 +1031,18 @@ function resolveSetupFn(mod) {
|
|
|
720
1031
|
const first = Object.values(mod).find((v) => typeof v === "function");
|
|
721
1032
|
return first ?? null;
|
|
722
1033
|
}
|
|
1034
|
+
function matchRenderModeConfig(url, renderModes) {
|
|
1035
|
+
if (!renderModes) return null;
|
|
1036
|
+
const path = url.split("?")[0];
|
|
1037
|
+
if (renderModes[path]) return renderModes[path];
|
|
1038
|
+
for (const [pattern, mode] of Object.entries(renderModes)) {
|
|
1039
|
+
if (pattern.includes("*")) {
|
|
1040
|
+
const re = new RegExp("^" + pattern.replace(/\*/g, ".*") + "$");
|
|
1041
|
+
if (re.test(path)) return mode;
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
return null;
|
|
1045
|
+
}
|
|
723
1046
|
function finesoftFrontViteConfig(options = {}) {
|
|
724
1047
|
const ssrEntry = options.ssr?.entry ?? "src/ssr.ts";
|
|
725
1048
|
let root = process.cwd();
|
|
@@ -752,7 +1075,7 @@ function finesoftFrontViteConfig(options = {}) {
|
|
|
752
1075
|
/* @vite-ignore */
|
|
753
1076
|
"hono"
|
|
754
1077
|
);
|
|
755
|
-
const { createSSRApp: createSSRApp2 } = await import("./app-
|
|
1078
|
+
const { createSSRApp: createSSRApp2 } = await import("./app-BPO26FAD.js");
|
|
756
1079
|
const { getRequestListener } = await import(
|
|
757
1080
|
/* @vite-ignore */
|
|
758
1081
|
"@hono/node-server"
|
|
@@ -799,9 +1122,9 @@ function finesoftFrontViteConfig(options = {}) {
|
|
|
799
1122
|
/* @vite-ignore */
|
|
800
1123
|
"hono"
|
|
801
1124
|
);
|
|
802
|
-
const { injectSSRContent: injectSSRContent2 } = await import(
|
|
1125
|
+
const { injectSSRContent: injectSSRContent2, injectCSRShell: injectCSRShell2 } = await import(
|
|
803
1126
|
/* @vite-ignore */
|
|
804
|
-
"./src-
|
|
1127
|
+
"./src-KUWACLPD.js"
|
|
805
1128
|
);
|
|
806
1129
|
const { parseAcceptLanguage: parseAcceptLanguage2 } = await import("./locale-YK3THSI6.js");
|
|
807
1130
|
const { getRequestListener } = await import(
|
|
@@ -809,6 +1132,7 @@ function finesoftFrontViteConfig(options = {}) {
|
|
|
809
1132
|
"@hono/node-server"
|
|
810
1133
|
);
|
|
811
1134
|
const app = new HonoClass();
|
|
1135
|
+
const isrCache = /* @__PURE__ */ new Map();
|
|
812
1136
|
if (typeof options.setup === "function") {
|
|
813
1137
|
await options.setup(app);
|
|
814
1138
|
} else if (typeof options.setup === "string") {
|
|
@@ -847,12 +1171,25 @@ function finesoftFrontViteConfig(options = {}) {
|
|
|
847
1171
|
options.locales,
|
|
848
1172
|
options.defaultLocale
|
|
849
1173
|
);
|
|
1174
|
+
const overrideMode = matchRenderModeConfig(
|
|
1175
|
+
url,
|
|
1176
|
+
options.renderModes
|
|
1177
|
+
);
|
|
1178
|
+
if (overrideMode === "csr") {
|
|
1179
|
+
return c.html(injectCSRShell2(template, locale));
|
|
1180
|
+
}
|
|
1181
|
+
const cached = isrCache.get(url);
|
|
1182
|
+
if (cached) return c.html(cached);
|
|
850
1183
|
const {
|
|
851
1184
|
html: appHtml,
|
|
852
1185
|
head,
|
|
853
1186
|
css,
|
|
854
|
-
serverData
|
|
1187
|
+
serverData,
|
|
1188
|
+
renderMode
|
|
855
1189
|
} = await ssrModule.render(url, locale);
|
|
1190
|
+
if (renderMode === "csr") {
|
|
1191
|
+
return c.html(injectCSRShell2(template, locale));
|
|
1192
|
+
}
|
|
856
1193
|
const serializedData = ssrModule.serializeServerData(serverData);
|
|
857
1194
|
const finalHtml = injectSSRContent2({
|
|
858
1195
|
template,
|
|
@@ -862,6 +1199,9 @@ function finesoftFrontViteConfig(options = {}) {
|
|
|
862
1199
|
html: appHtml,
|
|
863
1200
|
serializedData
|
|
864
1201
|
});
|
|
1202
|
+
if (renderMode === "prerender" || overrideMode === "prerender") {
|
|
1203
|
+
isrCache.set(url, finalHtml);
|
|
1204
|
+
}
|
|
865
1205
|
return c.html(finalHtml);
|
|
866
1206
|
} catch (e) {
|
|
867
1207
|
console.error("[SSR Preview Error]", e);
|
|
@@ -932,6 +1272,7 @@ function finesoftFrontViteConfig(options = {}) {
|
|
|
932
1272
|
locales,
|
|
933
1273
|
defaultLocale,
|
|
934
1274
|
templateHtml,
|
|
1275
|
+
renderModes: options.renderModes,
|
|
935
1276
|
resolvedResolve,
|
|
936
1277
|
resolvedCss,
|
|
937
1278
|
vite,
|
|
@@ -990,6 +1331,7 @@ export {
|
|
|
990
1331
|
finesoftFrontViteConfig,
|
|
991
1332
|
generateUuid,
|
|
992
1333
|
getBaseUrl,
|
|
1334
|
+
injectCSRShell,
|
|
993
1335
|
injectSSRContent,
|
|
994
1336
|
isCompoundAction,
|
|
995
1337
|
isExternalUrlAction,
|