@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.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { FrameworkConfig, Framework, BasePage, PrefetchedIntent } from './browser.cjs';
2
- export { ACTION_KINDS, Action, ActionDispatcher, ActionHandler, ActionHandlerDependencies, AsyncMapper, BaseController, BaseItem, BaseLogger, BaseShelf, BrowserAppConfig, CompositeLogger, CompositeLoggerFactory, CompoundAction, ConsoleLogger, ConsoleLoggerFactory, Container, DEP_KEYS, ExternalUrlAction, ExternalUrlDependencies, FeatureFlags, FlowAction, FlowActionCallbacks, FlowActionDependencies, History, HttpClient, HttpClientConfig, HttpError, Intent, IntentController, IntentDispatcher, Locale, Logger, LoggerFactory, Logger as LoggerInterface, LruMap, Mapper, MetricsRecorder, Net, None, Optional, PrefetchedIntents, RouteDefinition, RouteMatch, Router, Storage, buildUrl, createPrefetchedIntentsFromDom, defineRoutes, deserializeServerData, generateUuid, getBaseUrl, isCompoundAction, isExternalUrlAction, isFlowAction, isNone, isSome, makeDependencies, makeExternalUrlAction, makeFlowAction, mapEach, pipe, pipeAsync, registerActionHandlers, registerExternalUrlHandler, registerFlowActionHandler, removeHost, removeQueryParams, removeScheme, resetFilterCache, shouldLog, stableStringify, startBrowserApp, tryScroll } from './browser.cjs';
2
+ export { ACTION_KINDS, Action, ActionDispatcher, ActionHandler, ActionHandlerDependencies, AsyncMapper, BaseController, BaseItem, BaseLogger, BaseShelf, BrowserAppConfig, CompositeLogger, CompositeLoggerFactory, CompoundAction, ConsoleLogger, ConsoleLoggerFactory, Container, DEP_KEYS, ExternalUrlAction, ExternalUrlDependencies, FeatureFlags, FlowAction, FlowActionCallbacks, FlowActionDependencies, History, HttpClient, HttpClientConfig, HttpError, Intent, IntentController, IntentDispatcher, Locale, Logger, LoggerFactory, Logger as LoggerInterface, LruMap, Mapper, MetricsRecorder, Net, None, Optional, PrefetchedIntents, RenderMode, RouteDefinition, RouteMatch, Router, Storage, buildUrl, createPrefetchedIntentsFromDom, defineRoutes, deserializeServerData, generateUuid, getBaseUrl, isCompoundAction, isExternalUrlAction, isFlowAction, isNone, isSome, makeDependencies, makeExternalUrlAction, makeFlowAction, mapEach, pipe, pipeAsync, registerActionHandlers, registerExternalUrlHandler, registerFlowActionHandler, removeHost, removeQueryParams, removeScheme, resetFilterCache, shouldLog, stableStringify, startBrowserApp, tryScroll } from './browser.cjs';
3
3
  import * as node_fs from 'node:fs';
4
4
  import { Hono } from 'hono';
5
5
  import { ViteDevServer } from 'vite';
@@ -35,6 +35,8 @@ interface SSRRenderResult {
35
35
  head: string;
36
36
  css: string;
37
37
  serverData: PrefetchedIntent[];
38
+ /** 该路由的渲染模式(由 Router 返回) */
39
+ renderMode?: string;
38
40
  }
39
41
  declare function ssrRender(options: SSRRenderOptions): Promise<SSRRenderResult>;
40
42
 
@@ -88,6 +90,11 @@ interface InjectSSROptions {
88
90
  serializedData: string;
89
91
  }
90
92
  declare function injectSSRContent(options: InjectSSROptions): string;
93
+ /**
94
+ * CSR 空壳注入 — 只替换 lang,清空 body/head/data 占位符
95
+ * 用于 renderMode === "csr" 的路由
96
+ */
97
+ declare function injectCSRShell(template: string, locale: string): string;
91
98
 
92
99
  /**
93
100
  * serializeServerData — 将 PrefetchedIntents 数据序列化为安全的 JSON
@@ -121,6 +128,11 @@ interface AdapterContext {
121
128
  resolvedResolve: unknown;
122
129
  /** Vite 的 css 配置 */
123
130
  resolvedCss: unknown;
131
+ /**
132
+ * 按路由覆盖的渲染模式。
133
+ * key: 精确路径或 glob 模式,value: "ssr" | "csr" | "prerender"
134
+ */
135
+ renderModes?: Record<string, string>;
124
136
  vite: any;
