@finesoft/front 0.1.20 → 0.1.22

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.
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  createSSRApp
3
- } from "./chunk-SN3OO3DU.js";
3
+ } from "./chunk-IITKGRCO.js";
4
4
  import "./chunk-5ZECBECP.js";
5
5
  import "./chunk-BUYWNNNQ.js";
6
6
  import "./chunk-FYP2ZYYV.js";
7
7
  export {
8
8
  createSSRApp
9
9
  };
10
- //# sourceMappingURL=app-Z3EFLAP2.js.map
10
+ //# sourceMappingURL=app-BPO26FAD.js.map
@@ -19,7 +19,15 @@ function createSSRApp(options) {
19
19
  defaultLocale
20
20
  } = options;
21
21
  const app = new Hono();
22
+ const ISR_CACHE_MAX = 1e3;
22
23
  const isrCache = /* @__PURE__ */ new Map();
24
+ function isrSet(key, val) {
25
+ if (isrCache.size >= ISR_CACHE_MAX) {
26
+ const first = isrCache.keys().next().value;
27
+ if (first !== void 0) isrCache.delete(first);
28
+ }
29
+ isrCache.set(key, val);
30
+ }
23
31
  async function readTemplate(url) {
24
32
  if (!isProduction && vite) {
25
33
  const { readFileSync: readFileSync2 } = await import(
@@ -100,7 +108,7 @@ function createSSRApp(options) {
100
108
  serializedData
101
109
  });
102
110
  if (renderMode === "prerender") {
103
- isrCache.set(cacheKey, finalHtml);
111
+ isrSet(cacheKey, finalHtml);
104
112
  }
105
113
  return c.html(finalHtml);
106
114
  } catch (e) {
@@ -117,4 +125,4 @@ function createSSRApp(options) {
117
125
  export {
118
126
  createSSRApp
119
127
  };
120
- //# sourceMappingURL=chunk-SN3OO3DU.js.map
128
+ //# sourceMappingURL=chunk-IITKGRCO.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../server/src/app.ts"],"sourcesContent":["/**\n * createSSRApp — 创建 Hono SSR 应用\n *\n * 提供 SSR 通配路由,读取模板、加载 SSR 模块、渲染。\n * 应用层可在此之上追加自定义路由(API 代理等)。\n */\n\nimport { injectCSRShell, injectSSRContent } from \"@finesoft/ssr\";\nimport { Hono } from \"hono\";\nimport type { ViteDevServer } from \"vite\";\nimport { parseAcceptLanguage } from \"./locale\";\n\nexport interface SSRModule {\n\trender: (\n\t\turl: string,\n\t\tlocale: string,\n\t) => Promise<{\n\t\thtml: string;\n\t\thead: string;\n\t\tcss: string;\n\t\tserverData: unknown;\n\t\trenderMode?: string;\n\t}>;\n\tserializeServerData: (data: unknown) => string;\n}\n\nexport interface SSRAppOptions {\n\t/** 项目根路径 */\n\troot: string;\n\t/** Vite dev server(仅开发模式) */\n\tvite?: ViteDevServer;\n\t/** 是否生产环境 */\n\tisProduction: boolean;\n\t/** SSR 入口文件路径(开发用,如 \"/src/ssr.ts\") */\n\tssrEntryPath?: string;\n\t/** 生产环境 SSR 模块路径(如 \"../dist/server/ssr.js\") */\n\tssrProductionModule?: string;\n\t/** 支持的语言列表 */\n\tsupportedLocales?: string[];\n\t/** 默认语言 */\n\tdefaultLocale?: string;\n}\n\nexport function createSSRApp(options: SSRAppOptions): Hono {\n\tconst {\n\t\troot,\n\t\tvite,\n\t\tisProduction,\n\t\tssrEntryPath = \"/src/ssr.ts\",\n\t\tssrProductionModule,\n\t\tsupportedLocales,\n\t\tdefaultLocale,\n\t} = options;\n\n\tconst app = new Hono();\n\n\t/** ISR 内存缓存(prerender 路由首次请求后缓存,LRU 驱逐) */\n\tconst ISR_CACHE_MAX = 1000;\n\tconst isrCache = new Map<string, string>();\n\tfunction isrSet(key: string, val: string) {\n\t\tif (isrCache.size >= ISR_CACHE_MAX) {\n\t\t\tconst first = isrCache.keys().next().value;\n\t\t\tif (first !== undefined) isrCache.delete(first);\n\t\t}\n\t\tisrCache.set(key, val);\n\t}\n\n\tasync function readTemplate(url: string): Promise<string> {\n\t\tif (!isProduction && vite) {\n\t\t\tconst { readFileSync } = await import(/* @vite-ignore */ \"node:fs\");\n\t\t\tconst { resolve } = await import(/* @vite-ignore */ \"node:path\");\n\t\t\tconst raw = readFileSync(resolve(root, \"index.html\"), \"utf-8\");\n\t\t\treturn vite.transformIndexHtml(url, raw);\n\t\t}\n\n\t\tconst isDeno = typeof (globalThis as any).Deno !== \"undefined\";\n\t\tif (isDeno) {\n\t\t\treturn (globalThis as any).Deno.readTextFileSync(\n\t\t\t\tnew URL(\"../dist/client/index.html\", import.meta.url),\n\t\t\t);\n\t\t}\n\n\t\tconst { readFileSync } = await import(/* @vite-ignore */ \"node:fs\");\n\t\tconst { resolve } = await import(/* @vite-ignore */ \"node:path\");\n\t\treturn readFileSync(resolve(root, \"dist/client/index.html\"), \"utf-8\");\n\t}\n\n\tasync function loadSSRModule(): Promise<SSRModule> {\n\t\tif (!isProduction && vite) {\n\t\t\treturn (await vite.ssrLoadModule(ssrEntryPath)) as SSRModule;\n\t\t}\n\t\tif (ssrProductionModule) {\n\t\t\treturn import(ssrProductionModule) as Promise<SSRModule>;\n\t\t}\n\t\tconst { resolve } = await import(/* @vite-ignore */ \"node:path\");\n\t\tconst { pathToFileURL } = await import(/* @vite-ignore */ \"node:url\");\n\t\tconst absPath = pathToFileURL(resolve(root, \"dist/server/ssr.js\")).href;\n\t\treturn import(absPath) as Promise<SSRModule>;\n\t}\n\n\tapp.get(\"*\", async (c) => {\n\t\tconst url =\n\t\t\tc.req.path +\n\t\t\t(c.req.url.includes(\"?\") ? \"?\" + c.req.url.split(\"?\")[1] : \"\");\n\n\t\ttry {\n\t\t\t// ISR 缓存命中\n\t\t\tconst cacheKey = url;\n\t\t\tconst cached = isrCache.get(cacheKey);\n\t\t\tif (cached) return c.html(cached);\n\n\t\t\tconst template = await readTemplate(url);\n\t\t\tconst { render, serializeServerData } = await loadSSRModule();\n\n\t\t\tconst locale = parseAcceptLanguage(\n\t\t\t\tc.req.header(\"accept-language\"),\n\t\t\t\tsupportedLocales,\n\t\t\t\tdefaultLocale,\n\t\t\t);\n\n\t\t\tconst {\n\t\t\t\thtml: appHtml,\n\t\t\t\thead,\n\t\t\t\tcss,\n\t\t\t\tserverData,\n\t\t\t\trenderMode,\n\t\t\t} = await render(url, locale);\n\n\t\t\t// CSR 模式:返回空壳 HTML\n\t\t\tif (renderMode === \"csr\") {\n\t\t\t\treturn c.html(injectCSRShell(template, locale));\n\t\t\t}\n\n\t\t\tconst serializedData = serializeServerData(serverData);\n\n\t\t\tconst finalHtml = injectSSRContent({\n\t\t\t\ttemplate,\n\t\t\t\tlocale,\n\t\t\t\thead,\n\t\t\t\tcss,\n\t\t\t\thtml: appHtml,\n\t\t\t\tserializedData,\n\t\t\t});\n\n\t\t\t// Prerender ISR 缓存\n\t\t\tif (renderMode === \"prerender\") {\n\t\t\t\tisrSet(cacheKey, finalHtml);\n\t\t\t}\n\n\t\t\treturn c.html(finalHtml);\n\t\t} catch (e) {\n\t\t\tif (!isProduction && vite) {\n\t\t\t\tvite.ssrFixStacktrace(e as Error);\n\t\t\t}\n\t\t\tconsole.error(\"[SSR Error]\", e);\n\t\t\treturn c.text(\"Internal Server Error\", 500);\n\t\t}\n\t});\n\n\treturn app;\n}\n"],"mappings":"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","names":["readFileSync","resolve"]}
package/dist/index.cjs CHANGED
@@ -1149,7 +1149,15 @@ function createSSRApp(options) {
1149
1149
  defaultLocale
1150
1150
  } = options;
1151
1151
  const app = new import_hono.Hono();
1152
+ const ISR_CACHE_MAX = 1e3;
1152
1153
  const isrCache = /* @__PURE__ */ new Map();
1154
+ function isrSet(key, val) {
1155
+ if (isrCache.size >= ISR_CACHE_MAX) {
1156
+ const first = isrCache.keys().next().value;
1157
+ if (first !== void 0) isrCache.delete(first);
1158
+ }
1159
+ isrCache.set(key, val);
1160
+ }
1153
1161
  async function readTemplate(url) {
1154
1162
  if (!isProduction && vite) {
1155
1163
  const { readFileSync: readFileSync2 } = await import(
@@ -1230,7 +1238,7 @@ function createSSRApp(options) {
1230
1238
  serializedData
1231
1239
  });
1232
1240
  if (renderMode === "prerender") {
1233
- isrCache.set(cacheKey, finalHtml);
1241
+ isrSet(cacheKey, finalHtml);
1234
1242
  }
1235
1243
  return c.html(finalHtml);
1236
1244
  } catch (e) {
@@ -1675,6 +1683,19 @@ function generateSSREntry(ctx, opts) {
1675
1683
  const locales = JSON.stringify(ctx.locales);
1676
1684
  const defaultLocale = JSON.stringify(ctx.defaultLocale);
1677
1685
  const renderModes = JSON.stringify(ctx.renderModes ?? {});
1686
+ const cacheImpl = opts.platformCache ? opts.platformCache : `
1687
+ const ISR_CACHE_MAX = 1000;
1688
+ const _isrMap = new Map();
1689
+ async function platformCacheGet(url) {
1690
+ return _isrMap.get(url) ?? null;
1691
+ }
1692
+ async function platformCacheSet(url, html) {
1693
+ if (_isrMap.size >= ISR_CACHE_MAX) {
1694
+ const first = _isrMap.keys().next().value;
1695
+ _isrMap.delete(first);
1696
+ }
1697
+ _isrMap.set(url, html);
1698
+ }`;
1678
1699
  return `
1679
1700
  import { Hono } from "hono";
1680
1701
  ${opts.platformImport}
@@ -1685,6 +1706,7 @@ const TEMPLATE = ${JSON.stringify(ctx.templateHtml)};
1685
1706
  const LOCALES = ${locales};
1686
1707
  const DEFAULT_LOCALE = ${defaultLocale};
1687
1708
  const RENDER_MODES = ${renderModes};
1709
+ ${cacheImpl}
1688
1710
 
1689
1711
  function parseAcceptLanguage(header) {
1690
1712
  if (!header) return DEFAULT_LOCALE;
@@ -1727,10 +1749,9 @@ function matchRenderMode(url) {
1727
1749
  return null;
1728
1750
  }
1729
1751
 
1730
- const isrCache = new Map();
1731
-
1732
1752
  const app = new Hono();
1733
1753
  ${setupCall}
1754
+ ${opts.platformMiddleware ?? ""}
1734
1755
 
1735
1756
  app.get("*", async (c) => {
1736
1757
  const url = c.req.path + (c.req.url.includes("?") ? "?" + c.req.url.split("?")[1] : "");
@@ -1744,7 +1765,7 @@ app.get("*", async (c) => {
1744
1765
  }
1745
1766
 
1746
1767
  // ISR \u7F13\u5B58\u547D\u4E2D
1747
- const cached = isrCache.get(url);
1768
+ const cached = await platformCacheGet(url);
1748
1769
  if (cached) return c.html(cached);
1749
1770
 
1750
1771
  const { html: appHtml, head, css, serverData, renderMode } = await render(url, locale);
@@ -1759,7 +1780,8 @@ app.get("*", async (c) => {
1759
1780
 
1760
1781
  // Prerender ISR \u7F13\u5B58\uFF08\u5305\u62EC Vite \u914D\u7F6E\u8986\u76D6\u548C\u8DEF\u7531\u7EA7\uFF09
1761
1782
  if (renderMode === "prerender" || overrideMode === "prerender") {
1762
- isrCache.set(url, finalHtml);
1783
+ await platformCacheSet(url, finalHtml);
1784
+ ${opts.platformPrerenderResponseHook ?? ""}
1763
1785
  }
1764
1786
 
1765
1787
  return c.html(finalHtml);
@@ -1801,35 +1823,40 @@ function copyStaticAssets(ctx, destDir, opts) {
1801
1823
  fs.rmSync(path.join(destDir, "index.html"), { force: true });
1802
1824
  }
1803
1825
  }
1804
- async function prerenderRoutes(ctx, routesExport = "src/lib/bootstrap.ts") {
1826
+ async function prerenderRoutes(ctx) {
1805
1827
  const { fs, path, root, vite } = ctx;
1806
1828
  const { pathToFileURL } = await import(
1807
1829
  /* @vite-ignore */
1808
1830
  "url"
1809
1831
  );
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
- });
1832
+ const routesExport = ctx.bootstrapEntry ?? "src/lib/bootstrap.ts";
1833
+ let routes = [];
1834
+ const routesFileExists = fs.existsSync(path.resolve(root, routesExport));
1835
+ if (routesFileExists) {
1836
+ await vite.build({
1837
+ root,
1838
+ build: {
1839
+ ssr: routesExport,
1840
+ outDir: path.resolve(root, "dist/server"),
1841
+ emptyOutDir: false,
1842
+ rollupOptions: {
1843
+ output: { entryFileNames: "_routes_prerender.mjs" }
1844
+ }
1845
+ },
1846
+ resolve: ctx.resolvedResolve
1847
+ });
1848
+ const routesPath = pathToFileURL(
1849
+ path.resolve(root, "dist/server/_routes_prerender.mjs")
1850
+ ).href;
1851
+ const routesMod = await import(
1852
+ /* @vite-ignore */
1853
+ routesPath
1854
+ );
1855
+ routes = routesMod.routes ?? routesMod.default ?? [];
1856
+ fs.rmSync(path.resolve(root, "dist/server/_routes_prerender.mjs"), {
1857
+ force: true
1858
+ });
1859
+ }
1833
1860
  const prerenderPaths = /* @__PURE__ */ new Set();
1834
1861
  for (const r of routes) {
1835
1862
  if (r.renderMode === "prerender" && r.path && !r.path.includes(":")) {
@@ -1895,7 +1922,29 @@ function cloudflareAdapter() {
1895
1922
  fs.rmSync(outputDir, { recursive: true, force: true });
1896
1923
  const entrySource = generateSSREntry(ctx, {
1897
1924
  platformImport: ``,
1898
- platformExport: `export default app;`
1925
+ platformExport: `export default app;`,
1926
+ // Cloudflare Cache API — 持久化 ISR 缓存到 CDN 边缘节点
1927
+ platformCache: `
1928
+ const ISR_CACHE_TTL = 3600; // 1 hour
1929
+ async function platformCacheGet(url) {
1930
+ try {
1931
+ const cache = caches.default;
1932
+ const cacheKey = new Request("https://isr-cache/" + encodeURIComponent(url));
1933
+ const resp = await cache.match(cacheKey);
1934
+ if (resp) return await resp.text();
1935
+ } catch {}
1936
+ return null;
1937
+ }
1938
+ async function platformCacheSet(url, html) {
1939
+ try {
1940
+ const cache = caches.default;
1941
+ const cacheKey = new Request("https://isr-cache/" + encodeURIComponent(url));
1942
+ const resp = new Response(html, {
1943
+ headers: { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "public, max-age=" + ISR_CACHE_TTL },
1944
+ });
1945
+ await cache.put(cacheKey, resp);
1946
+ } catch {}
1947
+ }`
1899
1948
  });
1900
1949
  const tempEntry = path.resolve(root, ".cf-entry.tmp.mjs");
1901
1950
  fs.writeFileSync(tempEntry, entrySource);
@@ -1941,7 +1990,25 @@ function netlifyAdapter() {
1941
1990
  });
1942
1991
  const entrySource = generateSSREntry(ctx, {
1943
1992
  platformImport: `import { handle } from "hono/netlify";`,
1944
- platformExport: `export default handle(app);`
1993
+ platformExport: `export default handle(app);
1994
+ export const config = { path: "/*", preferStatic: true };`,
1995
+ // Netlify CDN 缓存 — 使用 Netlify-CDN-Cache-Control 头做 ISR
1996
+ platformCache: `
1997
+ const ISR_SWR_TTL = 3600;
1998
+ const ISR_CACHE_MAX = 1000;
1999
+ const _isrMap = new Map();
2000
+ async function platformCacheGet(url) {
2001
+ return _isrMap.get(url) ?? null;
2002
+ }
2003
+ async function platformCacheSet(url, html) {
2004
+ if (_isrMap.size >= ISR_CACHE_MAX) {
2005
+ const first = _isrMap.keys().next().value;
2006
+ _isrMap.delete(first);
2007
+ }
2008
+ _isrMap.set(url, html);
2009
+ }`,
2010
+ platformPrerenderResponseHook: `c.header("Cache-Control", "public, max-age=0, must-revalidate");
2011
+ c.header("Netlify-CDN-Cache-Control", "public, max-age=" + ISR_SWR_TTL + ", stale-while-revalidate=" + ISR_SWR_TTL + ", durable");`
1945
2012
  });
1946
2013
  const tempEntry = path.resolve(root, ".netlify-entry.tmp.mjs");
1947
2014
  fs.writeFileSync(tempEntry, entrySource);
@@ -1981,7 +2048,31 @@ function nodeAdapter() {
1981
2048
  async build(ctx) {
1982
2049
  const { fs, path, root } = ctx;
1983
2050
  const entrySource = generateSSREntry(ctx, {
1984
- platformImport: `import { serve } from "@hono/node-server";`,
2051
+ platformImport: `import { serve } from "@hono/node-server";
2052
+ import { readFileSync, existsSync } from "node:fs";
2053
+ import { resolve, dirname } from "node:path";
2054
+ import { fileURLToPath } from "node:url";`,
2055
+ platformMiddleware: `
2056
+ // \u9884\u6E32\u67D3\u6587\u4EF6\u4E2D\u95F4\u4EF6\uFF1A\u68C0\u67E5 dist/prerender/ \u4E0B\u662F\u5426\u6709\u5BF9\u5E94\u7684\u9759\u6001 HTML
2057
+ const __entry_dirname = dirname(fileURLToPath(import.meta.url));
2058
+ const prerenderDir = resolve(__entry_dirname, "../prerender");
2059
+
2060
+ app.use("*", async (c, next) => {
2061
+ const urlPath = c.req.path;
2062
+ const candidates = [
2063
+ resolve(prerenderDir, "." + urlPath, "index.html"),
2064
+ resolve(prerenderDir, "." + urlPath + ".html"),
2065
+ ];
2066
+ if (urlPath === "/") candidates.unshift(resolve(prerenderDir, "index.html"));
2067
+ for (const f of candidates) {
2068
+ if (existsSync(f)) {
2069
+ const html = readFileSync(f, "utf-8");
2070
+ return c.html(html);
2071
+ }
2072
+ }
2073
+ await next();
2074
+ });
2075
+ `,
1985
2076
  platformExport: `
1986
2077
  const port = +(process.env.PORT || 3000);
1987
2078
  serve({ fetch: app.fetch, port }, (info) => {
@@ -2243,7 +2334,7 @@ function vercelAdapter() {
2243
2334
  version: 3,
2244
2335
  routes: [
2245
2336
  { handle: "filesystem" },
2246
- { src: "/(.*)", dest: "/ssr" }
2337
+ { src: "/(.*)", dest: "/ssr/$1" }
2247
2338
  ]
2248
2339
  },
2249
2340
  null,
@@ -2260,6 +2351,32 @@ function vercelAdapter() {
2260
2351
  fs.mkdirSync(path.resolve(filePath, ".."), { recursive: true });
2261
2352
  fs.writeFileSync(filePath, html);
2262
2353
  }
2354
+ if (prerendered.length > 0) {
2355
+ const configPath = path.resolve(
2356
+ root,
2357
+ ".vercel/output/config.json"
2358
+ );
2359
+ const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
2360
+ config.overrides = config.overrides ?? {};
2361
+ for (const { url } of prerendered) {
2362
+ const key = url === "/" ? "index.html" : `${url.replace(/^\//, "")}/index.html`;
2363
+ config.overrides[key] = {
2364
+ path: url === "/" ? "/" : url,
2365
+ contentType: "text/html; charset=utf-8"
2366
+ };
2367
+ }
2368
+ const isrRoutes = prerendered.map(({ url }) => ({
2369
+ src: `^${url.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}/?$`,
2370
+ dest: url,
2371
+ has: [{ type: "header", key: "x-vercel-isr" }]
2372
+ }));
2373
+ config.routes = [
2374
+ ...isrRoutes,
2375
+ { handle: "filesystem" },
2376
+ { src: "/(.*)", dest: "/ssr/$1" }
2377
+ ];
2378
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
2379
+ }
2263
2380
  console.log(" Vercel output \u2192 .vercel/output/\n");
2264
2381
  }
2265
2382
  };
@@ -2767,6 +2884,7 @@ function finesoftFrontViteConfig(options = {}) {
2767
2884
  root,
2768
2885
  ssrEntry,
2769
2886
  setupPath: typeof options.setup === "string" ? options.setup : void 0,
2887
+ bootstrapEntry: options.bootstrapEntry,
2770
2888
  locales,
2771
2889
  defaultLocale,
2772
2890
  templateHtml,