@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.
- 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 +151 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +26 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +144 -34
- 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
|
@@ -118,6 +118,8 @@ interface AdapterContext {
|
|
|
118
118
|
ssrEntry: string;
|
|
119
119
|
/** setup 文件相对路径(如 "src/proxies.ts"),仅当 options.setup 为 string 时有值 */
|
|
120
120
|
setupPath?: string;
|
|
121
|
+
/** 路由定义入口文件(用于预渲染时加载路由),如 "src/lib/bootstrap.ts" */
|
|
122
|
+
bootstrapEntry?: string;
|
|
121
123
|
/** 支持的语言列表 */
|
|
122
124
|
locales: string[];
|
|
123
125
|
/** 默认语言 */
|
|
@@ -151,6 +153,25 @@ interface GenerateSSREntryOptions {
|
|
|
151
153
|
platformImport: string;
|
|
152
154
|
/** 平台特定的导出语句(如 `export default handle(app);`) */
|
|
153
155
|
platformExport: string;
|
|
156
|
+
/**
|
|
157
|
+
* 平台特定的 ISR 缓存实现代码(可选)。
|
|
158
|
+
* 需提供 `async function platformCacheGet(url)` 返回 string|null,
|
|
159
|
+
* 和 `async function platformCacheSet(url, html)` 的函数定义。
|
|
160
|
+
* 不提供时使用内置的内存 Map 缓存。
|
|
161
|
+
*/
|
|
162
|
+
platformCache?: string;
|
|
163
|
+
/**
|
|
164
|
+
* 平台特定的响应后处理代码(可选)。
|
|
165
|
+
* 在 prerender 路由返回前执行,可用于设置 CDN 缓存头等。
|
|
166
|
+
* 代码中可使用变量 `c`(Hono Context)。
|
|
167
|
+
*/
|
|
168
|
+
platformPrerenderResponseHook?: string;
|
|
169
|
+
/**
|
|
170
|
+
* 平台特定的中间件代码(可选)。
|
|
171
|
+
* 插入在 catch-all GET 路由之前,可用于添加静态文件服务等。
|
|
172
|
+
* 代码中可使用变量 `app`(Hono 实例)。
|
|
173
|
+
*/
|
|
174
|
+
platformMiddleware?: string;
|
|
154
175
|
}
|
|
155
176
|
interface BuildBundleOptions {
|
|
156
177
|
/** 临时入口文件路径 */
|
|
@@ -440,6 +461,11 @@ interface FinesoftFrontViteOptions {
|
|
|
440
461
|
* ```
|
|
441
462
|
*/
|
|
442
463
|
renderModes?: Record<string, "ssr" | "csr" | "prerender">;
|
|
464
|
+
/**
|
|
465
|
+
* 路由定义入口文件(用于预渲染时加载路由),默认 "src/lib/bootstrap.ts"。
|
|
466
|
+
* 如果项目不使用声明式路由定义,可以不设置。
|
|
467
|
+
*/
|
|
468
|
+
bootstrapEntry?: string;
|
|
443
469
|
}
|
|
444
470
|
declare function finesoftFrontViteConfig(options?: FinesoftFrontViteOptions): {
|
|
445
471
|
name: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -118,6 +118,8 @@ interface AdapterContext {
|
|
|
118
118
|
ssrEntry: string;
|
|
119
119
|
/** setup 文件相对路径(如 "src/proxies.ts"),仅当 options.setup 为 string 时有值 */
|
|
120
120
|
setupPath?: string;
|
|
121
|
+
/** 路由定义入口文件(用于预渲染时加载路由),如 "src/lib/bootstrap.ts" */
|
|
122
|
+
bootstrapEntry?: string;
|
|
121
123
|
/** 支持的语言列表 */
|
|
122
124
|
locales: string[];
|
|
123
125
|
/** 默认语言 */
|
|
@@ -151,6 +153,25 @@ interface GenerateSSREntryOptions {
|
|
|
151
153
|
platformImport: string;
|
|
152
154
|
/** 平台特定的导出语句(如 `export default handle(app);`) */
|
|
153
155
|
platformExport: string;
|
|
156
|
+
/**
|
|
157
|
+
* 平台特定的 ISR 缓存实现代码(可选)。
|
|
158
|
+
* 需提供 `async function platformCacheGet(url)` 返回 string|null,
|
|
159
|
+
* 和 `async function platformCacheSet(url, html)` 的函数定义。
|
|
160
|
+
* 不提供时使用内置的内存 Map 缓存。
|
|
161
|
+
*/
|
|
162
|
+
platformCache?: string;
|
|
163
|
+
/**
|
|
164
|
+
* 平台特定的响应后处理代码(可选)。
|
|
165
|
+
* 在 prerender 路由返回前执行,可用于设置 CDN 缓存头等。
|
|
166
|
+
* 代码中可使用变量 `c`(Hono Context)。
|
|
167
|
+
*/
|
|
168
|
+
platformPrerenderResponseHook?: string;
|
|
169
|
+
/**
|
|
170
|
+
* 平台特定的中间件代码(可选)。
|
|
171
|
+
* 插入在 catch-all GET 路由之前,可用于添加静态文件服务等。
|
|
172
|
+
* 代码中可使用变量 `app`(Hono 实例)。
|
|
173
|
+
*/
|
|
174
|
+
platformMiddleware?: string;
|
|
154
175
|
}
|
|
155
176
|
interface BuildBundleOptions {
|
|
156
177
|
/** 临时入口文件路径 */
|
|
@@ -440,6 +461,11 @@ interface FinesoftFrontViteOptions {
|
|
|
440
461
|
* ```
|
|
441
462
|
*/
|
|
442
463
|
renderModes?: Record<string, "ssr" | "csr" | "prerender">;
|
|
464
|
+
/**
|
|
465
|
+
* 路由定义入口文件(用于预渲染时加载路由),默认 "src/lib/bootstrap.ts"。
|
|
466
|
+
* 如果项目不使用声明式路由定义,可以不设置。
|
|
467
|
+
*/
|
|
468
|
+
bootstrapEntry?: string;
|
|
443
469
|
}
|
|
444
470
|
declare function finesoftFrontViteConfig(options?: FinesoftFrontViteOptions): {
|
|
445
471
|
name: string;
|
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);
|
|
@@ -203,35 +217,40 @@ function copyStaticAssets(ctx, destDir, opts) {
|
|
|
203
217
|
fs.rmSync(path.join(destDir, "index.html"), { force: true });
|
|
204
218
|
}
|
|
205
219
|
}
|
|
206
|
-
async function prerenderRoutes(ctx
|
|
220
|
+
async function prerenderRoutes(ctx) {
|
|
207
221
|
const { fs, path, root, vite } = ctx;
|
|
208
222
|
const { pathToFileURL } = await import(
|
|
209
223
|
/* @vite-ignore */
|
|
210
224
|
"url"
|
|
211
225
|
);
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
226
|
+
const routesExport = ctx.bootstrapEntry ?? "src/lib/bootstrap.ts";
|
|
227
|
+
let routes = [];
|
|
228
|
+
const routesFileExists = fs.existsSync(path.resolve(root, routesExport));
|
|
229
|
+
if (routesFileExists) {
|
|
230
|
+
await vite.build({
|
|
231
|
+
root,
|
|
232
|
+
build: {
|
|
233
|
+
ssr: routesExport,
|
|
234
|
+
outDir: path.resolve(root, "dist/server"),
|
|
235
|
+
emptyOutDir: false,
|
|
236
|
+
rollupOptions: {
|
|
237
|
+
output: { entryFileNames: "_routes_prerender.mjs" }
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
resolve: ctx.resolvedResolve
|
|
241
|
+
});
|
|
242
|
+
const routesPath = pathToFileURL(
|
|
243
|
+
path.resolve(root, "dist/server/_routes_prerender.mjs")
|
|
244
|
+
).href;
|
|
245
|
+
const routesMod = await import(
|
|
246
|
+
/* @vite-ignore */
|
|
247
|
+
routesPath
|
|
248
|
+
);
|
|
249
|
+
routes = routesMod.routes ?? routesMod.default ?? [];
|
|
250
|
+
fs.rmSync(path.resolve(root, "dist/server/_routes_prerender.mjs"), {
|
|
251
|
+
force: true
|
|
252
|
+
});
|
|
253
|
+
}
|
|
235
254
|
const prerenderPaths = /* @__PURE__ */ new Set();
|
|
236
255
|
for (const r of routes) {
|
|
237
256
|
if (r.renderMode === "prerender" && r.path && !r.path.includes(":")) {
|
|
@@ -297,7 +316,29 @@ function cloudflareAdapter() {
|
|
|
297
316
|
fs.rmSync(outputDir, { recursive: true, force: true });
|
|
298
317
|
const entrySource = generateSSREntry(ctx, {
|
|
299
318
|
platformImport: ``,
|
|
300
|
-
platformExport: `export default app
|
|
319
|
+
platformExport: `export default app;`,
|
|
320
|
+
// Cloudflare Cache API — 持久化 ISR 缓存到 CDN 边缘节点
|
|
321
|
+
platformCache: `
|
|
322
|
+
const ISR_CACHE_TTL = 3600; // 1 hour
|
|
323
|
+
async function platformCacheGet(url) {
|
|
324
|
+
try {
|
|
325
|
+
const cache = caches.default;
|
|
326
|
+
const cacheKey = new Request("https://isr-cache/" + encodeURIComponent(url));
|
|
327
|
+
const resp = await cache.match(cacheKey);
|
|
328
|
+
if (resp) return await resp.text();
|
|
329
|
+
} catch {}
|
|
330
|
+
return null;
|
|
331
|
+
}
|
|
332
|
+
async function platformCacheSet(url, html) {
|
|
333
|
+
try {
|
|
334
|
+
const cache = caches.default;
|
|
335
|
+
const cacheKey = new Request("https://isr-cache/" + encodeURIComponent(url));
|
|
336
|
+
const resp = new Response(html, {
|
|
337
|
+
headers: { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "public, max-age=" + ISR_CACHE_TTL },
|
|
338
|
+
});
|
|
339
|
+
await cache.put(cacheKey, resp);
|
|
340
|
+
} catch {}
|
|
341
|
+
}`
|
|
301
342
|
});
|
|
302
343
|
const tempEntry = path.resolve(root, ".cf-entry.tmp.mjs");
|
|
303
344
|
fs.writeFileSync(tempEntry, entrySource);
|
|
@@ -343,7 +384,25 @@ function netlifyAdapter() {
|
|
|
343
384
|
});
|
|
344
385
|
const entrySource = generateSSREntry(ctx, {
|
|
345
386
|
platformImport: `import { handle } from "hono/netlify";`,
|
|
346
|
-
platformExport: `export default handle(app)
|
|
387
|
+
platformExport: `export default handle(app);
|
|
388
|
+
export const config = { path: "/*", preferStatic: true };`,
|
|
389
|
+
// Netlify CDN 缓存 — 使用 Netlify-CDN-Cache-Control 头做 ISR
|
|
390
|
+
platformCache: `
|
|
391
|
+
const ISR_SWR_TTL = 3600;
|
|
392
|
+
const ISR_CACHE_MAX = 1000;
|
|
393
|
+
const _isrMap = new Map();
|
|
394
|
+
async function platformCacheGet(url) {
|
|
395
|
+
return _isrMap.get(url) ?? null;
|
|
396
|
+
}
|
|
397
|
+
async function platformCacheSet(url, html) {
|
|
398
|
+
if (_isrMap.size >= ISR_CACHE_MAX) {
|
|
399
|
+
const first = _isrMap.keys().next().value;
|
|
400
|
+
_isrMap.delete(first);
|
|
401
|
+
}
|
|
402
|
+
_isrMap.set(url, html);
|
|
403
|
+
}`,
|
|
404
|
+
platformPrerenderResponseHook: `c.header("Cache-Control", "public, max-age=0, must-revalidate");
|
|
405
|
+
c.header("Netlify-CDN-Cache-Control", "public, max-age=" + ISR_SWR_TTL + ", stale-while-revalidate=" + ISR_SWR_TTL + ", durable");`
|
|
347
406
|
});
|
|
348
407
|
const tempEntry = path.resolve(root, ".netlify-entry.tmp.mjs");
|
|
349
408
|
fs.writeFileSync(tempEntry, entrySource);
|
|
@@ -383,7 +442,31 @@ function nodeAdapter() {
|
|
|
383
442
|
async build(ctx) {
|
|
384
443
|
const { fs, path, root } = ctx;
|
|
385
444
|
const entrySource = generateSSREntry(ctx, {
|
|
386
|
-
platformImport: `import { serve } from "@hono/node-server"
|
|
445
|
+
platformImport: `import { serve } from "@hono/node-server";
|
|
446
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
447
|
+
import { resolve, dirname } from "node:path";
|
|
448
|
+
import { fileURLToPath } from "node:url";`,
|
|
449
|
+
platformMiddleware: `
|
|
450
|
+
// \u9884\u6E32\u67D3\u6587\u4EF6\u4E2D\u95F4\u4EF6\uFF1A\u68C0\u67E5 dist/prerender/ \u4E0B\u662F\u5426\u6709\u5BF9\u5E94\u7684\u9759\u6001 HTML
|
|
451
|
+
const __entry_dirname = dirname(fileURLToPath(import.meta.url));
|
|
452
|
+
const prerenderDir = resolve(__entry_dirname, "../prerender");
|
|
453
|
+
|
|
454
|
+
app.use("*", async (c, next) => {
|
|
455
|
+
const urlPath = c.req.path;
|
|
456
|
+
const candidates = [
|
|
457
|
+
resolve(prerenderDir, "." + urlPath, "index.html"),
|
|
458
|
+
resolve(prerenderDir, "." + urlPath + ".html"),
|
|
459
|
+
];
|
|
460
|
+
if (urlPath === "/") candidates.unshift(resolve(prerenderDir, "index.html"));
|
|
461
|
+
for (const f of candidates) {
|
|
462
|
+
if (existsSync(f)) {
|
|
463
|
+
const html = readFileSync(f, "utf-8");
|
|
464
|
+
return c.html(html);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
await next();
|
|
468
|
+
});
|
|
469
|
+
`,
|
|
387
470
|
platformExport: `
|
|
388
471
|
const port = +(process.env.PORT || 3000);
|
|
389
472
|
serve({ fetch: app.fetch, port }, (info) => {
|
|
@@ -645,7 +728,7 @@ function vercelAdapter() {
|
|
|
645
728
|
version: 3,
|
|
646
729
|
routes: [
|
|
647
730
|
{ handle: "filesystem" },
|
|
648
|
-
{ src: "/(.*)", dest: "/ssr" }
|
|
731
|
+
{ src: "/(.*)", dest: "/ssr/$1" }
|
|
649
732
|
]
|
|
650
733
|
},
|
|
651
734
|
null,
|
|
@@ -662,6 +745,32 @@ function vercelAdapter() {
|
|
|
662
745
|
fs.mkdirSync(path.resolve(filePath, ".."), { recursive: true });
|
|
663
746
|
fs.writeFileSync(filePath, html);
|
|
664
747
|
}
|
|
748
|
+
if (prerendered.length > 0) {
|
|
749
|
+
const configPath = path.resolve(
|
|
750
|
+
root,
|
|
751
|
+
".vercel/output/config.json"
|
|
752
|
+
);
|
|
753
|
+
const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
754
|
+
config.overrides = config.overrides ?? {};
|
|
755
|
+
for (const { url } of prerendered) {
|
|
756
|
+
const key = url === "/" ? "index.html" : `${url.replace(/^\//, "")}/index.html`;
|
|
757
|
+
config.overrides[key] = {
|
|
758
|
+
path: url === "/" ? "/" : url,
|
|
759
|
+
contentType: "text/html; charset=utf-8"
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
const isrRoutes = prerendered.map(({ url }) => ({
|
|
763
|
+
src: `^${url.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}/?$`,
|
|
764
|
+
dest: url,
|
|
765
|
+
has: [{ type: "header", key: "x-vercel-isr" }]
|
|
766
|
+
}));
|
|
767
|
+
config.routes = [
|
|
768
|
+
...isrRoutes,
|
|
769
|
+
{ handle: "filesystem" },
|
|
770
|
+
{ src: "/(.*)", dest: "/ssr/$1" }
|
|
771
|
+
];
|
|
772
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
773
|
+
}
|
|
665
774
|
console.log(" Vercel output \u2192 .vercel/output/\n");
|
|
666
775
|
}
|
|
667
776
|
};
|
|
@@ -971,7 +1080,7 @@ function finesoftFrontViteConfig(options = {}) {
|
|
|
971
1080
|
/* @vite-ignore */
|
|
972
1081
|
"hono"
|
|
973
1082
|
);
|
|
974
|
-
const { createSSRApp: createSSRApp2 } = await import("./app-
|
|
1083
|
+
const { createSSRApp: createSSRApp2 } = await import("./app-BPO26FAD.js");
|
|
975
1084
|
const { getRequestListener } = await import(
|
|
976
1085
|
/* @vite-ignore */
|
|
977
1086
|
"@hono/node-server"
|
|
@@ -1165,6 +1274,7 @@ function finesoftFrontViteConfig(options = {}) {
|
|
|
1165
1274
|
root,
|
|
1166
1275
|
ssrEntry,
|
|
1167
1276
|
setupPath: typeof options.setup === "string" ? options.setup : void 0,
|
|
1277
|
+
bootstrapEntry: options.bootstrapEntry,
|
|
1168
1278
|
locales,
|
|
1169
1279
|
defaultLocale,
|
|
1170
1280
|
templateHtml,
|