125
137
  fs: typeof node_fs;
126
138
  path: {
@@ -264,6 +276,7 @@ interface SSRModule {
264
276
  head: string;
265
277
  css: string;
266
278
  serverData: unknown;
279
+ renderMode?: string;
267
280
  }>;
268
281
  serializeServerData: (data: unknown) => string;
269
282
  }
@@ -413,6 +426,20 @@ interface FinesoftFrontViteOptions {
413
426
  * - 不设置则不生成部署产物
414
427
  */
415
428
  adapter?: string | Adapter;
429
+ /**
430
+ * 按路由覆盖渲染模式(优先级高于 RouteDefinition.renderMode)。
431
+ * key: 精确路径或 glob 模式,如 "/search" 或 "/blog/*"
432
+ * value: "ssr" | "csr" | "prerender"
433
+ *
434
+ * @example
435
+ * ```ts
436
+ * renderModes: {
437
+ * "/search": "csr",
438
+ * "/blog/*": "prerender",
439
+ * }
440
+ * ```
441
+ */
442
+ renderModes?: Record<string, "ssr" | "csr" | "prerender">;
416
443
  }
417
444
  declare function finesoftFrontViteConfig(options?: FinesoftFrontViteOptions): {
418
445
  name: string;
@@ -425,4 +452,4 @@ declare function finesoftFrontViteConfig(options?: FinesoftFrontViteOptions): {
425
452
  closeBundle(): Promise<void>;
426
453
  };
427
454
 
428
- export { type Adapter, type AdapterContext, BasePage, type FinesoftFrontViteOptions, Framework, FrameworkConfig, type InjectSSROptions, PrefetchedIntent, type RuntimeInfo, type SSRAppOptions, type SSRModule, type SSRRenderConfig, type SSRRenderOptions, type SSRRenderResult, SSR_PLACEHOLDERS, type ServerConfig, type ServerInstance, type StartServerOptions, autoAdapter, cloudflareAdapter, createSSRApp, createSSRRender, createServer, detectRuntime, finesoftFrontViteConfig, injectSSRContent, netlifyAdapter, nodeAdapter, parseAcceptLanguage, resolveAdapter, resolveRoot, serializeServerData, ssrRender, startServer, staticAdapter, vercelAdapter };
455
+ export { type Adapter, type AdapterContext, BasePage, type FinesoftFrontViteOptions, Framework, FrameworkConfig, type InjectSSROptions, PrefetchedIntent, type RuntimeInfo, type SSRAppOptions, type SSRModule, type SSRRenderConfig, type SSRRenderOptions, type SSRRenderResult, SSR_PLACEHOLDERS, type ServerConfig, type ServerInstance, type StartServerOptions, autoAdapter, cloudflareAdapter, createSSRApp, createSSRRender, createServer, detectRuntime, finesoftFrontViteConfig, injectCSRShell, injectSSRContent, netlifyAdapter, nodeAdapter, parseAcceptLanguage, resolveAdapter, resolveRoot, serializeServerData, ssrRender, startServer, staticAdapter, vercelAdapter };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { FrameworkConfig, Framework, BasePage, PrefetchedIntent } from './browser.js';
2
- export { ACTION_KINDS, Action, ActionDispatcher, ActionHandler, ActionHandlerDependencies, AsyncMapper, BaseController, BaseItem, BaseLogger, BaseShelf, BrowserAppConfig, CompositeLogger, CompositeLoggerFactory, CompoundAction, ConsoleLogger, ConsoleLoggerFactory, Container, DEP_KEYS, ExternalUrlAction, ExternalUrlDependencies, FeatureFlags, FlowAction, FlowActionCallbacks, FlowActionDependencies, History, HttpClient, HttpClientConfig, HttpError, Intent, IntentController, IntentDispatcher, Locale, Logger, LoggerFactory, Logger as LoggerInterface, LruMap, Mapper, MetricsRecorder, Net, None, Optional, PrefetchedIntents, RouteDefinition, RouteMatch, Router, Storage, buildUrl, createPrefetchedIntentsFromDom, defineRoutes, deserializeServerData, generateUuid, getBaseUrl, isCompoundAction, isExternalUrlAction, isFlowAction, isNone, isSome, makeDependencies, makeExternalUrlAction, makeFlowAction, mapEach, pipe, pipeAsync, registerActionHandlers, registerExternalUrlHandler, registerFlowActionHandler, removeHost, removeQueryParams, removeScheme, resetFilterCache, shouldLog, stableStringify, startBrowserApp, tryScroll } from './browser.js';
2
+ export { ACTION_KINDS, Action, ActionDispatcher, ActionHandler, ActionHandlerDependencies, AsyncMapper, BaseController, BaseItem, BaseLogger, BaseShelf, BrowserAppConfig, CompositeLogger, CompositeLoggerFactory, CompoundAction, ConsoleLogger, ConsoleLoggerFactory, Container, DEP_KEYS, ExternalUrlAction, ExternalUrlDependencies, FeatureFlags, FlowAction, FlowActionCallbacks, FlowActionDependencies, History, HttpClient, HttpClientConfig, HttpError, Intent, IntentController, IntentDispatcher, Locale, Logger, LoggerFactory, Logger as LoggerInterface, LruMap, Mapper, MetricsRecorder, Net, None, Optional, PrefetchedIntents, RenderMode, RouteDefinition, RouteMatch, Router, Storage, buildUrl, createPrefetchedIntentsFromDom, defineRoutes, deserializeServerData, generateUuid, getBaseUrl, isCompoundAction, isExternalUrlAction, isFlowAction, isNone, isSome, makeDependencies, makeExternalUrlAction, makeFlowAction, mapEach, pipe, pipeAsync, registerActionHandlers, registerExternalUrlHandler, registerFlowActionHandler, removeHost, removeQueryParams, removeScheme, resetFilterCache, shouldLog, stableStringify, startBrowserApp, tryScroll } from './browser.js';
3
3
  import * as node_fs from 'node:fs';
4
4
  import { Hono } from 'hono';
5
5
  import { ViteDevServer } from 'vite';
@@ -35,6 +35,8 @@ interface SSRRenderResult {
35
35
  head: string;
36
36
  css: string;
37
37
  serverData: PrefetchedIntent[];
38
+ /** 该路由的渲染模式(由 Router 返回) */
39
+ renderMode?: string;
38
40
  }
39
41
  declare function ssrRender(options: SSRRenderOptions): Promise<SSRRenderResult>;
40
42
 
@@ -88,6 +90,11 @@ interface InjectSSROptions {
88
90
  serializedData: string;
89
91
  }
90
92
  declare function injectSSRContent(options: InjectSSROptions): string;
93
+ /**
94
+ * CSR 空壳注入 — 只替换 lang,清空 body/head/data 占位符
95
+ * 用于 renderMode === "csr" 的路由
96
+ */
97
+ declare function injectCSRShell(template: string, locale: string): string;
91
98
 
92
99
  /**
93
100
  * serializeServerData — 将 PrefetchedIntents 数据序列化为安全的 JSON
@@ -121,6 +128,11 @@ interface AdapterContext {
121
128
  resolvedResolve: unknown;
122
129
  /** Vite 的 css 配置 */
123
130
  resolvedCss: unknown;
131
+ /**
132
+ * 按路由覆盖的渲染模式。
133
+ * key: 精确路径或 glob 模式,value: "ssr" | "csr" | "prerender"
134
+ */
135
+ renderModes?: Record<string, string>;
124
136
  vite: any;
125
137
  fs: typeof node_fs;
126
138
  path: {
@@ -264,6 +276,7 @@ interface SSRModule {
264
276
  head: string;
265
277
  css: string;
266
278
  serverData: unknown;
279
+ renderMode?: string;
267
280
  }>;
268
281
  serializeServerData: (data: unknown) => string;
269
282
  }
@@ -413,6 +426,20 @@ interface FinesoftFrontViteOptions {
413
426
  * - 不设置则不生成部署产物
414
427
  */
415
428
  adapter?: string | Adapter;
429
+ /**
430
+ * 按路由覆盖渲染模式(优先级高于 RouteDefinition.renderMode)。
431
+ * key: 精确路径或 glob 模式,如 "/search" 或 "/blog/*"
432
+ * value: "ssr" | "csr" | "prerender"
433
+ *
434
+ * @example
435
+ * ```ts
436
+ * renderModes: {
437
+ * "/search": "csr",
438
+ * "/blog/*": "prerender",
439
+ * }
440
+ * ```
441
+ */
442
+ renderModes?: Record<string, "ssr" | "csr" | "prerender">;
416
443
  }
417
444
  declare function finesoftFrontViteConfig(options?: FinesoftFrontViteOptions): {
418
445
  name: string;
@@ -425,4 +452,4 @@ declare function finesoftFrontViteConfig(options?: FinesoftFrontViteOptions): {
425
452
  closeBundle(): Promise<void>;
426
453
  };
427
454
 
428
- export { type Adapter, type AdapterContext, BasePage, type FinesoftFrontViteOptions, Framework, FrameworkConfig, type InjectSSROptions, PrefetchedIntent, type RuntimeInfo, type SSRAppOptions, type SSRModule, type SSRRenderConfig, type SSRRenderOptions, type SSRRenderResult, SSR_PLACEHOLDERS, type ServerConfig, type ServerInstance, type StartServerOptions, autoAdapter, cloudflareAdapter, createSSRApp, createSSRRender, createServer, detectRuntime, finesoftFrontViteConfig, injectSSRContent, netlifyAdapter, nodeAdapter, parseAcceptLanguage, resolveAdapter, resolveRoot, serializeServerData, ssrRender, startServer, staticAdapter, vercelAdapter };
455
+ export { type Adapter, type AdapterContext, BasePage, type FinesoftFrontViteOptions, Framework, FrameworkConfig, type InjectSSROptions, PrefetchedIntent, type RuntimeInfo, type SSRAppOptions, type SSRModule, type SSRRenderConfig, type SSRRenderOptions, type SSRRenderResult, SSR_PLACEHOLDERS, type ServerConfig, type ServerInstance, type StartServerOptions, autoAdapter, cloudflareAdapter, createSSRApp, createSSRRender, createServer, detectRuntime, finesoftFrontViteConfig, injectCSRShell, injectSSRContent, netlifyAdapter, nodeAdapter, parseAcceptLanguage, resolveAdapter, resolveRoot, serializeServerData, ssrRender, startServer, staticAdapter, vercelAdapter };
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-SN3OO3DU.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,7 @@ 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 ?? {});
78
80
  return `
79
81
  import { Hono } from "hono";
80
82
  ${opts.platformImport}
@@ -84,6 +86,7 @@ ${setupImport}
84
86
  const TEMPLATE = ${JSON.stringify(ctx.templateHtml)};
85
87
  const LOCALES = ${locales};
86
88
  const DEFAULT_LOCALE = ${defaultLocale};
89
+ const RENDER_MODES = ${renderModes};
87
90
 
88
91
  function parseAcceptLanguage(header) {
89
92
  if (!header) return DEFAULT_LOCALE;
@@ -106,6 +109,28 @@ function injectSSR(t, locale, head, css, html, data) {
106
109
  .replace("<!--ssr-data-->", '<script id="serialized-server-data" type="application/json">' + data + "</script>");
107
110
  }
108
111
 
112
+ function injectCSRShell(t, locale) {
113
+ return t
114
+ .replace("<!--ssr-lang-->", locale)
115
+ .replace("<!--ssr-head-->", "")
116
+ .replace("<!--ssr-body-->", "")
117
+ .replace("<!--ssr-data-->", "");
118
+ }
119
+
120
+ function matchRenderMode(url) {
121
+ const path = url.split("?")[0];
122
+ if (RENDER_MODES[path]) return RENDER_MODES[path];
123
+ for (const [pattern, mode] of Object.entries(RENDER_MODES)) {
124
+ if (pattern.includes("*")) {
125
+ const re = new RegExp("^" + pattern.replace(/\\*/g, ".*") + "$");
126
+ if (re.test(path)) return mode;
127
+ }
128
+ }
129
+ return null;
130
+ }
131
+
132
+ const isrCache = new Map();
133
+
109
134
  const app = new Hono();
110
135
  ${setupCall}
111
136
 
@@ -113,9 +138,33 @@ app.get("*", async (c) => {
113
138
  const url = c.req.path + (c.req.url.includes("?") ? "?" + c.req.url.split("?")[1] : "");
114
139
  try {
115
140
  const locale = parseAcceptLanguage(c.req.header("accept-language"));
116
- const { html: appHtml, head, css, serverData } = await render(url, locale);
141
+
142
+ // Vite \u914D\u7F6E\u7EA7\u522B\u8986\u76D6: CSR \u76F4\u63A5\u8FD4\u56DE\u7A7A\u58F3
143
+ const overrideMode = matchRenderMode(url);
144
+ if (overrideMode === "csr") {
145
+ return c.html(injectCSRShell(TEMPLATE, locale));
146
+ }
147
+
148
+ // ISR \u7F13\u5B58\u547D\u4E2D
149
+ const cached = isrCache.get(url);
150
+ if (cached) return c.html(cached);
151
+
152
+ const { html: appHtml, head, css, serverData, renderMode } = await render(url, locale);
153
+
154
+ // \u8DEF\u7531\u7EA7 CSR
155
+ if (renderMode === "csr") {
156
+ return c.html(injectCSRShell(TEMPLATE, locale));
157
+ }
158
+
117
159
  const serializedData = serializeServerData(serverData);
118
- return c.html(injectSSR(TEMPLATE, locale, head, css, appHtml, serializedData));
160
+ const finalHtml = injectSSR(TEMPLATE, locale, head, css, appHtml, serializedData);
161
+
162
+ // Prerender ISR \u7F13\u5B58\uFF08\u5305\u62EC Vite \u914D\u7F6E\u8986\u76D6\u548C\u8DEF\u7531\u7EA7\uFF09
163
+ if (renderMode === "prerender" || overrideMode === "prerender") {
164
+ isrCache.set(url, finalHtml);
165
+ }
166
+
167
+ return c.html(finalHtml);
119
168
  } catch (e) {
120
169
  console.error("[SSR Error]", e);
121
170
  return c.text("Internal Server Error", 500);
@@ -154,6 +203,89 @@ function copyStaticAssets(ctx, destDir, opts) {
154
203
  fs.rmSync(path.join(destDir, "index.html"), { force: true });
155
204
  }
156
205
  }
206
+ async function prerenderRoutes(ctx, routesExport = "src/lib/bootstrap.ts") {
207
+ const { fs, path, root, vite } = ctx;
208
+ const { pathToFileURL } = await import(
209
+ /* @vite-ignore */
210
+ "url"
211
+ );
212
+ await vite.build({
213
+ root,
214
+ build: {
215
+ ssr: routesExport,
216
+ outDir: path.resolve(root, "dist/server"),
217
+ emptyOutDir: false,
218
+ rollupOptions: {
219
+ output: { entryFileNames: "_routes_prerender.mjs" }
220
+ }
221
+ },
222
+ resolve: ctx.resolvedResolve
223
+ });
224
+ const routesPath = pathToFileURL(
225
+ path.resolve(root, "dist/server/_routes_prerender.mjs")
226
+ ).href;
227
+ const routesMod = await import(
228
+ /* @vite-ignore */
229
+ routesPath
230
+ );
231
+ const routes = routesMod.routes ?? routesMod.default ?? [];
232
+ fs.rmSync(path.resolve(root, "dist/server/_routes_prerender.mjs"), {
233
+ force: true
234
+ });
235
+ const prerenderPaths = /* @__PURE__ */ new Set();
236
+ for (const r of routes) {
237
+ if (r.renderMode === "prerender" && r.path && !r.path.includes(":")) {
238
+ prerenderPaths.add(r.path);
239
+ }
240
+ }
241
+ if (ctx.renderModes) {
242
+ for (const [pattern, mode] of Object.entries(ctx.renderModes)) {
243
+ if (mode === "prerender" && !pattern.includes("*") && !pattern.includes(":")) {
244
+ prerenderPaths.add(pattern);
245
+ }
246
+ }
247
+ }
248
+ if (prerenderPaths.size === 0) return [];
249
+ const ssrPath = pathToFileURL(
250
+ path.resolve(root, "dist/server/ssr.js")
251
+ ).href;
252
+ const ssrModule = await import(
253
+ /* @vite-ignore */
254
+ ssrPath
255
+ );
256
+ const results = [];
257
+ for (const routePath of prerenderPaths) {
258
+ for (const locale of ctx.locales) {
259
+ const url = locale === ctx.defaultLocale ? routePath : `/${locale}${routePath === "/" ? "" : routePath}`;
260
+ try {
261
+ const {
262
+ html: appHtml,
263
+ head,
264
+ css,
265
+ serverData
266
+ } = await ssrModule.render(url, locale);
267
+ const serializedData = ssrModule.serializeServerData(serverData);
268
+ const finalHtml = ctx.templateHtml.replace("<!--ssr-lang-->", locale).replace(
269
+ "<!--ssr-head-->",
270
+ head + "\n<style>" + css + "</style>"
271
+ ).replace("<!--ssr-body-->", appHtml).replace(
272
+ "<!--ssr-data-->",
273
+ '<script id="serialized-server-data" type="application/json">' + serializedData + "</script>"
274
+ );
275
+ results.push({ url, html: finalHtml });
276
+ } catch (e) {
277
+ console.warn(` [prerender] Failed to render ${url}:`, e);
278
+ }
279
+ }
280
+ }
281
+ if (results.length > 0) {
282
+ console.log(
283
+ ` Pre-rendered ${results.length} pages (${prerenderPaths.size} routes \xD7 ${ctx.locales.length} locales)
284
+ `
285
+ );
286
+ }
287
+ return results;
288
+ }
157
289
 
158
290
  // ../server/src/adapters/cloudflare.ts
159
291
  function cloudflareAdapter() {
@@ -179,6 +311,14 @@ function cloudflareAdapter() {
179
311
  // 这些构建工具运行时不需要,且 fsevents 是 macOS .node 原生二进制无法打包
180
312
  });
181
313
  copyStaticAssets(ctx, path.resolve(outputDir, "assets"));
314
+ const prerendered = await prerenderRoutes(ctx);
315
+ for (const { url, html } of prerendered) {
316
+ const filePath = url === "/" ? path.join(outputDir, "assets", "index.html") : path.join(outputDir, "assets", url, "index.html");
317
+ fs.mkdirSync(path.resolve(filePath, ".."), {
318
+ recursive: true
319
+ });
320
+ fs.writeFileSync(filePath, html);
321
+ }
182
322
  } finally {
183
323
  fs.rmSync(tempEntry, { force: true });
184
324
  }
@@ -222,6 +362,13 @@ function netlifyAdapter() {
222
362
  path.resolve(root, "dist/client/_redirects"),
223
363
  redirects
224
364
  );
365
+ const prerendered = await prerenderRoutes(ctx);
366
+ const clientDir = path.resolve(root, "dist/client");
367
+ for (const { url, html } of prerendered) {
368
+ const filePath = url === "/" ? path.join(clientDir, "index.html") : path.join(clientDir, url, "index.html");
369
+ fs.mkdirSync(path.resolve(filePath, ".."), { recursive: true });
370
+ fs.writeFileSync(filePath, html);
371
+ }
225
372
  console.log(
226
373
  " Netlify output \u2192 .netlify/functions-internal/ssr/\n Publish dir: dist/client/\n"
227
374
  );
@@ -255,6 +402,18 @@ serve({ fetch: app.fetch, port }, (info) => {
255
402
  } finally {
256
403
  fs.rmSync(tempEntry, { force: true });
257
404
  }
405
+ const prerendered = await prerenderRoutes(ctx);
406
+ if (prerendered.length > 0) {
407
+ const prerenderDir = path.resolve(root, "dist/prerender");
408
+ fs.mkdirSync(prerenderDir, { recursive: true });
409
+ for (const { url, html } of prerendered) {
410
+ const filePath = url === "/" ? path.join(prerenderDir, "index.html") : path.join(prerenderDir, url, "index.html");
411
+ fs.mkdirSync(path.resolve(filePath, ".."), {
412
+ recursive: true
413
+ });
414
+ fs.writeFileSync(filePath, html);
415
+ }
416
+ }
258
417
  console.log(
259
418
  " Node output \u2192 dist/server/index.mjs\n Run: node dist/server/index.mjs\n"
260
419
  );
@@ -283,7 +442,7 @@ function staticAdapter(opts = {}) {
283
442
  ssrPath
284
443
  );
285
444
  ctx.copyStaticAssets(outputDir, { excludeHtml: true });
286
- const routePaths = await extractRoutes(ctx, opts);
445
+ const { paths: routePaths, defs: routeDefs } = await extractRoutesWithModes(ctx, opts);
287
446
  const allUrls = [];
288
447
  for (const routePath of routePaths) {
289
448
  for (const locale of ctx.locales) {
@@ -302,21 +461,37 @@ function staticAdapter(opts = {}) {
302
461
  ctx.locales,
303
462
  ctx.defaultLocale
304
463
  );
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
464
+ const routeDef = routeDefs.find(
465
+ (r) => r.path === stripLocalePrefix(url, ctx.locales)
466
+ );
467
+ const mode = resolveRenderMode(
468
+ stripLocalePrefix(url, ctx.locales),
469
+ routeDef?.renderMode,
470
+ ctx.renderModes
319
471
  );
472
+ let finalHtml;
473
+ if (mode === "csr") {
474
+ finalHtml = injectCSRShellForStatic(
475
+ ctx.templateHtml,
476
+ locale
477
+ );
478
+ } else {
479
+ const {
480
+ html: appHtml,
481
+ head,
482
+ css,
483
+ serverData
484
+ } = await ssrModule.render(url, locale);
485
+ const serializedData = ssrModule.serializeServerData(serverData);
486
+ finalHtml = injectSSRForStatic(
487
+ ctx.templateHtml,
488
+ locale,
489
+ head,
490
+ css,
491
+ appHtml,
492
+ serializedData
493
+ );
494
+ }
320
495
  const filePath = url === "/" ? path.join(outputDir, "index.html") : path.join(outputDir, url, "index.html");
321
496
  fs.mkdirSync(path.resolve(filePath, ".."), {
322
497
  recursive: true
@@ -331,9 +506,10 @@ function staticAdapter(opts = {}) {
331
506
  }
332
507
  };
333
508
  }
334
- async function extractRoutes(ctx, opts) {
509
+ async function extractRoutesWithModes(ctx, opts) {
335
510
  const routesFile = opts.routesExport ?? "src/lib/bootstrap.ts";
336
511
  const paths = [];
512
+ const defs = [];
337
513
  try {
338
514
  const { pathToFileURL } = await import(
339
515
  /* @vite-ignore */
@@ -363,6 +539,7 @@ async function extractRoutes(ctx, opts) {
363
539
  for (const r of routes) {
364
540
  if (r.path && !r.path.includes(":")) {
365
541
  paths.push(r.path);
542
+ defs.push({ path: r.path, renderMode: r.renderMode });
366
543
  }
367
544
  }
368
545
  }
@@ -382,7 +559,7 @@ async function extractRoutes(ctx, opts) {
382
559
  }
383
560
  }
384
561
  if (paths.length === 0) paths.push("/");
385
- return paths;
562
+ return { paths, defs };
386
563
  }
387
564
  function inferLocale(url, locales, defaultLocale) {
388
565
  const segments = url.split("/").filter(Boolean);
@@ -397,6 +574,29 @@ function injectSSRForStatic(template, locale, head, css, html, serializedData) {
397
574
  '<script id="serialized-server-data" type="application/json">' + serializedData + "</script>"
398
575
  );
399
576
  }
577
+ function injectCSRShellForStatic(template, locale) {
578
+ return template.replace("<!--ssr-lang-->", locale).replace("<!--ssr-head-->", "").replace("<!--ssr-body-->", "").replace("<!--ssr-data-->", "");
579
+ }
580
+ function stripLocalePrefix(url, locales) {
581
+ const segments = url.split("/").filter(Boolean);
582
+ if (segments.length > 0 && locales.includes(segments[0])) {
583
+ const rest = segments.slice(1).join("/");
584
+ return rest ? `/${rest}` : "/";
585
+ }
586
+ return url;
587
+ }
588
+ function resolveRenderMode(routePath, routeRenderMode, renderModes) {
589
+ if (renderModes) {
590
+ if (renderModes[routePath]) return renderModes[routePath];
591
+ for (const [pattern, mode] of Object.entries(renderModes)) {
592
+ if (pattern.includes("*")) {
593
+ const re = new RegExp("^" + pattern.replace(/\*/g, ".*") + "$");
594
+ if (re.test(routePath)) return mode;
595
+ }
596
+ }
597
+ }
598
+ return routeRenderMode ?? "ssr";
599
+ }
400
600
 
401
601
  // ../server/src/adapters/vercel.ts
402
602
  function vercelAdapter() {
@@ -407,8 +607,8 @@ function vercelAdapter() {
407
607
  const outputDir = path.resolve(root, ".vercel/output");
408
608
  fs.rmSync(outputDir, { recursive: true, force: true });
409
609
  const entrySource = generateSSREntry(ctx, {
410
- platformImport: `import { handle } from "hono/vercel";`,
411
- platformExport: `export default handle(app);`
610
+ platformImport: `import { getRequestListener } from "@hono/node-server";`,
611
+ platformExport: `export default getRequestListener(app.fetch);`
412
612
  });
413
613
  const tempEntry = path.resolve(root, ".vercel-entry.tmp.mjs");
414
614
  fs.writeFileSync(tempEntry, entrySource);
@@ -455,6 +655,13 @@ function vercelAdapter() {
455
655
  } finally {
456
656
  fs.rmSync(tempEntry, { force: true });
457
657
  }
658
+ const prerendered = await prerenderRoutes(ctx);
659
+ const staticDir = path.resolve(root, ".vercel/output/static");
660
+ for (const { url, html } of prerendered) {
661
+ const filePath = url === "/" ? path.join(staticDir, "index.html") : path.join(staticDir, url, "index.html");
662
+ fs.mkdirSync(path.resolve(filePath, ".."), { recursive: true });
663
+ fs.writeFileSync(filePath, html);
664
+ }
458
665
  console.log(" Vercel output \u2192 .vercel/output/\n");
459
666
  }
460
667
  };
@@ -720,6 +927,18 @@ function resolveSetupFn(mod) {
720
927
  const first = Object.values(mod).find((v) => typeof v === "function");
721
928
  return first ?? null;
722
929
  }
930
+ function matchRenderModeConfig(url, renderModes) {
931
+ if (!renderModes) return null;
932
+ const path = url.split("?")[0];
933
+ if (renderModes[path]) return renderModes[path];
934
+ for (const [pattern, mode] of Object.entries(renderModes)) {
935
+ if (pattern.includes("*")) {
936
+ const re = new RegExp("^" + pattern.replace(/\*/g, ".*") + "$");
937
+ if (re.test(path)) return mode;
938
+ }
939
+ }
940
+ return null;
941
+ }
723
942
  function finesoftFrontViteConfig(options = {}) {
724
943
  const ssrEntry = options.ssr?.entry ?? "src/ssr.ts";
725
944
  let root = process.cwd();
@@ -752,7 +971,7 @@ function finesoftFrontViteConfig(options = {}) {
752
971
  /* @vite-ignore */
753
972
  "hono"
754
973
  );
755
- const { createSSRApp: createSSRApp2 } = await import("./app-MYBG3TGV.js");
974
+ const { createSSRApp: createSSRApp2 } = await import("./app-Z3EFLAP2.js");
756
975
  const { getRequestListener } = await import(
757
976
  /* @vite-ignore */
758
977
  "@hono/node-server"
@@ -799,9 +1018,9 @@ function finesoftFrontViteConfig(options = {}) {
799
1018
  /* @vite-ignore */
800
1019
  "hono"
801
1020
  );
802
- const { injectSSRContent: injectSSRContent2 } = await import(
1021
+ const { injectSSRContent: injectSSRContent2, injectCSRShell: injectCSRShell2 } = await import(
803
1022
  /* @vite-ignore */
804
- "./src-7D236CLJ.js"
1023
+ "./src-KUWACLPD.js"
805
1024
  );
806
1025
  const { parseAcceptLanguage: parseAcceptLanguage2 } = await import("./locale-YK3THSI6.js");
807
1026
  const { getRequestListener } = await import(
@@ -809,6 +1028,7 @@ function finesoftFrontViteConfig(options = {}) {
809
1028
  "@hono/node-server"
810
1029
  );
811
1030
  const app = new HonoClass();
1031
+ const isrCache = /* @__PURE__ */ new Map();
812
1032
  if (typeof options.setup === "function") {
813
1033
  await options.setup(app);
814
1034
  } else if (typeof options.setup === "string") {
@@ -847,12 +1067,25 @@ function finesoftFrontViteConfig(options = {}) {
847
1067
  options.locales,
848
1068
  options.defaultLocale
849
1069
  );
1070
+ const overrideMode = matchRenderModeConfig(
1071
+ url,
1072
+ options.renderModes
1073
+ );
1074
+ if (overrideMode === "csr") {
1075
+ return c.html(injectCSRShell2(template, locale));
1076
+ }
1077
+ const cached = isrCache.get(url);
1078
+ if (cached) return c.html(cached);
850
1079
  const {
851
1080
  html: appHtml,
852
1081
  head,
853
1082
  css,
854
- serverData
1083
+ serverData,
1084
+ renderMode
855
1085
  } = await ssrModule.render(url, locale);
1086
+ if (renderMode === "csr") {
1087
+ return c.html(injectCSRShell2(template, locale));
1088
+ }
856
1089
  const serializedData = ssrModule.serializeServerData(serverData);
857
1090
  const finalHtml = injectSSRContent2({
858
1091
  template,
@@ -862,6 +1095,9 @@ function finesoftFrontViteConfig(options = {}) {
862
1095
  html: appHtml,
863
1096
  serializedData
864
1097
  });
1098
+ if (renderMode === "prerender" || overrideMode === "prerender") {
1099
+ isrCache.set(url, finalHtml);
1100
+ }
865
1101
  return c.html(finalHtml);
866
1102
  } catch (e) {
867
1103
  console.error("[SSR Preview Error]", e);
@@ -932,6 +1168,7 @@ function finesoftFrontViteConfig(options = {}) {
932
1168
  locales,
933
1169
  defaultLocale,
934
1170
  templateHtml,
1171
+ renderModes: options.renderModes,
935
1172
  resolvedResolve,
936
1173
  resolvedCss,
937
1174
  vite,
@@ -990,6 +1227,7 @@ export {
990
1227
  finesoftFrontViteConfig,
991
1228
  generateUuid,
992
1229
  getBaseUrl,
1230
+ injectCSRShell,
993
1231
  injectSSRContent,
994
1232
  isCompoundAction,
995
1233
  isExternalUrlAction,