@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
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createSSRApp
|
|
3
|
-
} from "./chunk-
|
|
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-
|
|
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
|
-
|
|
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-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
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,
|