@finesoft/front 0.1.20 → 0.1.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{app-Z3EFLAP2.js → app-BPO26FAD.js} +2 -2
- package/dist/{chunk-SN3OO3DU.js → chunk-IITKGRCO.js} +10 -2
- package/dist/chunk-IITKGRCO.js.map +1 -0
- package/dist/index.cjs +121 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +114 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-SN3OO3DU.js.map +0 -1
- /package/dist/{app-Z3EFLAP2.js.map → app-BPO26FAD.js.map} +0 -0
package/dist/index.d.cts
CHANGED
|
@@ -151,6 +151,25 @@ interface GenerateSSREntryOptions {
|
|
|
151
151
|
platformImport: string;
|
|
152
152
|
/** 平台特定的导出语句(如 `export default handle(app);`) */
|
|
153
153
|
platformExport: string;
|
|
154
|
+
/**
|
|
155
|
+
* 平台特定的 ISR 缓存实现代码(可选)。
|
|
156
|
+
* 需提供 `async function platformCacheGet(url)` 返回 string|null,
|
|
157
|
+
* 和 `async function platformCacheSet(url, html)` 的函数定义。
|
|
158
|
+
* 不提供时使用内置的内存 Map 缓存。
|
|
159
|
+
*/
|
|
160
|
+
platformCache?: string;
|
|
161
|
+
/**
|
|
162
|
+
* 平台特定的响应后处理代码(可选)。
|
|
163
|
+
* 在 prerender 路由返回前执行,可用于设置 CDN 缓存头等。
|
|
164
|
+
* 代码中可使用变量 `c`(Hono Context)。
|
|
165
|
+
*/
|
|
166
|
+
platformPrerenderResponseHook?: string;
|
|
167
|
+
/**
|
|
168
|
+
* 平台特定的中间件代码(可选)。
|
|
169
|
+
* 插入在 catch-all GET 路由之前,可用于添加静态文件服务等。
|
|
170
|
+
* 代码中可使用变量 `app`(Hono 实例)。
|
|
171
|
+
*/
|
|
172
|
+
platformMiddleware?: string;
|
|
154
173
|
}
|
|
155
174
|
interface BuildBundleOptions {
|
|
156
175
|
/** 临时入口文件路径 */
|
package/dist/index.d.ts
CHANGED
|
@@ -151,6 +151,25 @@ interface GenerateSSREntryOptions {
|
|
|
151
151
|
platformImport: string;
|
|
152
152
|
/** 平台特定的导出语句(如 `export default handle(app);`) */
|
|
153
153
|
platformExport: string;
|
|
154
|
+
/**
|
|
155
|
+
* 平台特定的 ISR 缓存实现代码(可选)。
|
|
156
|
+
* 需提供 `async function platformCacheGet(url)` 返回 string|null,
|
|
157
|
+
* 和 `async function platformCacheSet(url, html)` 的函数定义。
|
|
158
|
+
* 不提供时使用内置的内存 Map 缓存。
|
|
159
|
+
*/
|
|
160
|
+
platformCache?: string;
|
|
161
|
+
/**
|
|
162
|
+
* 平台特定的响应后处理代码(可选)。
|
|
163
|
+
* 在 prerender 路由返回前执行,可用于设置 CDN 缓存头等。
|
|
164
|
+
* 代码中可使用变量 `c`(Hono Context)。
|
|
165
|
+
*/
|
|
166
|
+
platformPrerenderResponseHook?: string;
|
|
167
|
+
/**
|
|
168
|
+
* 平台特定的中间件代码(可选)。
|
|
169
|
+
* 插入在 catch-all GET 路由之前,可用于添加静态文件服务等。
|
|
170
|
+
* 代码中可使用变量 `app`(Hono 实例)。
|
|
171
|
+
*/
|
|
172
|
+
platformMiddleware?: string;
|
|
154
173
|
}
|
|
155
174
|
interface BuildBundleOptions {
|
|
156
175
|
/** 临时入口文件路径 */
|
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
} from "./chunk-H3RNYNSD.js";
|
|
11
11
|
import {
|
|
12
12
|
createSSRApp
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-IITKGRCO.js";
|
|
14
14
|
import {
|
|
15
15
|
SSR_PLACEHOLDERS,
|
|
16
16
|
createSSRRender,
|
|
@@ -77,6 +77,19 @@ function generateSSREntry(ctx, opts) {
|
|
|
77
77
|
const locales = JSON.stringify(ctx.locales);
|
|
78
78
|
const defaultLocale = JSON.stringify(ctx.defaultLocale);
|
|
79
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
|
+
}`;
|
|
80
93
|
return `
|
|
81
94
|
import { Hono } from "hono";
|
|
82
95
|
${opts.platformImport}
|
|
@@ -87,6 +100,7 @@ const TEMPLATE = ${JSON.stringify(ctx.templateHtml)};
|
|
|
87
100
|
const LOCALES = ${locales};
|
|
88
101
|
const DEFAULT_LOCALE = ${defaultLocale};
|
|
89
102
|
const RENDER_MODES = ${renderModes};
|
|
103
|
+
${cacheImpl}
|
|
90
104
|
|
|
91
105
|
function parseAcceptLanguage(header) {
|
|
92
106
|
if (!header) return DEFAULT_LOCALE;
|
|
@@ -129,10 +143,9 @@ function matchRenderMode(url) {
|
|
|
129
143
|
return null;
|
|
130
144
|
}
|
|
131
145
|
|
|
132
|
-
const isrCache = new Map();
|
|
133
|
-
|
|
134
146
|
const app = new Hono();
|
|
135
147
|
${setupCall}
|
|
148
|
+
${opts.platformMiddleware ?? ""}
|
|
136
149
|
|
|
137
150
|
app.get("*", async (c) => {
|
|
138
151
|
const url = c.req.path + (c.req.url.includes("?") ? "?" + c.req.url.split("?")[1] : "");
|
|
@@ -146,7 +159,7 @@ app.get("*", async (c) => {
|
|
|
146
159
|
}
|
|
147
160
|
|
|
148
161
|
// ISR \u7F13\u5B58\u547D\u4E2D
|
|
149
|
-
const cached =
|
|
162
|
+
const cached = await platformCacheGet(url);
|
|
150
163
|
if (cached) return c.html(cached);
|
|
151
164
|
|
|
152
165
|
const { html: appHtml, head, css, serverData, renderMode } = await render(url, locale);
|
|
@@ -161,7 +174,8 @@ app.get("*", async (c) => {
|
|
|
161
174
|
|
|
162
175
|
// Prerender ISR \u7F13\u5B58\uFF08\u5305\u62EC Vite \u914D\u7F6E\u8986\u76D6\u548C\u8DEF\u7531\u7EA7\uFF09
|
|
163
176
|
if (renderMode === "prerender" || overrideMode === "prerender") {
|
|
164
|
-
|
|
177
|
+
await platformCacheSet(url, finalHtml);
|
|
178
|
+
${opts.platformPrerenderResponseHook ?? ""}
|
|
165
179
|
}
|
|
166
180
|
|
|
167
181
|
return c.html(finalHtml);
|
|
@@ -297,7 +311,29 @@ function cloudflareAdapter() {
|
|
|
297
311
|
fs.rmSync(outputDir, { recursive: true, force: true });
|
|
298
312
|
const entrySource = generateSSREntry(ctx, {
|
|
299
313
|
platformImport: ``,
|
|
300
|
-
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
|
+
}`
|
|
301
337
|
});
|
|
302
338
|
const tempEntry = path.resolve(root, ".cf-entry.tmp.mjs");
|
|
303
339
|
fs.writeFileSync(tempEntry, entrySource);
|
|
@@ -343,7 +379,25 @@ function netlifyAdapter() {
|
|
|
343
379
|
});
|
|
344
380
|
const entrySource = generateSSREntry(ctx, {
|
|
345
381
|
platformImport: `import { handle } from "hono/netlify";`,
|
|
346
|
-
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");`
|
|
347
401
|
});
|
|
348
402
|
const tempEntry = path.resolve(root, ".netlify-entry.tmp.mjs");
|
|
349
403
|
fs.writeFileSync(tempEntry, entrySource);
|
|
@@ -383,7 +437,31 @@ function nodeAdapter() {
|
|
|
383
437
|
async build(ctx) {
|
|
384
438
|
const { fs, path, root } = ctx;
|
|
385
439
|
const entrySource = generateSSREntry(ctx, {
|
|
386
|
-
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
|
+
`,
|
|
387
465
|
platformExport: `
|
|
388
466
|
const port = +(process.env.PORT || 3000);
|
|
389
467
|
serve({ fetch: app.fetch, port }, (info) => {
|
|
@@ -645,7 +723,7 @@ function vercelAdapter() {
|
|
|
645
723
|
version: 3,
|
|
646
724
|
routes: [
|
|
647
725
|
{ handle: "filesystem" },
|
|
648
|
-
{ src: "/(.*)", dest: "/ssr" }
|
|
726
|
+
{ src: "/(.*)", dest: "/ssr/$1" }
|
|
649
727
|
]
|
|
650
728
|
},
|
|
651
729
|
null,
|
|
@@ -662,6 +740,32 @@ function vercelAdapter() {
|
|
|
662
740
|
fs.mkdirSync(path.resolve(filePath, ".."), { recursive: true });
|
|
663
741
|
fs.writeFileSync(filePath, html);
|
|
664
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
|
+
}
|
|
665
769
|
console.log(" Vercel output \u2192 .vercel/output/\n");
|
|
666
770
|
}
|
|
667
771
|
};
|
|
@@ -971,7 +1075,7 @@ function finesoftFrontViteConfig(options = {}) {
|
|
|
971
1075
|
/* @vite-ignore */
|
|
972
1076
|
"hono"
|
|
973
1077
|
);
|
|
974
|
-
const { createSSRApp: createSSRApp2 } = await import("./app-
|
|
1078
|
+
const { createSSRApp: createSSRApp2 } = await import("./app-BPO26FAD.js");
|
|
975
1079
|
const { getRequestListener } = await import(
|
|
976
1080
|
/* @vite-ignore */
|
|
977
1081
|
"@hono/node-server"
|