vike-lite 1.15.8 → 1.15.10
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/__internal/shared.d.mts +8 -8
- package/dist/__internal/shared.mjs +54 -2
- package/dist/client/router.mjs +2 -4
- package/dist/server.mjs +215 -1
- package/dist/vite.mjs +8 -11
- package/package.json +1 -1
- package/dist/matchRoute-nTNPxoMq.mjs +0 -44
- package/dist/server-DSrh6B3f.mjs +0 -222
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
//#region src/__internal/shared
|
|
2
|
-
declare function matchRoute(urlPathname: string, routes: typeof import('virtual:vike-lite/routes').routes): {
|
|
3
|
-
route: any;
|
|
4
|
-
routeParams: Record<string, string>;
|
|
5
|
-
} | null;
|
|
6
|
-
//#endregion
|
|
7
|
-
//#region src/__internal/shared/index.d.ts
|
|
1
|
+
//#region src/__internal/shared.d.ts
|
|
8
2
|
interface RenderContext {
|
|
9
3
|
pageContext: any;
|
|
10
4
|
Page: unknown;
|
|
@@ -19,5 +13,11 @@ interface RenderContext {
|
|
|
19
13
|
};
|
|
20
14
|
nonce?: string;
|
|
21
15
|
}
|
|
16
|
+
declare function matchRoute(urlPathname: string, routes: typeof import('virtual:vike-lite/routes').routes): {
|
|
17
|
+
route: any;
|
|
18
|
+
routeParams: Record<string, string>;
|
|
19
|
+
} | null;
|
|
20
|
+
declare const BASE_URL: string;
|
|
21
|
+
declare function stripBase(pathname: string): string;
|
|
22
22
|
//#endregion
|
|
23
|
-
export { RenderContext, matchRoute };
|
|
23
|
+
export { BASE_URL, RenderContext, matchRoute, stripBase };
|
|
@@ -1,2 +1,54 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
//#region src/__internal/shared.ts
|
|
2
|
+
const regexCache = /* @__PURE__ */ new Map();
|
|
3
|
+
function escapeRegex(str) {
|
|
4
|
+
return str.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
|
|
5
|
+
}
|
|
6
|
+
function matchRoute(urlPathname, routes) {
|
|
7
|
+
for (const route of routes) {
|
|
8
|
+
if (!route.path.includes(":")) {
|
|
9
|
+
if (route.path === urlPathname || route.path + "/" === urlPathname) return {
|
|
10
|
+
route,
|
|
11
|
+
routeParams: {}
|
|
12
|
+
};
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
let compiled = regexCache.get(route.path);
|
|
16
|
+
if (!compiled) {
|
|
17
|
+
const paramNames = [];
|
|
18
|
+
const regexPath = route.path.split("/").map((segment) => {
|
|
19
|
+
if (segment.startsWith(":")) {
|
|
20
|
+
paramNames.push(segment.slice(1));
|
|
21
|
+
return "([^/]+)";
|
|
22
|
+
}
|
|
23
|
+
return escapeRegex(segment);
|
|
24
|
+
}).join("/");
|
|
25
|
+
compiled = {
|
|
26
|
+
regex: new RegExp(`^${regexPath}/?$`),
|
|
27
|
+
paramNames
|
|
28
|
+
};
|
|
29
|
+
regexCache.set(route.path, compiled);
|
|
30
|
+
}
|
|
31
|
+
const match = urlPathname.match(compiled.regex);
|
|
32
|
+
if (match) {
|
|
33
|
+
const routeParams = {};
|
|
34
|
+
for (let i = 0; i < compiled.paramNames.length; i++) routeParams[compiled.paramNames[i]] = decodeURIComponent(match[i + 1]);
|
|
35
|
+
return {
|
|
36
|
+
route,
|
|
37
|
+
routeParams
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
const BASE_URL = (() => {
|
|
44
|
+
const { BASE_URL } = import.meta.env;
|
|
45
|
+
return BASE_URL.endsWith("/") ? BASE_URL.slice(0, -1) : BASE_URL;
|
|
46
|
+
})();
|
|
47
|
+
function stripBase(pathname) {
|
|
48
|
+
if (BASE_URL === "") return pathname;
|
|
49
|
+
if (pathname === BASE_URL) return "/";
|
|
50
|
+
if (pathname.startsWith(BASE_URL + "/")) return pathname.slice(BASE_URL.length);
|
|
51
|
+
return pathname;
|
|
52
|
+
}
|
|
53
|
+
//#endregion
|
|
54
|
+
export { BASE_URL, matchRoute, stripBase };
|
package/dist/client/router.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { BASE_URL } from "../__internal/shared.mjs";
|
|
1
2
|
//#region src/client/router.ts
|
|
2
3
|
/**
|
|
3
4
|
* Change page programmatically on the client without reloading the browser.
|
|
@@ -7,10 +8,7 @@
|
|
|
7
8
|
function navigate(url, options) {
|
|
8
9
|
if (typeof globalThis === "undefined") throw new Error("navigate() can only be called on the client side.");
|
|
9
10
|
let finalUrl = url;
|
|
10
|
-
if (finalUrl.startsWith("/"))
|
|
11
|
-
const { BASE_URL } = import.meta.env;
|
|
12
|
-
finalUrl = (BASE_URL.endsWith("/") ? BASE_URL.slice(0, -1) : BASE_URL) + (finalUrl === "/" ? "" : finalUrl);
|
|
13
|
-
}
|
|
11
|
+
if (finalUrl.startsWith("/")) finalUrl = BASE_URL + (finalUrl === "/" ? "" : finalUrl);
|
|
14
12
|
globalThis.history.pushState({ triggeredBy: "vike-lite" }, "", finalUrl);
|
|
15
13
|
globalThis.dispatchEvent(new CustomEvent("vike-navigate", { detail: {
|
|
16
14
|
keepScrollPosition: options?.keepScrollPosition,
|
package/dist/server.mjs
CHANGED
|
@@ -1,2 +1,216 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BASE_URL, matchRoute, stripBase } from "./__internal/shared.mjs";
|
|
2
|
+
import { AbortRedirect, AbortRender } from "./server/abort.mjs";
|
|
3
|
+
import { n as store } from "./store-BCs8gL_o.mjs";
|
|
4
|
+
//#region src/utils/serializeContext.ts
|
|
5
|
+
const ESCAPE_LOOKUP = {
|
|
6
|
+
"&": String.raw`\u0026`,
|
|
7
|
+
">": String.raw`\u003e`,
|
|
8
|
+
"<": String.raw`\u003c`,
|
|
9
|
+
"\u2028": String.raw`\u2028`,
|
|
10
|
+
"\u2029": String.raw`\u2029`
|
|
11
|
+
};
|
|
12
|
+
const ESCAPE_REGEX = /[&><\u{2028}\u{2029}]/gu;
|
|
13
|
+
function serializeContext(data) {
|
|
14
|
+
return JSON.stringify(data).replaceAll(ESCAPE_REGEX, (match) => ESCAPE_LOOKUP[match]);
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/server/renderPage.ts
|
|
18
|
+
const isProd = process.env.NODE_ENV === "production";
|
|
19
|
+
function withBase(file) {
|
|
20
|
+
return `${BASE_URL === "" ? "" : BASE_URL}${file.startsWith("/") ? file : `/${file}`}`;
|
|
21
|
+
}
|
|
22
|
+
const assetsCache = /* @__PURE__ */ new WeakMap();
|
|
23
|
+
function computeAssetFiles(route) {
|
|
24
|
+
const cssFiles = /* @__PURE__ */ new Set();
|
|
25
|
+
const jsFiles = /* @__PURE__ */ new Map();
|
|
26
|
+
const { manifest } = store;
|
|
27
|
+
function getVirtualEntryClientKey() {
|
|
28
|
+
for (const key in manifest) if (manifest[key].isEntry) return key;
|
|
29
|
+
throw new Error("entry-client not found in manifest");
|
|
30
|
+
}
|
|
31
|
+
function collectAssets(key, isCritical) {
|
|
32
|
+
const chunk = manifest[key];
|
|
33
|
+
if (!chunk) return;
|
|
34
|
+
const current = jsFiles.get(chunk.file);
|
|
35
|
+
if (current === true) return;
|
|
36
|
+
if (current === false && !isCritical) return;
|
|
37
|
+
jsFiles.set(chunk.file, isCritical);
|
|
38
|
+
if (chunk.css) for (const css of chunk.css) cssFiles.add(css);
|
|
39
|
+
if (chunk.imports) for (const imp of chunk.imports) collectAssets(imp, false);
|
|
40
|
+
}
|
|
41
|
+
const virtualEntryClientKey = getVirtualEntryClientKey();
|
|
42
|
+
collectAssets(virtualEntryClientKey, true);
|
|
43
|
+
const { page, layout, head } = route;
|
|
44
|
+
collectAssets(page, true);
|
|
45
|
+
if (head) collectAssets(head, true);
|
|
46
|
+
if (layout) collectAssets(layout, true);
|
|
47
|
+
const criticalJs = [];
|
|
48
|
+
const sharedJs = [];
|
|
49
|
+
for (const [file, isCritical] of jsFiles) (isCritical ? criticalJs : sharedJs).push(file);
|
|
50
|
+
return {
|
|
51
|
+
cssFiles: [...cssFiles],
|
|
52
|
+
criticalJs,
|
|
53
|
+
sharedJs,
|
|
54
|
+
entryClientFile: manifest[virtualEntryClientKey].file
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function getAssets(route, nonce) {
|
|
58
|
+
if (!isProd) return {
|
|
59
|
+
cssLinks: "",
|
|
60
|
+
jsPreloads: "",
|
|
61
|
+
entryClient: withBase("@id/virtual:vike-lite/entry-client")
|
|
62
|
+
};
|
|
63
|
+
let files = assetsCache.get(route);
|
|
64
|
+
if (!files) {
|
|
65
|
+
files = computeAssetFiles(route);
|
|
66
|
+
assetsCache.set(route, files);
|
|
67
|
+
}
|
|
68
|
+
const nonceAttr = nonce ? ` nonce="${nonce}"` : "";
|
|
69
|
+
return {
|
|
70
|
+
cssLinks: files.cssFiles.map((href) => `<link rel="stylesheet" href="${withBase(href)}"${nonceAttr}>`).join(""),
|
|
71
|
+
jsPreloads: [...files.criticalJs.map((href) => `<link rel="modulepreload" href="${withBase(href)}" crossorigin fetchpriority="high"${nonceAttr}>`), ...files.sharedJs.map((href) => `<link rel="modulepreload" href="${withBase(href)}" crossorigin${nonceAttr}>`)].join(""),
|
|
72
|
+
entryClient: withBase(files.entryClientFile)
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
async function buildPageContext(urlPathname, urlOriginal, isJsonRequest) {
|
|
76
|
+
const matched = matchRoute(urlPathname, store.routes);
|
|
77
|
+
if (!matched) return null;
|
|
78
|
+
const { route, routeParams } = matched;
|
|
79
|
+
const pageContext = {
|
|
80
|
+
routeParams,
|
|
81
|
+
urlOriginal,
|
|
82
|
+
urlPathname
|
|
83
|
+
};
|
|
84
|
+
const [dataMod, titleMod, PageModule, HeadModule, LayoutModule] = await Promise.all([
|
|
85
|
+
route.Data?.() ?? null,
|
|
86
|
+
route.Title?.() ?? null,
|
|
87
|
+
isJsonRequest ? null : route.Page(),
|
|
88
|
+
isJsonRequest ? null : route.Head?.() ?? null,
|
|
89
|
+
isJsonRequest ? null : route.Layout?.() ?? null
|
|
90
|
+
]);
|
|
91
|
+
if (dataMod) try {
|
|
92
|
+
pageContext.data = await (dataMod.data ?? dataMod.default)(pageContext);
|
|
93
|
+
} catch (error) {
|
|
94
|
+
if (!(error instanceof AbortRedirect || error instanceof AbortRender)) console.error("+data hook failed at:", urlPathname);
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
if (titleMod) try {
|
|
98
|
+
const titleFn = titleMod.title ?? titleMod.default;
|
|
99
|
+
pageContext.title = typeof titleFn === "function" ? titleFn(pageContext) : titleFn;
|
|
100
|
+
} catch (error) {
|
|
101
|
+
if (!(error instanceof AbortRedirect || error instanceof AbortRender)) console.error("+title hook failed at:", urlPathname);
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
pageContext,
|
|
106
|
+
route,
|
|
107
|
+
PageModule,
|
|
108
|
+
HeadModule,
|
|
109
|
+
LayoutModule
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
async function renderErrorPage(req, status, urlPathname, error, nonce) {
|
|
113
|
+
let errorMessage;
|
|
114
|
+
let is500;
|
|
115
|
+
if (status === 500) {
|
|
116
|
+
errorMessage = isProd ? "Internal Server Error" : error instanceof Error ? error.message : "Unknown error";
|
|
117
|
+
is500 = true;
|
|
118
|
+
} else is500 = false;
|
|
119
|
+
const fallbackText = status === 404 ? "Not Found" : "Internal Server Error";
|
|
120
|
+
if (!store.errorRoute) return new Response(fallbackText, {
|
|
121
|
+
status,
|
|
122
|
+
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
123
|
+
});
|
|
124
|
+
try {
|
|
125
|
+
const [PageModule, HeadModule, LayoutModule] = await Promise.all([
|
|
126
|
+
store.errorRoute.Page(),
|
|
127
|
+
store.errorRoute.Head?.() ?? null,
|
|
128
|
+
store.errorRoute.Layout?.() ?? null
|
|
129
|
+
]);
|
|
130
|
+
const pageContext = {
|
|
131
|
+
urlOriginal: req.url,
|
|
132
|
+
urlPathname,
|
|
133
|
+
routeParams: {},
|
|
134
|
+
is404: status === 404,
|
|
135
|
+
is500,
|
|
136
|
+
errorMessage
|
|
137
|
+
};
|
|
138
|
+
const html = await store.config.onRenderHtml({
|
|
139
|
+
pageContext,
|
|
140
|
+
Page: PageModule.Page ?? PageModule.default,
|
|
141
|
+
Layout: LayoutModule ? LayoutModule.Layout ?? LayoutModule.default : void 0,
|
|
142
|
+
Head: HeadModule ? HeadModule.Head ?? HeadModule.default : void 0,
|
|
143
|
+
pageTitleTag: `<title>${status === 404 ? "Page Not Found" : "Server Error"}</title>`,
|
|
144
|
+
serializedContext: serializeContext(pageContext),
|
|
145
|
+
assets: getAssets(store.errorRoute, nonce),
|
|
146
|
+
nonce
|
|
147
|
+
});
|
|
148
|
+
return new Response(html, {
|
|
149
|
+
status,
|
|
150
|
+
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
151
|
+
});
|
|
152
|
+
} catch (renderError) {
|
|
153
|
+
console.error("[vike-lite] Error page render failed:", renderError);
|
|
154
|
+
return new Response(fallbackText, {
|
|
155
|
+
status,
|
|
156
|
+
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
async function renderPage(req, { nonce } = {}) {
|
|
161
|
+
let { pathname } = new URL(req.url);
|
|
162
|
+
pathname = stripBase(pathname);
|
|
163
|
+
const isJsonRequest = pathname.endsWith(".pageContext.json");
|
|
164
|
+
let targetPathname = pathname;
|
|
165
|
+
if (isJsonRequest) {
|
|
166
|
+
targetPathname = targetPathname.replace(/\.pageContext\.json$/, "");
|
|
167
|
+
if (targetPathname === "/index") targetPathname = "/";
|
|
168
|
+
}
|
|
169
|
+
try {
|
|
170
|
+
const resolved = await buildPageContext(targetPathname, req.url, isJsonRequest);
|
|
171
|
+
if (!resolved) {
|
|
172
|
+
if (isJsonRequest) return Response.json({ is404: true }, { status: 404 });
|
|
173
|
+
return renderErrorPage(req, 404, targetPathname, nonce);
|
|
174
|
+
}
|
|
175
|
+
const { pageContext, route, PageModule, HeadModule, LayoutModule } = resolved;
|
|
176
|
+
if (isJsonRequest) return Response.json(pageContext);
|
|
177
|
+
const html = await store.config.onRenderHtml({
|
|
178
|
+
pageContext,
|
|
179
|
+
Page: PageModule.Page ?? PageModule.default,
|
|
180
|
+
Head: HeadModule ? HeadModule.Head ?? HeadModule.default : void 0,
|
|
181
|
+
Layout: LayoutModule ? LayoutModule.Layout ?? LayoutModule.default : void 0,
|
|
182
|
+
pageTitleTag: pageContext.title ? `<title>${pageContext.title}</title>` : "",
|
|
183
|
+
serializedContext: serializeContext(pageContext),
|
|
184
|
+
assets: getAssets(route, nonce),
|
|
185
|
+
nonce
|
|
186
|
+
});
|
|
187
|
+
return new Response(html, {
|
|
188
|
+
status: 200,
|
|
189
|
+
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
190
|
+
});
|
|
191
|
+
} catch (error) {
|
|
192
|
+
if (error instanceof AbortRedirect) {
|
|
193
|
+
let redirectUrl = error.url;
|
|
194
|
+
if (redirectUrl.startsWith("/")) redirectUrl = BASE_URL + (redirectUrl === "/" ? "" : redirectUrl);
|
|
195
|
+
if (isJsonRequest) return Response.json({ _redirect: redirectUrl }, { status: 200 });
|
|
196
|
+
return new Response(null, {
|
|
197
|
+
status: error.statusCode,
|
|
198
|
+
headers: { Location: redirectUrl }
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
if (error instanceof AbortRender) {
|
|
202
|
+
if (isJsonRequest) return Response.json({
|
|
203
|
+
is404: error.statusCode === 404,
|
|
204
|
+
is500: error.statusCode >= 500,
|
|
205
|
+
isError: true,
|
|
206
|
+
reason: error.reason
|
|
207
|
+
}, { status: error.statusCode });
|
|
208
|
+
return renderErrorPage(req, error.statusCode, targetPathname, error.reason, nonce);
|
|
209
|
+
}
|
|
210
|
+
console.error("Render Error:", error);
|
|
211
|
+
if (isJsonRequest) return Response.json({ is500: true }, { status: 500 });
|
|
212
|
+
return renderErrorPage(req, 500, targetPathname, error, nonce);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
//#endregion
|
|
2
216
|
export { renderPage };
|
package/dist/vite.mjs
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { t as renderPage } from "./server-DSrh6B3f.mjs";
|
|
2
1
|
import fs from "node:fs";
|
|
3
2
|
import path from "node:path";
|
|
4
3
|
import { Readable } from "node:stream";
|
|
@@ -112,6 +111,7 @@ function vikeLite({ pagesDir = "pages", apiPrefix = "/api", prerender = false, s
|
|
|
112
111
|
let viteConfigRoot;
|
|
113
112
|
let outDir;
|
|
114
113
|
let hasAnyPrerender;
|
|
114
|
+
let baseUrl;
|
|
115
115
|
const VIRTUAL = {
|
|
116
116
|
routes: "virtual:vike-lite/routes",
|
|
117
117
|
manifest: "virtual:vike-lite/client-manifest",
|
|
@@ -128,6 +128,8 @@ function vikeLite({ pagesDir = "pages", apiPrefix = "/api", prerender = false, s
|
|
|
128
128
|
return {
|
|
129
129
|
name: "vike-lite",
|
|
130
130
|
config(config, { mode }) {
|
|
131
|
+
const rawBase = config.base || "/";
|
|
132
|
+
baseUrl = rawBase === "/" ? "" : rawBase.endsWith("/") ? rawBase.slice(0, -1) : rawBase;
|
|
131
133
|
const envVariables = loadEnv(mode, config.envDir || process.cwd(), "");
|
|
132
134
|
for (const key in envVariables) if (process.env[key] === void 0) process.env[key] = envVariables[key];
|
|
133
135
|
outDir = config.build?.outDir ?? "dist";
|
|
@@ -287,15 +289,12 @@ function vikeLite({ pagesDir = "pages", apiPrefix = "/api", prerender = false, s
|
|
|
287
289
|
const defaultServerEntryContent = isProd ? fs.readFileSync(path.resolve("vike-lite/__internal/vite/defaultServerEntry.mjs"), "utf8") : `import{renderPage}from'vike-lite/server';`;
|
|
288
290
|
return importSetup + defaultServerEntryContent + "export default{fetch:renderPage};";
|
|
289
291
|
}
|
|
290
|
-
if (id === RESOLVED.entryPrerender) return importSetup + `export{routes}from'${VIRTUAL.routes}';`;
|
|
292
|
+
if (id === RESOLVED.entryPrerender) return importSetup + `export{routes}from'${VIRTUAL.routes}';export{renderPage}from'vike-lite/server';`;
|
|
291
293
|
},
|
|
292
294
|
async closeBundle() {
|
|
293
|
-
if (!isProd || this.environment.name !== "ssr") return;
|
|
295
|
+
if (!isProd || !hasAnyPrerender || this.environment.name !== "ssr") return;
|
|
294
296
|
const { pathToFileURL } = await import("node:url");
|
|
295
|
-
const
|
|
296
|
-
if (!fs.existsSync(prerenderPath)) return;
|
|
297
|
-
const { routes } = await import(pathToFileURL(prerenderPath).href);
|
|
298
|
-
if (!routes) return;
|
|
297
|
+
const { routes, renderPage } = await import(pathToFileURL(path.join(viteConfigRoot, outDir, "server/prerender.mjs")).href);
|
|
299
298
|
const urlsToPrerender = /* @__PURE__ */ new Set();
|
|
300
299
|
for (const route of routes) {
|
|
301
300
|
let shouldPrerender = prerender;
|
|
@@ -322,19 +321,17 @@ function vikeLite({ pagesDir = "pages", apiPrefix = "/api", prerender = false, s
|
|
|
322
321
|
}
|
|
323
322
|
if (urlsToPrerender.size === 0) return;
|
|
324
323
|
console.log("[vike-lite] 📦 Starting Static Site Generation (SSG)…");
|
|
325
|
-
const { BASE_URL } = import.meta.env;
|
|
326
|
-
const baseNoSlash = BASE_URL.endsWith("/") ? BASE_URL.slice(0, -1) : BASE_URL;
|
|
327
324
|
let generatedCount = 0;
|
|
328
325
|
const clientDir = path.join(viteConfigRoot, outDir, "client");
|
|
329
326
|
for (const urlPath of urlsToPrerender) {
|
|
330
|
-
const htmlRes = await renderPage(new Request(`http://localhost${
|
|
327
|
+
const htmlRes = await renderPage(new Request(`http://localhost${baseUrl}${urlPath}`));
|
|
331
328
|
if (htmlRes?.ok && htmlRes.headers.get("content-type")?.includes("text/html")) {
|
|
332
329
|
const outDirRoute = path.join(clientDir, urlPath === "/" ? "" : urlPath);
|
|
333
330
|
fs.mkdirSync(outDirRoute, { recursive: true });
|
|
334
331
|
fs.writeFileSync(path.join(outDirRoute, "index.html"), await htmlRes.text());
|
|
335
332
|
} else throw new Error(`[vike-lite] ❌ SSG HTML Error for "${urlPath}"`);
|
|
336
333
|
const jsonTarget = urlPath === "/" ? "/index" : urlPath;
|
|
337
|
-
const jsonRes = await renderPage(new Request(`http://localhost${
|
|
334
|
+
const jsonRes = await renderPage(new Request(`http://localhost${baseUrl}${jsonTarget}.pageContext.json`));
|
|
338
335
|
if (jsonRes?.ok) {
|
|
339
336
|
const jsonOutPath = path.join(clientDir, `${jsonTarget}.pageContext.json`);
|
|
340
337
|
fs.mkdirSync(path.dirname(jsonOutPath), { recursive: true });
|
package/package.json
CHANGED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
//#region src/__internal/shared/matchRoute.ts
|
|
2
|
-
const regexCache = /* @__PURE__ */ new Map();
|
|
3
|
-
function escapeRegex(str) {
|
|
4
|
-
return str.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
|
|
5
|
-
}
|
|
6
|
-
function matchRoute(urlPathname, routes) {
|
|
7
|
-
for (const route of routes) {
|
|
8
|
-
if (!route.path.includes(":")) {
|
|
9
|
-
if (route.path === urlPathname || route.path + "/" === urlPathname) return {
|
|
10
|
-
route,
|
|
11
|
-
routeParams: {}
|
|
12
|
-
};
|
|
13
|
-
continue;
|
|
14
|
-
}
|
|
15
|
-
let compiled = regexCache.get(route.path);
|
|
16
|
-
if (!compiled) {
|
|
17
|
-
const paramNames = [];
|
|
18
|
-
const regexPath = route.path.split("/").map((segment) => {
|
|
19
|
-
if (segment.startsWith(":")) {
|
|
20
|
-
paramNames.push(segment.slice(1));
|
|
21
|
-
return "([^/]+)";
|
|
22
|
-
}
|
|
23
|
-
return escapeRegex(segment);
|
|
24
|
-
}).join("/");
|
|
25
|
-
compiled = {
|
|
26
|
-
regex: new RegExp(`^${regexPath}/?$`),
|
|
27
|
-
paramNames
|
|
28
|
-
};
|
|
29
|
-
regexCache.set(route.path, compiled);
|
|
30
|
-
}
|
|
31
|
-
const match = urlPathname.match(compiled.regex);
|
|
32
|
-
if (match) {
|
|
33
|
-
const routeParams = {};
|
|
34
|
-
for (let i = 0; i < compiled.paramNames.length; i++) routeParams[compiled.paramNames[i]] = decodeURIComponent(match[i + 1]);
|
|
35
|
-
return {
|
|
36
|
-
route,
|
|
37
|
-
routeParams
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
|
-
//#endregion
|
|
44
|
-
export { matchRoute as t };
|
package/dist/server-DSrh6B3f.mjs
DELETED
|
@@ -1,222 +0,0 @@
|
|
|
1
|
-
import { t as matchRoute } from "./matchRoute-nTNPxoMq.mjs";
|
|
2
|
-
import { AbortRedirect, AbortRender } from "./server/abort.mjs";
|
|
3
|
-
import { n as store } from "./store-BCs8gL_o.mjs";
|
|
4
|
-
//#region src/utils/serializeContext.ts
|
|
5
|
-
const ESCAPE_LOOKUP = {
|
|
6
|
-
"&": String.raw`\u0026`,
|
|
7
|
-
">": String.raw`\u003e`,
|
|
8
|
-
"<": String.raw`\u003c`,
|
|
9
|
-
"\u2028": String.raw`\u2028`,
|
|
10
|
-
"\u2029": String.raw`\u2029`
|
|
11
|
-
};
|
|
12
|
-
const ESCAPE_REGEX = /[&><\u{2028}\u{2029}]/gu;
|
|
13
|
-
function serializeContext(data) {
|
|
14
|
-
return JSON.stringify(data).replaceAll(ESCAPE_REGEX, (match) => ESCAPE_LOOKUP[match]);
|
|
15
|
-
}
|
|
16
|
-
//#endregion
|
|
17
|
-
//#region src/server/renderPage.ts
|
|
18
|
-
const isProd = process.env.NODE_ENV === "production";
|
|
19
|
-
function withBase(file) {
|
|
20
|
-
const { BASE_URL } = import.meta.env;
|
|
21
|
-
return `${BASE_URL === "/" ? "" : BASE_URL.endsWith("/") ? BASE_URL.slice(0, -1) : BASE_URL}${file.startsWith("/") ? file : `/${file}`}`;
|
|
22
|
-
}
|
|
23
|
-
const assetsCache = /* @__PURE__ */ new WeakMap();
|
|
24
|
-
function computeAssetFiles(route) {
|
|
25
|
-
const cssFiles = /* @__PURE__ */ new Set();
|
|
26
|
-
const jsFiles = /* @__PURE__ */ new Map();
|
|
27
|
-
const { manifest } = store;
|
|
28
|
-
function getVirtualEntryClientKey() {
|
|
29
|
-
for (const key in manifest) if (manifest[key].isEntry) return key;
|
|
30
|
-
throw new Error("entry-client not found in manifest");
|
|
31
|
-
}
|
|
32
|
-
function collectAssets(key, isCritical) {
|
|
33
|
-
const chunk = manifest[key];
|
|
34
|
-
if (!chunk) return;
|
|
35
|
-
const current = jsFiles.get(chunk.file);
|
|
36
|
-
if (current === true) return;
|
|
37
|
-
if (current === false && !isCritical) return;
|
|
38
|
-
jsFiles.set(chunk.file, isCritical);
|
|
39
|
-
if (chunk.css) for (const css of chunk.css) cssFiles.add(css);
|
|
40
|
-
if (chunk.imports) for (const imp of chunk.imports) collectAssets(imp, false);
|
|
41
|
-
}
|
|
42
|
-
const virtualEntryClientKey = getVirtualEntryClientKey();
|
|
43
|
-
collectAssets(virtualEntryClientKey, true);
|
|
44
|
-
const { page, layout, head } = route;
|
|
45
|
-
collectAssets(page, true);
|
|
46
|
-
if (head) collectAssets(head, true);
|
|
47
|
-
if (layout) collectAssets(layout, true);
|
|
48
|
-
const criticalJs = [];
|
|
49
|
-
const sharedJs = [];
|
|
50
|
-
for (const [file, isCritical] of jsFiles) (isCritical ? criticalJs : sharedJs).push(file);
|
|
51
|
-
return {
|
|
52
|
-
cssFiles: [...cssFiles],
|
|
53
|
-
criticalJs,
|
|
54
|
-
sharedJs,
|
|
55
|
-
entryClientFile: manifest[virtualEntryClientKey].file
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
function getAssets(route, nonce) {
|
|
59
|
-
if (!isProd) return {
|
|
60
|
-
cssLinks: "",
|
|
61
|
-
jsPreloads: "",
|
|
62
|
-
entryClient: withBase("@id/virtual:vike-lite/entry-client")
|
|
63
|
-
};
|
|
64
|
-
let files = assetsCache.get(route);
|
|
65
|
-
if (!files) {
|
|
66
|
-
files = computeAssetFiles(route);
|
|
67
|
-
assetsCache.set(route, files);
|
|
68
|
-
}
|
|
69
|
-
const nonceAttr = nonce ? ` nonce="${nonce}"` : "";
|
|
70
|
-
return {
|
|
71
|
-
cssLinks: files.cssFiles.map((href) => `<link rel="stylesheet" href="${withBase(href)}"${nonceAttr}>`).join(""),
|
|
72
|
-
jsPreloads: [...files.criticalJs.map((href) => `<link rel="modulepreload" href="${withBase(href)}" crossorigin fetchpriority="high"${nonceAttr}>`), ...files.sharedJs.map((href) => `<link rel="modulepreload" href="${withBase(href)}" crossorigin${nonceAttr}>`)].join(""),
|
|
73
|
-
entryClient: withBase(files.entryClientFile)
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
async function buildPageContext(urlPathname, urlOriginal, isJsonRequest) {
|
|
77
|
-
const matched = matchRoute(urlPathname, store.routes);
|
|
78
|
-
if (!matched) return null;
|
|
79
|
-
const { route, routeParams } = matched;
|
|
80
|
-
const pageContext = {
|
|
81
|
-
routeParams,
|
|
82
|
-
urlOriginal,
|
|
83
|
-
urlPathname
|
|
84
|
-
};
|
|
85
|
-
const [dataMod, titleMod, PageModule, HeadModule, LayoutModule] = await Promise.all([
|
|
86
|
-
route.Data?.() ?? null,
|
|
87
|
-
route.Title?.() ?? null,
|
|
88
|
-
isJsonRequest ? null : route.Page(),
|
|
89
|
-
isJsonRequest ? null : route.Head?.() ?? null,
|
|
90
|
-
isJsonRequest ? null : route.Layout?.() ?? null
|
|
91
|
-
]);
|
|
92
|
-
if (dataMod) try {
|
|
93
|
-
pageContext.data = await (dataMod.data ?? dataMod.default)(pageContext);
|
|
94
|
-
} catch (error) {
|
|
95
|
-
if (!(error instanceof AbortRedirect || error instanceof AbortRender)) console.error("+data hook failed at:", urlPathname);
|
|
96
|
-
throw error;
|
|
97
|
-
}
|
|
98
|
-
if (titleMod) try {
|
|
99
|
-
const titleFn = titleMod.title ?? titleMod.default;
|
|
100
|
-
pageContext.title = typeof titleFn === "function" ? titleFn(pageContext) : titleFn;
|
|
101
|
-
} catch (error) {
|
|
102
|
-
if (!(error instanceof AbortRedirect || error instanceof AbortRender)) console.error("+title hook failed at:", urlPathname);
|
|
103
|
-
throw error;
|
|
104
|
-
}
|
|
105
|
-
return {
|
|
106
|
-
pageContext,
|
|
107
|
-
route,
|
|
108
|
-
PageModule,
|
|
109
|
-
HeadModule,
|
|
110
|
-
LayoutModule
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
async function renderErrorPage(req, status, urlPathname, error, nonce) {
|
|
114
|
-
let errorMessage;
|
|
115
|
-
let is500;
|
|
116
|
-
if (status === 500) {
|
|
117
|
-
errorMessage = isProd ? "Internal Server Error" : error instanceof Error ? error.message : "Unknown error";
|
|
118
|
-
is500 = true;
|
|
119
|
-
} else is500 = false;
|
|
120
|
-
const fallbackText = status === 404 ? "Not Found" : "Internal Server Error";
|
|
121
|
-
if (!store.errorRoute) return new Response(fallbackText, {
|
|
122
|
-
status,
|
|
123
|
-
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
124
|
-
});
|
|
125
|
-
try {
|
|
126
|
-
const [PageModule, HeadModule, LayoutModule] = await Promise.all([
|
|
127
|
-
store.errorRoute.Page(),
|
|
128
|
-
store.errorRoute.Head?.() ?? null,
|
|
129
|
-
store.errorRoute.Layout?.() ?? null
|
|
130
|
-
]);
|
|
131
|
-
const pageContext = {
|
|
132
|
-
urlOriginal: req.url,
|
|
133
|
-
urlPathname,
|
|
134
|
-
routeParams: {},
|
|
135
|
-
is404: status === 404,
|
|
136
|
-
is500,
|
|
137
|
-
errorMessage
|
|
138
|
-
};
|
|
139
|
-
const html = await store.config.onRenderHtml({
|
|
140
|
-
pageContext,
|
|
141
|
-
Page: PageModule.Page ?? PageModule.default,
|
|
142
|
-
Layout: LayoutModule ? LayoutModule.Layout ?? LayoutModule.default : void 0,
|
|
143
|
-
Head: HeadModule ? HeadModule.Head ?? HeadModule.default : void 0,
|
|
144
|
-
pageTitleTag: `<title>${status === 404 ? "Page Not Found" : "Server Error"}</title>`,
|
|
145
|
-
serializedContext: serializeContext(pageContext),
|
|
146
|
-
assets: getAssets(store.errorRoute, nonce),
|
|
147
|
-
nonce
|
|
148
|
-
});
|
|
149
|
-
return new Response(html, {
|
|
150
|
-
status,
|
|
151
|
-
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
152
|
-
});
|
|
153
|
-
} catch (renderError) {
|
|
154
|
-
console.error("[vike-lite] Error page render failed:", renderError);
|
|
155
|
-
return new Response(fallbackText, {
|
|
156
|
-
status,
|
|
157
|
-
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
async function renderPage(req, { nonce } = {}) {
|
|
162
|
-
let { pathname } = new URL(req.url);
|
|
163
|
-
const { BASE_URL } = import.meta.env;
|
|
164
|
-
if (BASE_URL !== "/") {
|
|
165
|
-
const baseSlashed = BASE_URL.endsWith("/") ? BASE_URL : BASE_URL + "/";
|
|
166
|
-
const baseNoSlash = baseSlashed.slice(0, -1);
|
|
167
|
-
pathname = pathname === baseNoSlash ? "/" : pathname.slice(baseSlashed.length - 1);
|
|
168
|
-
}
|
|
169
|
-
const isJsonRequest = pathname.endsWith(".pageContext.json");
|
|
170
|
-
let targetPathname = pathname;
|
|
171
|
-
if (isJsonRequest) {
|
|
172
|
-
targetPathname = targetPathname.replace(/\.pageContext\.json$/, "");
|
|
173
|
-
if (targetPathname === "/index") targetPathname = "/";
|
|
174
|
-
}
|
|
175
|
-
try {
|
|
176
|
-
const resolved = await buildPageContext(targetPathname, req.url, isJsonRequest);
|
|
177
|
-
if (!resolved) {
|
|
178
|
-
if (isJsonRequest) return Response.json({ is404: true }, { status: 404 });
|
|
179
|
-
return renderErrorPage(req, 404, targetPathname, nonce);
|
|
180
|
-
}
|
|
181
|
-
const { pageContext, route, PageModule, HeadModule, LayoutModule } = resolved;
|
|
182
|
-
if (isJsonRequest) return Response.json(pageContext);
|
|
183
|
-
const html = await store.config.onRenderHtml({
|
|
184
|
-
pageContext,
|
|
185
|
-
Page: PageModule.Page ?? PageModule.default,
|
|
186
|
-
Head: HeadModule ? HeadModule.Head ?? HeadModule.default : void 0,
|
|
187
|
-
Layout: LayoutModule ? LayoutModule.Layout ?? LayoutModule.default : void 0,
|
|
188
|
-
pageTitleTag: pageContext.title ? `<title>${pageContext.title}</title>` : "",
|
|
189
|
-
serializedContext: serializeContext(pageContext),
|
|
190
|
-
assets: getAssets(route, nonce),
|
|
191
|
-
nonce
|
|
192
|
-
});
|
|
193
|
-
return new Response(html, {
|
|
194
|
-
status: 200,
|
|
195
|
-
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
196
|
-
});
|
|
197
|
-
} catch (error) {
|
|
198
|
-
if (error instanceof AbortRedirect) {
|
|
199
|
-
let redirectUrl = error.url;
|
|
200
|
-
if (redirectUrl.startsWith("/")) redirectUrl = (BASE_URL.endsWith("/") ? BASE_URL.slice(0, -1) : BASE_URL) + (redirectUrl === "/" ? "" : redirectUrl);
|
|
201
|
-
if (isJsonRequest) return Response.json({ _redirect: redirectUrl }, { status: 200 });
|
|
202
|
-
return new Response(null, {
|
|
203
|
-
status: error.statusCode,
|
|
204
|
-
headers: { Location: redirectUrl }
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
if (error instanceof AbortRender) {
|
|
208
|
-
if (isJsonRequest) return Response.json({
|
|
209
|
-
is404: error.statusCode === 404,
|
|
210
|
-
is500: error.statusCode >= 500,
|
|
211
|
-
isError: true,
|
|
212
|
-
reason: error.reason
|
|
213
|
-
}, { status: error.statusCode });
|
|
214
|
-
return renderErrorPage(req, error.statusCode, targetPathname, error.reason, nonce);
|
|
215
|
-
}
|
|
216
|
-
console.error("Render Error:", error);
|
|
217
|
-
if (isJsonRequest) return Response.json({ is500: true }, { status: 500 });
|
|
218
|
-
return renderErrorPage(req, 500, targetPathname, error, nonce);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
//#endregion
|
|
222
|
-
export { renderPage as t };
|