@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/index.js CHANGED
@@ -7,17 +7,18 @@ import {
7
7
  registerFlowActionHandler,
8
8
  startBrowserApp,
9
9
  tryScroll
10
- } from "./chunk-T2AQHAYK.js";
10
+ } from "./chunk-H3RNYNSD.js";
11
11
  import {
12
12
  createSSRApp
13
- } from "./chunk-Z4MHYAS3.js";
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-2VETWTN5.js";
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-RVKDILGM.js";
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
- const { html: appHtml, head, css, serverData } = await render(url, locale);
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
- return c.html(injectSSR(TEMPLATE, locale, head, css, appHtml, serializedData));
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 extractRoutes(ctx, opts);
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
- html: appHtml,
307
- head,
308
- css,
309
- serverData
310
- } = await ssrModule.render(url, locale);
311
- const serializedData = ssrModule.serializeServerData(serverData);
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 extractRoutes(ctx, opts) {
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-MYBG3TGV.js");
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-7D236CLJ.js"
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,