@finesoft/front 0.1.18 → 0.1.20

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.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,7 @@ function createSSRApp(options) {
1132
1149
  defaultLocale
1133
1150
  } = options;
1134
1151
  const app = new import_hono.Hono();
1152
+ const isrCache = /* @__PURE__ */ new Map();
1135
1153
  async function readTemplate(url) {
1136
1154
  if (!isProduction && vite) {
1137
1155
  const { readFileSync: readFileSync2 } = await import(
@@ -1182,6 +1200,9 @@ function createSSRApp(options) {
1182
1200
  app.get("*", async (c) => {
1183
1201
  const url = c.req.path + (c.req.url.includes("?") ? "?" + c.req.url.split("?")[1] : "");
1184
1202
  try {
1203
+ const cacheKey = url;
1204
+ const cached = isrCache.get(cacheKey);
1205
+ if (cached) return c.html(cached);
1185
1206
  const template = await readTemplate(url);
1186
1207
  const { render, serializeServerData: serializeServerData2 } = await loadSSRModule();
1187
1208
  const locale = parseAcceptLanguage(
@@ -1193,8 +1214,12 @@ function createSSRApp(options) {
1193
1214
  html: appHtml,
1194
1215
  head,
1195
1216
  css,
1196
- serverData
1217
+ serverData,
1218
+ renderMode
1197
1219
  } = await render(url, locale);
1220
+ if (renderMode === "csr") {
1221
+ return c.html(injectCSRShell(template, locale));
1222
+ }
1198
1223
  const serializedData = serializeServerData2(serverData);
1199
1224
  const finalHtml = injectSSRContent({
1200
1225
  template,
@@ -1204,6 +1229,9 @@ function createSSRApp(options) {
1204
1229
  html: appHtml,
1205
1230
  serializedData
1206
1231
  });
1232
+ if (renderMode === "prerender") {
1233
+ isrCache.set(cacheKey, finalHtml);
1234
+ }
1207
1235
  return c.html(finalHtml);
1208
1236
  } catch (e) {
1209
1237
  if (!isProduction && vite) {
@@ -1261,6 +1289,7 @@ __export(index_exports, {
1261
1289
  finesoftFrontViteConfig: () => finesoftFrontViteConfig,
1262
1290
  generateUuid: () => generateUuid,
1263
1291
  getBaseUrl: () => getBaseUrl,
1292
+ injectCSRShell: () => injectCSRShell,
1264
1293
  injectSSRContent: () => injectSSRContent,
1265
1294
  isCompoundAction: () => isCompoundAction,
1266
1295
  isExternalUrlAction: () => isExternalUrlAction,
@@ -1645,6 +1674,7 @@ function generateSSREntry(ctx, opts) {
1645
1674
  const setupCall = ctx.setupPath ? `if (typeof _setupDefault === "function") await _setupDefault(app);` : ``;
1646
1675
  const locales = JSON.stringify(ctx.locales);
1647
1676
  const defaultLocale = JSON.stringify(ctx.defaultLocale);
1677
+ const renderModes = JSON.stringify(ctx.renderModes ?? {});
1648
1678
  return `
1649
1679
  import { Hono } from "hono";
1650
1680
  ${opts.platformImport}
@@ -1654,6 +1684,7 @@ ${setupImport}
1654
1684
  const TEMPLATE = ${JSON.stringify(ctx.templateHtml)};
1655
1685
  const LOCALES = ${locales};
1656
1686
  const DEFAULT_LOCALE = ${defaultLocale};
1687
+ const RENDER_MODES = ${renderModes};
1657
1688
 
1658
1689
  function parseAcceptLanguage(header) {
1659
1690
  if (!header) return DEFAULT_LOCALE;
@@ -1676,6 +1707,28 @@ function injectSSR(t, locale, head, css, html, data) {
1676
1707
  .replace("<!--ssr-data-->", '<script id="serialized-server-data" type="application/json">' + data + "</script>");
1677
1708
  }
1678
1709
 
1710
+ function injectCSRShell(t, locale) {
1711
+ return t
1712
+ .replace("<!--ssr-lang-->", locale)
1713
+ .replace("<!--ssr-head-->", "")
1714
+ .replace("<!--ssr-body-->", "")
1715
+ .replace("<!--ssr-data-->", "");
1716
+ }
1717
+
1718
+ function matchRenderMode(url) {
1719
+ const path = url.split("?")[0];
1720
+ if (RENDER_MODES[path]) return RENDER_MODES[path];
1721
+ for (const [pattern, mode] of Object.entries(RENDER_MODES)) {
1722
+ if (pattern.includes("*")) {
1723
+ const re = new RegExp("^" + pattern.replace(/\\*/g, ".*") + "$");
1724
+ if (re.test(path)) return mode;
1725
+ }
1726
+ }
1727
+ return null;
1728
+ }
1729
+
1730
+ const isrCache = new Map();
1731
+
1679
1732
  const app = new Hono();
1680
1733
  ${setupCall}
1681
1734
 
@@ -1683,9 +1736,33 @@ app.get("*", async (c) => {
1683
1736
  const url = c.req.path + (c.req.url.includes("?") ? "?" + c.req.url.split("?")[1] : "");
1684
1737
  try {
1685
1738
  const locale = parseAcceptLanguage(c.req.header("accept-language"));
1686
- const { html: appHtml, head, css, serverData } = await render(url, locale);
1739
+
1740
+ // Vite \u914D\u7F6E\u7EA7\u522B\u8986\u76D6: CSR \u76F4\u63A5\u8FD4\u56DE\u7A7A\u58F3
1741
+ const overrideMode = matchRenderMode(url);
1742
+ if (overrideMode === "csr") {
1743
+ return c.html(injectCSRShell(TEMPLATE, locale));
1744
+ }
1745
+
1746
+ // ISR \u7F13\u5B58\u547D\u4E2D
1747
+ const cached = isrCache.get(url);
1748
+ if (cached) return c.html(cached);
1749
+
1750
+ const { html: appHtml, head, css, serverData, renderMode } = await render(url, locale);
1751
+
1752
+ // \u8DEF\u7531\u7EA7 CSR
1753
+ if (renderMode === "csr") {
1754
+ return c.html(injectCSRShell(TEMPLATE, locale));
1755
+ }
1756
+
1687
1757
  const serializedData = serializeServerData(serverData);
1688
- return c.html(injectSSR(TEMPLATE, locale, head, css, appHtml, serializedData));
1758
+ const finalHtml = injectSSR(TEMPLATE, locale, head, css, appHtml, serializedData);
1759
+
1760
+ // Prerender ISR \u7F13\u5B58\uFF08\u5305\u62EC Vite \u914D\u7F6E\u8986\u76D6\u548C\u8DEF\u7531\u7EA7\uFF09
1761
+ if (renderMode === "prerender" || overrideMode === "prerender") {
1762
+ isrCache.set(url, finalHtml);
1763
+ }
1764
+
1765
+ return c.html(finalHtml);
1689
1766
  } catch (e) {
1690
1767
  console.error("[SSR Error]", e);
1691
1768
  return c.text("Internal Server Error", 500);
@@ -1724,6 +1801,89 @@ function copyStaticAssets(ctx, destDir, opts) {
1724
1801
  fs.rmSync(path.join(destDir, "index.html"), { force: true });
1725
1802
  }
1726
1803
  }
1804
+ async function prerenderRoutes(ctx, routesExport = "src/lib/bootstrap.ts") {
1805
+ const { fs, path, root, vite } = ctx;
1806
+ const { pathToFileURL } = await import(
1807
+ /* @vite-ignore */
1808
+ "url"
1809
+ );
1810
+ await vite.build({
1811
+ root,
1812
+ build: {
1813
+ ssr: routesExport,
1814
+ outDir: path.resolve(root, "dist/server"),
1815
+ emptyOutDir: false,
1816
+ rollupOptions: {
1817
+ output: { entryFileNames: "_routes_prerender.mjs" }
1818
+ }
1819
+ },
1820
+ resolve: ctx.resolvedResolve
1821
+ });
1822
+ const routesPath = pathToFileURL(
1823
+ path.resolve(root, "dist/server/_routes_prerender.mjs")
1824
+ ).href;
1825
+ const routesMod = await import(
1826
+ /* @vite-ignore */
1827
+ routesPath
1828
+ );
1829
+ const routes = routesMod.routes ?? routesMod.default ?? [];
1830
+ fs.rmSync(path.resolve(root, "dist/server/_routes_prerender.mjs"), {
1831
+ force: true
1832
+ });
1833
+ const prerenderPaths = /* @__PURE__ */ new Set();
1834
+ for (const r of routes) {
1835
+ if (r.renderMode === "prerender" && r.path && !r.path.includes(":")) {
1836
+ prerenderPaths.add(r.path);
1837
+ }
1838
+ }
1839
+ if (ctx.renderModes) {
1840
+ for (const [pattern, mode] of Object.entries(ctx.renderModes)) {
1841
+ if (mode === "prerender" && !pattern.includes("*") && !pattern.includes(":")) {
1842
+ prerenderPaths.add(pattern);
1843
+ }
1844
+ }
1845
+ }
1846
+ if (prerenderPaths.size === 0) return [];
1847
+ const ssrPath = pathToFileURL(
1848
+ path.resolve(root, "dist/server/ssr.js")
1849
+ ).href;
1850
+ const ssrModule = await import(
1851
+ /* @vite-ignore */
1852
+ ssrPath
1853
+ );
1854
+ const results = [];
1855
+ for (const routePath of prerenderPaths) {
1856
+ for (const locale of ctx.locales) {
1857
+ const url = locale === ctx.defaultLocale ? routePath : `/${locale}${routePath === "/" ? "" : routePath}`;
1858
+ try {
1859
+ const {
1860
+ html: appHtml,
1861
+ head,
1862
+ css,
1863
+ serverData
1864
+ } = await ssrModule.render(url, locale);
1865
+ const serializedData = ssrModule.serializeServerData(serverData);
1866
+ const finalHtml = ctx.templateHtml.replace("<!--ssr-lang-->", locale).replace(
1867
+ "<!--ssr-head-->",
1868
+ head + "\n<style>" + css + "</style>"
1869
+ ).replace("<!--ssr-body-->", appHtml).replace(
1870
+ "<!--ssr-data-->",
1871
+ '<script id="serialized-server-data" type="application/json">' + serializedData + "</script>"
1872
+ );
1873
+ results.push({ url, html: finalHtml });
1874
+ } catch (e) {
1875
+ console.warn(` [prerender] Failed to render ${url}:`, e);
1876
+ }
1877
+ }
1878
+ }
1879
+ if (results.length > 0) {
1880
+ console.log(
1881
+ ` Pre-rendered ${results.length} pages (${prerenderPaths.size} routes \xD7 ${ctx.locales.length} locales)
1882
+ `
1883
+ );
1884
+ }
1885
+ return results;
1886
+ }
1727
1887
 
1728
1888
  // ../server/src/adapters/cloudflare.ts
1729
1889
  function cloudflareAdapter() {
@@ -1749,6 +1909,14 @@ function cloudflareAdapter() {
1749
1909
  // 这些构建工具运行时不需要,且 fsevents 是 macOS .node 原生二进制无法打包
1750
1910
  });
1751
1911
  copyStaticAssets(ctx, path.resolve(outputDir, "assets"));
1912
+ const prerendered = await prerenderRoutes(ctx);
1913
+ for (const { url, html } of prerendered) {
1914
+ const filePath = url === "/" ? path.join(outputDir, "assets", "index.html") : path.join(outputDir, "assets", url, "index.html");
1915
+ fs.mkdirSync(path.resolve(filePath, ".."), {
1916
+ recursive: true
1917
+ });
1918
+ fs.writeFileSync(filePath, html);
1919
+ }
1752
1920
  } finally {
1753
1921
  fs.rmSync(tempEntry, { force: true });
1754
1922
  }
@@ -1792,6 +1960,13 @@ function netlifyAdapter() {
1792
1960
  path.resolve(root, "dist/client/_redirects"),
1793
1961
  redirects
1794
1962
  );
1963
+ const prerendered = await prerenderRoutes(ctx);
1964
+ const clientDir = path.resolve(root, "dist/client");
1965
+ for (const { url, html } of prerendered) {
1966
+ const filePath = url === "/" ? path.join(clientDir, "index.html") : path.join(clientDir, url, "index.html");
1967
+ fs.mkdirSync(path.resolve(filePath, ".."), { recursive: true });
1968
+ fs.writeFileSync(filePath, html);
1969
+ }
1795
1970
  console.log(
1796
1971
  " Netlify output \u2192 .netlify/functions-internal/ssr/\n Publish dir: dist/client/\n"
1797
1972
  );
@@ -1825,6 +2000,18 @@ serve({ fetch: app.fetch, port }, (info) => {
1825
2000
  } finally {
1826
2001
  fs.rmSync(tempEntry, { force: true });
1827
2002
  }
2003
+ const prerendered = await prerenderRoutes(ctx);
2004
+ if (prerendered.length > 0) {
2005
+ const prerenderDir = path.resolve(root, "dist/prerender");
2006
+ fs.mkdirSync(prerenderDir, { recursive: true });
2007
+ for (const { url, html } of prerendered) {
2008
+ const filePath = url === "/" ? path.join(prerenderDir, "index.html") : path.join(prerenderDir, url, "index.html");
2009
+ fs.mkdirSync(path.resolve(filePath, ".."), {
2010
+ recursive: true
2011
+ });
2012
+ fs.writeFileSync(filePath, html);
2013
+ }
2014
+ }
1828
2015
  console.log(
1829
2016
  " Node output \u2192 dist/server/index.mjs\n Run: node dist/server/index.mjs\n"
1830
2017
  );
@@ -1853,7 +2040,7 @@ function staticAdapter(opts = {}) {
1853
2040
  ssrPath
1854
2041
  );
1855
2042
  ctx.copyStaticAssets(outputDir, { excludeHtml: true });
1856
- const routePaths = await extractRoutes(ctx, opts);
2043
+ const { paths: routePaths, defs: routeDefs } = await extractRoutesWithModes(ctx, opts);
1857
2044
  const allUrls = [];
1858
2045
  for (const routePath of routePaths) {
1859
2046
  for (const locale of ctx.locales) {
@@ -1872,21 +2059,37 @@ function staticAdapter(opts = {}) {
1872
2059
  ctx.locales,
1873
2060
  ctx.defaultLocale
1874
2061
  );
1875
- const {
1876
- html: appHtml,
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
2062
+ const routeDef = routeDefs.find(
2063
+ (r) => r.path === stripLocalePrefix(url, ctx.locales)
2064
+ );
2065
+ const mode = resolveRenderMode(
2066
+ stripLocalePrefix(url, ctx.locales),
2067
+ routeDef?.renderMode,
2068
+ ctx.renderModes
1889
2069
  );
2070
+ let finalHtml;
2071
+ if (mode === "csr") {
2072
+ finalHtml = injectCSRShellForStatic(
2073
+ ctx.templateHtml,
2074
+ locale
2075
+ );
2076
+ } else {
2077
+ const {
2078
+ html: appHtml,
2079
+ head,
2080
+ css,
2081
+ serverData
2082
+ } = await ssrModule.render(url, locale);
2083
+ const serializedData = ssrModule.serializeServerData(serverData);
2084
+ finalHtml = injectSSRForStatic(
2085
+ ctx.templateHtml,
2086
+ locale,
2087
+ head,
2088
+ css,
2089
+ appHtml,
2090
+ serializedData
2091
+ );
2092
+ }
1890
2093
  const filePath = url === "/" ? path.join(outputDir, "index.html") : path.join(outputDir, url, "index.html");
1891
2094
  fs.mkdirSync(path.resolve(filePath, ".."), {
1892
2095
  recursive: true
@@ -1901,9 +2104,10 @@ function staticAdapter(opts = {}) {
1901
2104
  }
1902
2105
  };
1903
2106
  }
1904
- async function extractRoutes(ctx, opts) {
2107
+ async function extractRoutesWithModes(ctx, opts) {
1905
2108
  const routesFile = opts.routesExport ?? "src/lib/bootstrap.ts";
1906
2109
  const paths = [];
2110
+ const defs = [];
1907
2111
  try {
1908
2112
  const { pathToFileURL } = await import(
1909
2113
  /* @vite-ignore */
@@ -1933,6 +2137,7 @@ async function extractRoutes(ctx, opts) {
1933
2137
  for (const r of routes) {
1934
2138
  if (r.path && !r.path.includes(":")) {
1935
2139
  paths.push(r.path);
2140
+ defs.push({ path: r.path, renderMode: r.renderMode });
1936
2141
  }
1937
2142
  }
1938
2143
  }
@@ -1952,7 +2157,7 @@ async function extractRoutes(ctx, opts) {
1952
2157
  }
1953
2158
  }
1954
2159
  if (paths.length === 0) paths.push("/");
1955
- return paths;
2160
+ return { paths, defs };
1956
2161
  }
1957
2162
  function inferLocale(url, locales, defaultLocale) {
1958
2163
  const segments = url.split("/").filter(Boolean);
@@ -1967,6 +2172,29 @@ function injectSSRForStatic(template, locale, head, css, html, serializedData) {
1967
2172
  '<script id="serialized-server-data" type="application/json">' + serializedData + "</script>"
1968
2173
  );
1969
2174
  }
2175
+ function injectCSRShellForStatic(template, locale) {
2176
+ return template.replace("<!--ssr-lang-->", locale).replace("<!--ssr-head-->", "").replace("<!--ssr-body-->", "").replace("<!--ssr-data-->", "");
2177
+ }
2178
+ function stripLocalePrefix(url, locales) {
2179
+ const segments = url.split("/").filter(Boolean);
2180
+ if (segments.length > 0 && locales.includes(segments[0])) {
2181
+ const rest = segments.slice(1).join("/");
2182
+ return rest ? `/${rest}` : "/";
2183
+ }
2184
+ return url;
2185
+ }
2186
+ function resolveRenderMode(routePath, routeRenderMode, renderModes) {
2187
+ if (renderModes) {
2188
+ if (renderModes[routePath]) return renderModes[routePath];
2189
+ for (const [pattern, mode] of Object.entries(renderModes)) {
2190
+ if (pattern.includes("*")) {
2191
+ const re = new RegExp("^" + pattern.replace(/\*/g, ".*") + "$");
2192
+ if (re.test(routePath)) return mode;
2193
+ }
2194
+ }
2195
+ }
2196
+ return routeRenderMode ?? "ssr";
2197
+ }
1970
2198
 
1971
2199
  // ../server/src/adapters/vercel.ts
1972
2200
  function vercelAdapter() {
@@ -1977,8 +2205,8 @@ function vercelAdapter() {
1977
2205
  const outputDir = path.resolve(root, ".vercel/output");
1978
2206
  fs.rmSync(outputDir, { recursive: true, force: true });
1979
2207
  const entrySource = generateSSREntry(ctx, {
1980
- platformImport: `import { handle } from "hono/vercel";`,
1981
- platformExport: `export default handle(app);`
2208
+ platformImport: `import { getRequestListener } from "@hono/node-server";`,
2209
+ platformExport: `export default getRequestListener(app.fetch);`
1982
2210
  });
1983
2211
  const tempEntry = path.resolve(root, ".vercel-entry.tmp.mjs");
1984
2212
  fs.writeFileSync(tempEntry, entrySource);
@@ -2025,6 +2253,13 @@ function vercelAdapter() {
2025
2253
  } finally {
2026
2254
  fs.rmSync(tempEntry, { force: true });
2027
2255
  }
2256
+ const prerendered = await prerenderRoutes(ctx);
2257
+ const staticDir = path.resolve(root, ".vercel/output/static");
2258
+ for (const { url, html } of prerendered) {
2259
+ const filePath = url === "/" ? path.join(staticDir, "index.html") : path.join(staticDir, url, "index.html");
2260
+ fs.mkdirSync(path.resolve(filePath, ".."), { recursive: true });
2261
+ fs.writeFileSync(filePath, html);
2262
+ }
2028
2263
  console.log(" Vercel output \u2192 .vercel/output/\n");
2029
2264
  }
2030
2265
  };
@@ -2297,6 +2532,18 @@ function resolveSetupFn(mod) {
2297
2532
  const first = Object.values(mod).find((v) => typeof v === "function");
2298
2533
  return first ?? null;
2299
2534
  }
2535
+ function matchRenderModeConfig(url, renderModes) {
2536
+ if (!renderModes) return null;
2537
+ const path = url.split("?")[0];
2538
+ if (renderModes[path]) return renderModes[path];
2539
+ for (const [pattern, mode] of Object.entries(renderModes)) {
2540
+ if (pattern.includes("*")) {
2541
+ const re = new RegExp("^" + pattern.replace(/\*/g, ".*") + "$");
2542
+ if (re.test(path)) return mode;
2543
+ }
2544
+ }
2545
+ return null;
2546
+ }
2300
2547
  function finesoftFrontViteConfig(options = {}) {
2301
2548
  const ssrEntry = options.ssr?.entry ?? "src/ssr.ts";
2302
2549
  let root = process.cwd();
@@ -2376,13 +2623,14 @@ function finesoftFrontViteConfig(options = {}) {
2376
2623
  /* @vite-ignore */
2377
2624
  "hono"
2378
2625
  );
2379
- const { injectSSRContent: injectSSRContent2 } = await Promise.resolve().then(() => (init_src2(), src_exports));
2626
+ const { injectSSRContent: injectSSRContent2, injectCSRShell: injectCSRShell2 } = await Promise.resolve().then(() => (init_src2(), src_exports));
2380
2627
  const { parseAcceptLanguage: parseAcceptLanguage2 } = await Promise.resolve().then(() => (init_locale(), locale_exports));
2381
2628
  const { getRequestListener } = await import(
2382
2629
  /* @vite-ignore */
2383
2630
  "@hono/node-server"
2384
2631
  );
2385
2632
  const app = new HonoClass();
2633
+ const isrCache = /* @__PURE__ */ new Map();
2386
2634
  if (typeof options.setup === "function") {
2387
2635
  await options.setup(app);
2388
2636
  } else if (typeof options.setup === "string") {
@@ -2421,12 +2669,25 @@ function finesoftFrontViteConfig(options = {}) {
2421
2669
  options.locales,
2422
2670
  options.defaultLocale
2423
2671
  );
2672
+ const overrideMode = matchRenderModeConfig(
2673
+ url,
2674
+ options.renderModes
2675
+ );
2676
+ if (overrideMode === "csr") {
2677
+ return c.html(injectCSRShell2(template, locale));
2678
+ }
2679
+ const cached = isrCache.get(url);
2680
+ if (cached) return c.html(cached);
2424
2681
  const {
2425
2682
  html: appHtml,
2426
2683
  head,
2427
2684
  css,
2428
- serverData
2685
+ serverData,
2686
+ renderMode
2429
2687
  } = await ssrModule.render(url, locale);
2688
+ if (renderMode === "csr") {
2689
+ return c.html(injectCSRShell2(template, locale));
2690
+ }
2430
2691
  const serializedData = ssrModule.serializeServerData(serverData);
2431
2692
  const finalHtml = injectSSRContent2({
2432
2693
  template,
@@ -2436,6 +2697,9 @@ function finesoftFrontViteConfig(options = {}) {
2436
2697
  html: appHtml,
2437
2698
  serializedData
2438
2699
  });
2700
+ if (renderMode === "prerender" || overrideMode === "prerender") {
2701
+ isrCache.set(url, finalHtml);
2702
+ }
2439
2703
  return c.html(finalHtml);
2440
2704
  } catch (e) {
2441
2705
  console.error("[SSR Preview Error]", e);
@@ -2506,6 +2770,7 @@ function finesoftFrontViteConfig(options = {}) {
2506
2770
  locales,
2507
2771
  defaultLocale,
2508
2772
  templateHtml,
2773
+ renderModes: options.renderModes,
2509
2774
  resolvedResolve,
2510
2775
  resolvedCss,
2511
2776
  vite,
@@ -2565,6 +2830,7 @@ function finesoftFrontViteConfig(options = {}) {
2565
2830
  finesoftFrontViteConfig,
2566
2831
  generateUuid,
2567
2832
  getBaseUrl,
2833
+ injectCSRShell,
2568
2834
  injectSSRContent,
2569
2835
  isCompoundAction,
2570
2836
  isExternalUrlAction,