vike-lite 1.11.1 → 1.13.0

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,11 +1,9 @@
1
- import { t as Config } from "../index-ZDN-jKX_.mjs";
2
-
3
1
  //#region src/server/store.d.ts
4
2
  interface VikeState {
5
3
  routes: typeof import('virtual:routes')['routes'];
6
4
  errorRoute: typeof import('virtual:routes')['errorRoute'] | null;
7
- config: Config | null;
8
- manifest: Manifest | undefined;
5
+ config: typeof import('virtual:routes')['config'] | null;
6
+ manifest: typeof import('virtual:client-manifest')['default'] | null;
9
7
  }
10
8
  declare const store: VikeState;
11
9
  declare function setVikeState(newState: Partial<VikeState>): void;
@@ -1,2 +1,2 @@
1
- import { n as store, t as setVikeState } from "../store-CfUB1COP.mjs";
1
+ import { n as store, t as setVikeState } from "../store-DPJQje-K.mjs";
2
2
  export { setVikeState, store };
@@ -1,2 +1,23 @@
1
- import { n as RenderContext, r as matchRoute, t as Config } from "../index-ZDN-jKX_.mjs";
2
- export { Config, RenderContext, matchRoute };
1
+ //#region src/__internal/shared/matchRoute.d.ts
2
+ declare function matchRoute(urlPathname: string, routes: typeof import('virtual:routes').routes): {
3
+ route: any;
4
+ routeParams: Record<string, string>;
5
+ } | null;
6
+ //#endregion
7
+ //#region src/__internal/shared/index.d.ts
8
+ interface RenderContext {
9
+ pageContext: any;
10
+ Page: unknown;
11
+ Head?: unknown;
12
+ Layout?: unknown;
13
+ pageTitleTag: string;
14
+ serializedContext: string;
15
+ assets: {
16
+ cssLinks: string;
17
+ jsPreloads: string;
18
+ entryClient: string;
19
+ };
20
+ nonce?: string;
21
+ }
22
+ //#endregion
23
+ export { RenderContext, matchRoute };
@@ -1,5 +1,4 @@
1
1
  import { PageContext } from "../index.mjs";
2
-
3
2
  //#region src/client/router.d.ts
4
3
  /**
5
4
  * Change page programmatically on the client without reloading the browser.
@@ -1,4 +1,3 @@
1
- import { t as BASE_URL } from "../shared-DEpJkq09.mjs";
2
1
  //#region src/client/router.ts
3
2
  /**
4
3
  * Change page programmatically on the client without reloading the browser.
@@ -8,7 +7,10 @@ import { t as BASE_URL } from "../shared-DEpJkq09.mjs";
8
7
  function navigate(url, options) {
9
8
  if (typeof globalThis === "undefined") throw new Error("navigate() can only be called on the client side.");
10
9
  let finalUrl = url;
11
- if (finalUrl.startsWith("/")) finalUrl = (BASE_URL.endsWith("/") ? BASE_URL.slice(0, -1) : BASE_URL) + (finalUrl === "/" ? "" : finalUrl);
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
+ }
12
14
  globalThis.history.pushState({ triggeredBy: "vike-lite" }, "", finalUrl);
13
15
  globalThis.dispatchEvent(new CustomEvent("vike-navigate", { detail: {
14
16
  keepScrollPosition: options?.keepScrollPosition,
@@ -0,0 +1,220 @@
1
+ import { t as matchRoute } from "./matchRoute-nTNPxoMq.mjs";
2
+ import { AbortRedirect, AbortRender } from "./server/abort.mjs";
3
+ import { n as store } from "./store-DPJQje-K.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 getAssets(route, nonce) {
25
+ if (!isProd) return computeAssets(route, nonce);
26
+ const nonceKey = nonce || "";
27
+ let routeCache = assetsCache.get(route);
28
+ if (!routeCache) {
29
+ routeCache = /* @__PURE__ */ new Map();
30
+ assetsCache.set(route, routeCache);
31
+ }
32
+ let assets = routeCache.get(nonceKey);
33
+ if (!assets) {
34
+ assets = computeAssets(route, nonce);
35
+ routeCache.set(nonceKey, assets);
36
+ }
37
+ return assets;
38
+ }
39
+ function computeAssets(route, nonce) {
40
+ if (!isProd) return {
41
+ cssLinks: "",
42
+ jsPreloads: "",
43
+ entryClient: withBase("@id/virtual:entry-client")
44
+ };
45
+ const cssFiles = /* @__PURE__ */ new Set();
46
+ const jsFiles = /* @__PURE__ */ new Map();
47
+ const { manifest } = store;
48
+ function getVirtualEntryClientKey() {
49
+ for (const key in manifest) if (manifest[key].isEntry) return key;
50
+ throw new Error("entry-client not found in manifest");
51
+ }
52
+ function collectAssets(key, isCritical) {
53
+ const chunk = manifest[key];
54
+ if (!chunk) return;
55
+ const current = jsFiles.get(chunk.file);
56
+ if (current === true) return;
57
+ if (current === false && !isCritical) return;
58
+ jsFiles.set(chunk.file, isCritical);
59
+ if (chunk.css) for (const css of chunk.css) cssFiles.add(css);
60
+ if (chunk.imports) for (const imp of chunk.imports) collectAssets(imp, false);
61
+ }
62
+ const virtualEntryClientKey = getVirtualEntryClientKey();
63
+ collectAssets(virtualEntryClientKey, true);
64
+ const { page, layout, head } = route;
65
+ collectAssets(page, true);
66
+ if (head) collectAssets(head, true);
67
+ if (layout) collectAssets(layout, true);
68
+ const criticalJs = [];
69
+ const sharedJs = [];
70
+ for (const [file, isCritical] of jsFiles) (isCritical ? criticalJs : sharedJs).push(file);
71
+ const nonceAttr = nonce ? ` nonce="${nonce}"` : "";
72
+ return {
73
+ cssLinks: [...cssFiles].map((href) => `<link rel="stylesheet" href="${withBase(href)}"${nonceAttr}>`).join(""),
74
+ jsPreloads: [...criticalJs.map((href) => `<link rel="modulepreload" href="${withBase(href)}" crossorigin fetchpriority="high"${nonceAttr}>`), ...sharedJs.map((href) => `<link rel="modulepreload" href="${withBase(href)}" crossorigin${nonceAttr}>`)].join(""),
75
+ entryClient: withBase(manifest[virtualEntryClientKey].file)
76
+ };
77
+ }
78
+ async function buildPageContext(urlPathname, urlOriginal, isJsonRequest) {
79
+ const matched = matchRoute(urlPathname, store.routes);
80
+ if (!matched) return null;
81
+ const { route, routeParams } = matched;
82
+ const pageContext = {
83
+ routeParams,
84
+ urlOriginal,
85
+ urlPathname
86
+ };
87
+ const [dataMod, titleMod, PageModule, HeadModule, LayoutModule] = await Promise.all([
88
+ route.Data?.() ?? null,
89
+ route.Title?.() ?? null,
90
+ isJsonRequest ? null : route.Page(),
91
+ isJsonRequest ? null : route.Head?.() ?? null,
92
+ isJsonRequest ? null : route.Layout?.() ?? null
93
+ ]);
94
+ if (dataMod) try {
95
+ pageContext.data = await (dataMod.data ?? dataMod.default)(pageContext);
96
+ } catch (error) {
97
+ console.error("+data hook failed at:", urlPathname);
98
+ throw error;
99
+ }
100
+ if (titleMod) try {
101
+ const titleFn = titleMod.title ?? titleMod.default;
102
+ pageContext.title = typeof titleFn === "function" ? titleFn(pageContext) : titleFn;
103
+ } catch (error) {
104
+ console.error("+title hook failed at:", urlPathname);
105
+ throw error;
106
+ }
107
+ return {
108
+ pageContext,
109
+ route,
110
+ PageModule,
111
+ HeadModule,
112
+ LayoutModule
113
+ };
114
+ }
115
+ async function renderErrorPage(req, status, urlPathname, error, nonce) {
116
+ let errorMessage;
117
+ let is500;
118
+ if (status === 500) {
119
+ console.error(`[vike-lite] Server Error:`, error);
120
+ errorMessage = isProd ? "Internal Server Error" : error instanceof Error ? error.message : "Unknown error";
121
+ is500 = true;
122
+ } else is500 = false;
123
+ if (!store.errorRoute) return new Response(status === 404 ? "Not Found" : "Internal Server Error", { status });
124
+ try {
125
+ const { default: onRenderHtml } = await store.config.onRenderHtml();
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 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" }
152
+ });
153
+ } catch (renderError) {
154
+ console.error("[vike-lite] Error page render failed:", renderError);
155
+ return new Response(status === 404 ? "Not Found" : "Internal Server Error", { status });
156
+ }
157
+ }
158
+ async function renderPage(req, { nonce } = {}) {
159
+ let { pathname } = new URL(req.url);
160
+ const { BASE_URL } = import.meta.env;
161
+ if (BASE_URL !== "/") {
162
+ const baseSlashed = BASE_URL.endsWith("/") ? BASE_URL : BASE_URL + "/";
163
+ const baseNoSlash = baseSlashed.slice(0, -1);
164
+ pathname = pathname === baseNoSlash ? "/" : pathname.slice(baseSlashed.length - 1);
165
+ }
166
+ const isJsonRequest = pathname.endsWith(".pageContext.json");
167
+ let targetPathname = pathname;
168
+ if (isJsonRequest) {
169
+ targetPathname = targetPathname.replace(/\.pageContext\.json$/, "");
170
+ if (targetPathname === "/index") targetPathname = "/";
171
+ }
172
+ try {
173
+ const resolved = await buildPageContext(targetPathname, req.url, isJsonRequest);
174
+ if (!resolved) {
175
+ if (isJsonRequest) return Response.json({ is404: true }, { status: 404 });
176
+ return renderErrorPage(req, 404, targetPathname, nonce);
177
+ }
178
+ const { pageContext, route, PageModule, HeadModule, LayoutModule } = resolved;
179
+ if (isJsonRequest) return Response.json(pageContext);
180
+ const { default: onRenderHtml } = await store.config.onRenderHtml();
181
+ const html = await onRenderHtml({
182
+ pageContext,
183
+ Page: PageModule.Page ?? PageModule.default,
184
+ Head: HeadModule ? HeadModule.Head ?? HeadModule.default : void 0,
185
+ Layout: LayoutModule ? LayoutModule.Layout ?? LayoutModule.default : void 0,
186
+ pageTitleTag: pageContext.title ? `<title>${pageContext.title}</title>` : "",
187
+ serializedContext: serializeContext(pageContext),
188
+ assets: getAssets(route, nonce),
189
+ nonce
190
+ });
191
+ return new Response(html, {
192
+ status: 200,
193
+ headers: { "Content-Type": "text/html" }
194
+ });
195
+ } catch (error) {
196
+ if (error instanceof AbortRedirect) {
197
+ let redirectUrl = error.url;
198
+ if (redirectUrl.startsWith("/")) redirectUrl = (BASE_URL.endsWith("/") ? BASE_URL.slice(0, -1) : BASE_URL) + (redirectUrl === "/" ? "" : redirectUrl);
199
+ if (isJsonRequest) return Response.json({ _redirect: redirectUrl }, { status: 200 });
200
+ return new Response(null, {
201
+ status: error.statusCode,
202
+ headers: { Location: redirectUrl }
203
+ });
204
+ }
205
+ if (error instanceof AbortRender) {
206
+ if (isJsonRequest) return Response.json({
207
+ is404: error.statusCode === 404,
208
+ is500: error.statusCode >= 500,
209
+ isError: true,
210
+ reason: error.reason
211
+ }, { status: error.statusCode });
212
+ return renderErrorPage(req, error.statusCode, targetPathname, error.reason, nonce);
213
+ }
214
+ console.error("Render Error:", error);
215
+ if (isJsonRequest) return Response.json({ is500: true }, { status: 500 });
216
+ return renderErrorPage(req, 500, targetPathname, error, nonce);
217
+ }
218
+ }
219
+ //#endregion
220
+ export { renderPage as t };
package/dist/server.d.mts CHANGED
@@ -1,7 +1,5 @@
1
1
  //#region src/server/renderPage.d.ts
2
- declare function renderPage(req: Request, {
3
- nonce
4
- }?: {
2
+ declare function renderPage(req: Request, { nonce }?: {
5
3
  nonce?: string;
6
4
  }): Promise<Response>;
7
5
  //#endregion
package/dist/server.mjs CHANGED
@@ -1,196 +1,2 @@
1
- import { t as BASE_URL } from "./shared-DEpJkq09.mjs";
2
- import { t as matchRoute } from "./matchRoute-nTNPxoMq.mjs";
3
- import { AbortRedirect, AbortRender } from "./server/abort.mjs";
4
- import { n as store } from "./store-CfUB1COP.mjs";
5
- //#region src/utils/serializeContext.ts
6
- const ESCAPE_LOOKUP = {
7
- "&": String.raw`\u0026`,
8
- ">": String.raw`\u003e`,
9
- "<": String.raw`\u003c`,
10
- "\u2028": String.raw`\u2028`,
11
- "\u2029": String.raw`\u2029`
12
- };
13
- const ESCAPE_REGEX = /[&><\u{2028}\u{2029}]/gu;
14
- function serializeContext(data) {
15
- return JSON.stringify(data).replaceAll(ESCAPE_REGEX, (match) => ESCAPE_LOOKUP[match]);
16
- }
17
- //#endregion
18
- //#region src/server/renderPage.ts
19
- const isProd = process.env.NODE_ENV === "production";
20
- function withBase(file) {
21
- return `${BASE_URL.replace(/\/$/, "")}/${file.replace(/^\//, "")}`;
22
- }
23
- function getAssets(route, nonce) {
24
- if (!isProd) return {
25
- cssLinks: "",
26
- jsPreloads: "",
27
- entryClient: withBase("@id/virtual:entry-client")
28
- };
29
- const cssFiles = /* @__PURE__ */ new Set();
30
- const jsFiles = /* @__PURE__ */ new Map();
31
- const { manifest } = store;
32
- function getVirtualEntryClientKey() {
33
- for (const key in manifest) if (manifest[key].isEntry) return key;
34
- throw new Error("entry-client not found in manifest");
35
- }
36
- function collectAssets(key, isCritical) {
37
- const chunk = manifest[key];
38
- if (!chunk) return;
39
- const current = jsFiles.get(chunk.file);
40
- if (current === true) return;
41
- if (current === false && !isCritical) return;
42
- jsFiles.set(chunk.file, isCritical);
43
- if (chunk.css) for (const css of chunk.css) cssFiles.add(css);
44
- if (chunk.imports) for (const imp of chunk.imports) collectAssets(imp, false);
45
- }
46
- const virtualEntryClientKey = getVirtualEntryClientKey();
47
- collectAssets(virtualEntryClientKey, true);
48
- const { page, layout, head } = route;
49
- collectAssets(page, true);
50
- if (head) collectAssets(head, true);
51
- if (layout) collectAssets(layout, true);
52
- const criticalJs = [];
53
- const sharedJs = [];
54
- for (const [file, isCritical] of jsFiles) (isCritical ? criticalJs : sharedJs).push(file);
55
- const nonceAttr = nonce ? ` nonce="${nonce}"` : "";
56
- return {
57
- cssLinks: [...cssFiles].map((href) => `<link rel="stylesheet" href="${withBase(href)}"${nonceAttr}>`).join(""),
58
- jsPreloads: [...criticalJs.map((href) => `<link rel="modulepreload" href="${withBase(href)}" crossorigin fetchpriority="high"${nonceAttr}>`), ...sharedJs.map((href) => `<link rel="modulepreload" href="${withBase(href)}" crossorigin${nonceAttr}>`)].join(""),
59
- entryClient: withBase(manifest[virtualEntryClientKey].file)
60
- };
61
- }
62
- async function buildPageContext(urlPathname, urlOriginal, isJsonRequest) {
63
- const matched = matchRoute(urlPathname, store.routes);
64
- if (!matched) return null;
65
- const { route, routeParams } = matched;
66
- const pageContext = {
67
- routeParams,
68
- urlOriginal,
69
- urlPathname
70
- };
71
- const [dataMod, titleMod, PageModule, HeadModule, LayoutModule] = await Promise.all([
72
- route.Data?.() ?? null,
73
- route.Title?.() ?? null,
74
- isJsonRequest ? null : route.Page(),
75
- isJsonRequest ? null : route.Head?.() ?? null,
76
- isJsonRequest ? null : route.Layout?.() ?? null
77
- ]);
78
- if (dataMod) try {
79
- pageContext.data = await (dataMod.data ?? dataMod.default)(pageContext);
80
- } catch (error) {
81
- console.error("+data hook failed at:", urlPathname);
82
- throw error;
83
- }
84
- if (titleMod) try {
85
- const titleFn = titleMod.title ?? titleMod.default;
86
- pageContext.title = typeof titleFn === "function" ? titleFn(pageContext) : titleFn;
87
- } catch (error) {
88
- console.error("+title hook failed at:", urlPathname);
89
- throw error;
90
- }
91
- return {
92
- pageContext,
93
- route,
94
- PageModule,
95
- HeadModule,
96
- LayoutModule
97
- };
98
- }
99
- async function renderErrorPage(req, status, urlPathname, error, nonce) {
100
- if (!store.errorRoute) return new Response(status === 404 ? "Not Found" : "Internal Server Error", { status });
101
- try {
102
- const { default: onRenderHtml } = await store.config.onRenderHtml();
103
- const [PageModule, HeadModule, LayoutModule] = await Promise.all([
104
- store.errorRoute.Page(),
105
- store.errorRoute.Head?.() ?? null,
106
- store.errorRoute.Layout?.() ?? null
107
- ]);
108
- const pageContext = {
109
- urlOriginal: req.url,
110
- urlPathname,
111
- routeParams: {},
112
- is404: status === 404,
113
- is500: status === 500,
114
- errorMessage: status === 500 && error instanceof Error ? error.message : void 0
115
- };
116
- const html = await onRenderHtml({
117
- pageContext,
118
- Page: PageModule.Page ?? PageModule.default,
119
- Layout: LayoutModule ? LayoutModule.Layout ?? LayoutModule.default : void 0,
120
- Head: HeadModule ? HeadModule.Head ?? HeadModule.default : void 0,
121
- pageTitleTag: `<title>${status === 404 ? "Page Not Found" : "Server Error"}</title>`,
122
- serializedContext: serializeContext(pageContext),
123
- assets: getAssets(store.errorRoute, nonce),
124
- nonce
125
- });
126
- return new Response(html, {
127
- status,
128
- headers: { "Content-Type": "text/html" }
129
- });
130
- } catch (renderError) {
131
- console.error("Error page render failed:", renderError);
132
- return new Response(status === 404 ? "Not Found" : "Internal Server Error", { status });
133
- }
134
- }
135
- async function renderPage(req, { nonce } = {}) {
136
- let { pathname } = new URL(req.url);
137
- if (BASE_URL !== "/") {
138
- const baseSlashed = BASE_URL.endsWith("/") ? BASE_URL : BASE_URL + "/";
139
- const baseNoSlash = baseSlashed.slice(0, -1);
140
- pathname = pathname === baseNoSlash ? "/" : pathname.slice(baseSlashed.length - 1);
141
- }
142
- const isJsonRequest = pathname.endsWith(".pageContext.json");
143
- let targetPathname = pathname;
144
- if (isJsonRequest) {
145
- targetPathname = targetPathname.replace(/\.pageContext\.json$/, "");
146
- if (targetPathname === "/index") targetPathname = "/";
147
- }
148
- try {
149
- const resolved = await buildPageContext(targetPathname, req.url, isJsonRequest);
150
- if (!resolved) {
151
- if (isJsonRequest) return Response.json({ is404: true }, { status: 404 });
152
- return renderErrorPage(req, 404, targetPathname, nonce);
153
- }
154
- const { pageContext, route, PageModule, HeadModule, LayoutModule } = resolved;
155
- if (isJsonRequest) return Response.json(pageContext);
156
- const { default: onRenderHtml } = await store.config.onRenderHtml();
157
- const html = await onRenderHtml({
158
- pageContext,
159
- Page: PageModule.Page ?? PageModule.default,
160
- Head: HeadModule ? HeadModule.Head ?? HeadModule.default : void 0,
161
- Layout: LayoutModule ? LayoutModule.Layout ?? LayoutModule.default : void 0,
162
- pageTitleTag: pageContext.title ? `<title>${pageContext.title}</title>` : "",
163
- serializedContext: serializeContext(pageContext),
164
- assets: getAssets(route, nonce),
165
- nonce
166
- });
167
- return new Response(html, {
168
- status: 200,
169
- headers: { "Content-Type": "text/html" }
170
- });
171
- } catch (error) {
172
- if (error instanceof AbortRedirect) {
173
- let redirectUrl = error.url;
174
- if (redirectUrl.startsWith("/")) redirectUrl = (BASE_URL.endsWith("/") ? BASE_URL.slice(0, -1) : BASE_URL) + (redirectUrl === "/" ? "" : redirectUrl);
175
- if (isJsonRequest) return Response.json({ _redirect: redirectUrl }, { status: 200 });
176
- return new Response(null, {
177
- status: error.statusCode,
178
- headers: { Location: redirectUrl }
179
- });
180
- }
181
- if (error instanceof AbortRender) {
182
- if (isJsonRequest) return Response.json({
183
- is404: error.statusCode === 404,
184
- is500: error.statusCode >= 500,
185
- isError: true,
186
- reason: error.reason
187
- }, { status: error.statusCode });
188
- return renderErrorPage(req, error.statusCode, targetPathname, error.reason, nonce);
189
- }
190
- console.error("Render Error:", error);
191
- if (isJsonRequest) return Response.json({ is500: true }, { status: 500 });
192
- return renderErrorPage(req, 500, targetPathname, error, nonce);
193
- }
194
- }
195
- //#endregion
1
+ import { t as renderPage } from "./server-Cm1ZA_Dp.mjs";
196
2
  export { renderPage };
@@ -4,7 +4,7 @@ if (!Object.hasOwn(globalThis, STORE_KEY)) globalThis[STORE_KEY] = {
4
4
  routes: [],
5
5
  errorRoute: null,
6
6
  config: null,
7
- manifest: void 0
7
+ manifest: null
8
8
  };
9
9
  const store = new Proxy({}, {
10
10
  get: (_, prop) => globalThis[STORE_KEY][prop],
package/dist/vite.d.mts CHANGED
@@ -1,12 +1,6 @@
1
1
  import { Plugin } from "vite";
2
-
3
2
  //#region src/vite-plugin.d.ts
4
- declare function routerPlugin({
5
- pagesDir,
6
- serverEntry,
7
- apiPrefix,
8
- prerender
9
- }?: {
3
+ declare function routerPlugin({ pagesDir, serverEntry, apiPrefix, prerender }?: {
10
4
  /**
11
5
  * The directory where your page components are located.
12
6
  * This is where the plugin will look for your page files to generate routes.
@@ -26,7 +20,8 @@ declare function routerPlugin({
26
20
  */
27
21
  apiPrefix?: string;
28
22
  /**
29
- * Whether to prerender the pages.
23
+ * Whether to prerender the pages by default.
24
+ * Individual pages can override this via +prerender.ts.
30
25
  * @default false
31
26
  */
32
27
  prerender?: boolean;
package/dist/vite.mjs CHANGED
@@ -1,3 +1,4 @@
1
+ import { t as renderPage } from "./server-Cm1ZA_Dp.mjs";
1
2
  import fs from "node:fs";
2
3
  import path from "node:path";
3
4
  import { Readable } from "node:stream";
@@ -102,17 +103,18 @@ function routerPlugin({ pagesDir = "pages", serverEntry = "server/index", apiPre
102
103
  const isProd = process.env.NODE_ENV === "production";
103
104
  let viteConfigRoot;
104
105
  let outDir;
105
- const virtualModuleId = "virtual:routes";
106
- const virtualManifestId = "virtual:client-manifest";
107
- const virtualRendererId = "virtual:vike-lite/renderer";
108
- const virtualEntryClientId = "virtual:entry-client";
109
- const virtualSetupId = "virtual:vike-lite/setup";
110
- const virtualEntryServerId = "virtual:entry-server";
111
- const resolvedVirtualModuleId = "\0virtual:routes";
112
- const resolvedVirtualManifestId = "\0virtual:client-manifest";
113
- const resolvedVirtualEntryClientId = "\0virtual:entry-client";
114
- const resolvedVirtualSetupId = "\0virtual:vike-lite/setup";
115
- const resolvedVirtualEntryServerId = "\0virtual:entry-server";
106
+ let hasAnyPrerender;
107
+ const VIRTUAL = {
108
+ routes: "virtual:routes",
109
+ manifest: "virtual:client-manifest",
110
+ renderer: "virtual:vike-lite/renderer",
111
+ entryClient: "virtual:entry-client",
112
+ setup: "virtual:vike-lite/setup",
113
+ entryServer: "virtual:entry-server",
114
+ entryPrerender: "virtual:entry-prerender"
115
+ };
116
+ const VIRTUAL_VALUES = new Set(Object.values(VIRTUAL));
117
+ const RESOLVED = Object.fromEntries(Object.entries(VIRTUAL).map(([k, v]) => [k, `\0${v}`]));
116
118
  return {
117
119
  name: "vike-lite",
118
120
  config(config, { mode }) {
@@ -120,6 +122,9 @@ function routerPlugin({ pagesDir = "pages", serverEntry = "server/index", apiPre
120
122
  for (const key in envVariables) if (process.env[key] === void 0) process.env[key] = envVariables[key];
121
123
  outDir = config.build?.outDir ?? "dist";
122
124
  const { emptyOutDir, minify = true, cssMinify = true, sourcemap } = config.build || {};
125
+ viteConfigRoot = config.root ? path.resolve(config.root) : process.cwd();
126
+ const { routes } = generateRoutes(viteConfigRoot, pagesDir);
127
+ hasAnyPrerender = prerender || routes.some((r) => r.prerender);
123
128
  return {
124
129
  appType: "custom",
125
130
  ssr: { noExternal: [/^vike-lite(?:$|-)/] },
@@ -132,7 +137,7 @@ function routerPlugin({ pagesDir = "pages", serverEntry = "server/index", apiPre
132
137
  sourcemap,
133
138
  manifest: true,
134
139
  rolldownOptions: {
135
- input: virtualEntryClientId,
140
+ input: VIRTUAL.entryClient,
136
141
  output: {
137
142
  format: "esm",
138
143
  hoistTransitiveImports: false,
@@ -184,7 +189,10 @@ function routerPlugin({ pagesDir = "pages", serverEntry = "server/index", apiPre
184
189
  minify,
185
190
  sourcemap,
186
191
  rolldownOptions: {
187
- input: virtualEntryServerId,
192
+ input: hasAnyPrerender ? {
193
+ index: VIRTUAL.entryServer,
194
+ prerender: VIRTUAL.entryPrerender
195
+ } : VIRTUAL.entryServer,
188
196
  output: {
189
197
  format: "esm",
190
198
  entryFileNames: "index.mjs",
@@ -199,21 +207,16 @@ function routerPlugin({ pagesDir = "pages", serverEntry = "server/index", apiPre
199
207
  };
200
208
  },
201
209
  configResolved(config) {
202
- viteConfigRoot = config.root;
203
210
  if (!config.plugins.some((plugin) => plugin.name?.startsWith("vike-lite-") && SUPPORTED_RENDERERS.includes(plugin.name.replace("vike-lite-", "")))) throw new Error(`No UI renderer plugin found in 'vite.config': please install and configure one of ${SUPPORTED_RENDERERS.map((r) => `vike-lite-${r}`).join(", ")}`);
204
211
  },
205
212
  resolveId(id) {
206
- if (id === virtualModuleId) return resolvedVirtualModuleId;
207
- if (id === virtualManifestId) return resolvedVirtualManifestId;
208
- if (id === virtualEntryClientId) return resolvedVirtualEntryClientId;
209
- if (id === virtualSetupId) return resolvedVirtualSetupId;
210
- if (id === virtualEntryServerId) return resolvedVirtualEntryServerId;
213
+ if (VIRTUAL_VALUES.has(id)) return "\0" + id;
211
214
  },
212
215
  async load(id, options) {
213
- if (id === resolvedVirtualModuleId) {
216
+ if (id === RESOLVED.routes) {
214
217
  const { routes, errorRoute } = generateRoutes(viteConfigRoot, pagesDir);
215
218
  const isSSR = options.ssr;
216
- let code = `import { onRenderHtml } from '${virtualRendererId}';\nexport const config = { onRenderHtml };\nexport const routes = [\n`;
219
+ let code = `import { onRenderHtml } from '${VIRTUAL.renderer}';\nexport const config = { onRenderHtml };\nexport const routes = [\n`;
217
220
  for (const r of routes) {
218
221
  code += `{path:'${r.path}',page:'${r.page}',Page:()=>import('/${r.page}'),`;
219
222
  if (r.head) code += `head:'${r.head}',Head:()=>import('/${r.head}'),`;
@@ -238,44 +241,30 @@ function routerPlugin({ pagesDir = "pages", serverEntry = "server/index", apiPre
238
241
  } else code += "export const errorRoute=null;\n";
239
242
  return code;
240
243
  }
241
- if (id === resolvedVirtualManifestId) {
244
+ if (id === RESOLVED.manifest) {
242
245
  if (!isProd || !options?.ssr) return "export default {}";
243
246
  const manifestPath = path.join(viteConfigRoot, outDir, "client/.vite/manifest.json");
244
247
  return `export default ${fs.readFileSync(manifestPath, "utf8")}`;
245
248
  }
246
- if (id === resolvedVirtualEntryClientId) return `import { routes, errorRoute } from '${virtualModuleId}';
247
- import { onRenderClient } from '${virtualRendererId}';
248
- const { default: render } = await onRenderClient();
249
- await render({ routes, errorRoute });`;
250
- if (id === resolvedVirtualSetupId) return `import { routes, errorRoute, config } from '${virtualModuleId}';
251
- import { setVikeState } from 'vike-lite/__internal/server';
252
- let manifest;
253
- if (process.env.NODE_ENV === 'production') {
254
- manifest = (await import('${virtualManifestId}')).default;
255
- }
256
- setVikeState({ routes, errorRoute, config, manifest });`;
257
- if (id === resolvedVirtualEntryServerId) {
258
- let hasCustomServer = false;
259
- let customServerPath = "";
249
+ if (id === RESOLVED.entryClient) return `import{routes,errorRoute}from'${VIRTUAL.routes}';import{onRenderClient}from'${VIRTUAL.renderer}';const{default:render}=await onRenderClient();await render({routes,errorRoute});`;
250
+ if (id === RESOLVED.setup) return `import{routes,errorRoute,config}from'${VIRTUAL.routes}';import{setVikeState}from'vike-lite/__internal/server';const manifest=process.env.NODE_ENV==='production'?(await import('${VIRTUAL.manifest}')).default:null;setVikeState({routes,errorRoute,config,manifest});`;
251
+ if (id === RESOLVED.entryServer) {
260
252
  if (serverEntry) {
261
253
  const basePath = path.resolve(viteConfigRoot, serverEntry);
254
+ let serverEntryPath = "";
262
255
  for (const ext of [
263
256
  ".ts",
264
257
  ".js",
265
258
  ".mjs"
266
259
  ]) if (fs.existsSync(basePath + ext)) {
267
- hasCustomServer = true;
268
- customServerPath = (basePath + ext).replaceAll("\\", "/");
260
+ serverEntryPath = (basePath + ext).replaceAll("\\", "/");
269
261
  break;
270
262
  }
263
+ if (!serverEntryPath) throw new Error(`[vike-lite] serverEntry ${serverEntry} file not found!`);
264
+ return `import '${VIRTUAL.setup}';export * from'${serverEntryPath}';export{default}from'${serverEntryPath}';`;
271
265
  }
272
- if (hasCustomServer) return `import '${virtualSetupId}';` + (prerender ? `export{routes}from'${virtualModuleId}';` : "") + `export * from'${customServerPath}';export{default}from'${customServerPath}';`;
273
- return `import '${virtualSetupId}';` + (prerender ? `export{routes}from'${virtualModuleId}';` : "") + `import { renderPage } from 'vike-lite/server';
274
- export default {
275
- async fetch(request) {
276
- return renderPage(request);
277
- }
278
- };
266
+ return `import '${VIRTUAL.setup}';import{renderPage}from'vike-lite/server';
267
+ export default{async fetch(request){return renderPage(request);}};
279
268
  if (process.env.NODE_ENV === 'production') {
280
269
  const { createServer } = await import('node:http');
281
270
  const { Readable } = await import('node:stream');
@@ -291,18 +280,27 @@ if (process.env.NODE_ENV === 'production') {
291
280
  '.mjs': 'text/javascript; charset=utf-8',
292
281
  '.css': 'text/css; charset=utf-8',
293
282
  '.json': 'application/json; charset=utf-8',
283
+ '.xml': 'application/xml',
284
+ '.txt': 'text/plain; charset=utf-8',
285
+ '.map': 'application/json; charset=utf-8',
294
286
  '.png': 'image/png',
295
287
  '.jpg': 'image/jpeg',
296
288
  '.jpeg': 'image/jpeg',
297
289
  '.gif': 'image/gif',
298
290
  '.svg': 'image/svg+xml',
299
291
  '.ico': 'image/x-icon',
300
- '.webmanifest': 'application/manifest+json',
301
- '.xml': 'application/xml',
302
- '.txt': 'text/plain; charset=utf-8',
292
+ '.webp': 'image/webp',
293
+ '.avif': 'image/avif',
303
294
  '.woff': 'font/woff',
304
295
  '.woff2': 'font/woff2',
305
- '.wasm': 'application/wasm'
296
+ '.ttf': 'font/ttf',
297
+ '.otf': 'font/otf',
298
+ '.mp4': 'video/mp4',
299
+ '.webm': 'video/webm',
300
+ '.mp3': 'audio/mpeg',
301
+ '.pdf': 'application/pdf',
302
+ '.wasm': 'application/wasm',
303
+ '.webmanifest': 'application/manifest+json'
306
304
  };
307
305
  const { BASE_URL } = import.meta.env;
308
306
  const server = createServer(async (req, res) => {
@@ -334,22 +332,13 @@ if (process.env.NODE_ENV === 'production') {
334
332
  }
335
333
  const { method } = req;
336
334
  const init = { method, headers };
337
- if (req.method === 'HEAD') {
338
- res.end()
339
- return
340
- }
341
- if (method !== 'GET' && method !== 'HEAD') {
342
- init.body = Readable.toWeb(req);
343
- init.duplex = 'half';
344
- }
335
+ if (method !== 'GET' && method !== 'HEAD') { init.body = Readable.toWeb(req); init.duplex = 'half'; }
345
336
  const request = new Request(urlObj.href, init);
346
337
  const response = await renderPage(request);
347
338
  res.statusCode = response.status;
348
- for (const [key, val] of response.headers) {
349
- res.setHeader(key, val);
350
- }
351
- if (response.body) await pipeline(Readable.fromWeb(response.body), res);
352
- else res.end();
339
+ for (const [key, val] of response.headers) res.setHeader(key, val);
340
+ if (method === 'HEAD' || !response.body) { await response.body?.cancel(); res.end(); return; }
341
+ try { await pipeline(Readable.fromWeb(response.body), res); } catch {}
353
342
  } catch (e) {
354
343
  console.error(e);
355
344
  res.statusCode = 500;
@@ -363,19 +352,18 @@ if (process.env.NODE_ENV === 'production') {
363
352
  });
364
353
  }`;
365
354
  }
355
+ if (id === RESOLVED.entryPrerender) return `import'${VIRTUAL.setup}';export{routes}from'${VIRTUAL.routes}';`;
366
356
  },
367
357
  async closeBundle() {
368
- if (!prerender || !isProd || !import.meta.env.SSR) return;
369
- await new Promise((resolve) => setTimeout(resolve, 500));
358
+ if (!isProd || this.environment.name !== "ssr") return;
370
359
  const { pathToFileURL } = await import("node:url");
371
- const serverEntryPath = path.resolve(viteConfigRoot, outDir, "server/index.mjs");
372
- if (!fs.existsSync(serverEntryPath)) return;
373
- const serverModule = await import(pathToFileURL(serverEntryPath).href);
374
- const app = serverModule.default;
375
- const routes = serverModule.routes || [];
360
+ const prerenderPath = path.resolve(viteConfigRoot, outDir, "server/prerender.mjs");
361
+ if (!fs.existsSync(prerenderPath)) return;
362
+ const { routes } = await import(pathToFileURL(prerenderPath).href);
363
+ if (!routes) return;
376
364
  const urlsToPrerender = /* @__PURE__ */ new Set();
377
365
  for (const route of routes) {
378
- let shouldPrerender = true;
366
+ let shouldPrerender = prerender;
379
367
  let dynamicUrls = [];
380
368
  if (route.Prerender) {
381
369
  const mod = await route.Prerender();
@@ -389,7 +377,11 @@ if (process.env.NODE_ENV === 'production') {
389
377
  }
390
378
  }
391
379
  if (shouldPrerender) {
392
- if (!route.path.includes(":")) urlsToPrerender.add(route.path === "/" ? "/" : route.path);
380
+ if (route.path.includes(":") && dynamicUrls.length === 0) {
381
+ console.warn(`[vike-lite] ⚠️ Skipping dynamic route "${route.path}": no URLs provided by +prerender. Return an array of URLs to prerender it.`);
382
+ continue;
383
+ }
384
+ if (!route.path.includes(":")) urlsToPrerender.add(route.path);
393
385
  for (const url of dynamicUrls) urlsToPrerender.add(url);
394
386
  }
395
387
  }
@@ -400,21 +392,19 @@ if (process.env.NODE_ENV === 'production') {
400
392
  let generatedCount = 0;
401
393
  const clientDir = path.resolve(viteConfigRoot, outDir, "client");
402
394
  for (const urlPath of urlsToPrerender) {
403
- const htmlReq = new Request(`http://localhost${baseNoSlash}${urlPath}`);
404
- const htmlRes = await app.fetch(htmlReq);
405
- if (htmlRes.ok && htmlRes.headers.get("content-type")?.includes("text/html")) {
395
+ const htmlRes = await renderPage(new Request(`http://localhost${baseNoSlash}${urlPath}`));
396
+ if (htmlRes && htmlRes.ok && htmlRes.headers.get("content-type")?.includes("text/html")) {
406
397
  const outDirRoute = path.join(clientDir, urlPath === "/" ? "" : urlPath);
407
398
  fs.mkdirSync(outDirRoute, { recursive: true });
408
399
  fs.writeFileSync(path.join(outDirRoute, "index.html"), await htmlRes.text());
409
- } else console.error(`\u{1B}[31m✖ SSG HTML Error for ${urlPath}\u{1B}[0m`);
400
+ } else throw new Error(`\u{1B}[31m✖ SSG HTML Error for ${urlPath}\u{1B}[0m`);
410
401
  const jsonTarget = urlPath === "/" ? "/index" : urlPath;
411
- const jsonReq = new Request(`http://localhost${baseNoSlash}${jsonTarget}.pageContext.json`);
412
- const jsonRes = await app.fetch(jsonReq);
413
- if (jsonRes.ok) {
402
+ const jsonRes = await renderPage(new Request(`http://localhost${baseNoSlash}${jsonTarget}.pageContext.json`));
403
+ if (jsonRes && jsonRes.ok) {
414
404
  const jsonOutPath = path.join(clientDir, `${jsonTarget}.pageContext.json`);
415
405
  fs.mkdirSync(path.dirname(jsonOutPath), { recursive: true });
416
406
  fs.writeFileSync(jsonOutPath, await jsonRes.text());
417
- } else console.error(`\u{1B}[31m✖ SSG JSON Error for ${jsonTarget}\u{1B}[0m`);
407
+ } else throw new Error(`\u{1B}[31m✖ SSG JSON Error for ${jsonTarget}\u{1B}[0m`);
418
408
  console.log(`\u{1B}[32m✓\u{1B}[0m Pre-rendered: ${urlPath}`);
419
409
  generatedCount++;
420
410
  }
@@ -426,14 +416,14 @@ if (process.env.NODE_ENV === 'production') {
426
416
  server.watcher.on("all", (event, file) => {
427
417
  if (!((event === "add" || event === "unlink") && file.startsWith(pagesPath))) return;
428
418
  for (const env of Object.values(server.environments)) {
429
- const mod = env.moduleGraph.getModuleById(resolvedVirtualModuleId);
419
+ const mod = env.moduleGraph.getModuleById(RESOLVED.routes);
430
420
  if (mod) env.moduleGraph.invalidateModule(mod);
431
421
  }
432
422
  server.ws.send({ type: "full-reload" });
433
423
  });
434
424
  server.middlewares.use(async (req, res, next) => {
435
425
  try {
436
- const { default: app } = await server.environments.ssr.runner.import(resolvedVirtualEntryServerId);
426
+ const { default: app } = await server.environments.ssr.runner.import(RESOLVED.entryServer);
437
427
  const headers = new Headers();
438
428
  for (const [key, value] of Object.entries(req.headers)) {
439
429
  if (key.startsWith(":")) continue;
@@ -461,11 +451,15 @@ if (process.env.NODE_ENV === 'production') {
461
451
  return;
462
452
  }
463
453
  for (const [key, value] of response.headers) res.setHeader(key, value);
464
- if (!response.body) {
465
- res.end();
454
+ if (req.method === "HEAD" || !response.body) {
455
+ await response.body?.cancel();
456
+ if (!res.destroyed && !res.closed) res.end();
457
+ return;
458
+ }
459
+ if (res.destroyed || res.closed) {
460
+ await response.body.cancel();
466
461
  return;
467
462
  }
468
- if (res.destroyed || res.closed) return;
469
463
  try {
470
464
  await pipeline(Readable.fromWeb(response.body), res);
471
465
  } catch {}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vike-lite",
3
- "version": "1.11.1",
3
+ "version": "1.13.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "module": "./dist/index.mjs",
@@ -46,12 +46,13 @@
46
46
  "dist"
47
47
  ],
48
48
  "scripts": {
49
+ "tsc": "tsc --noEmit",
49
50
  "build": "tsdown"
50
51
  },
51
52
  "devDependencies": {
52
- "@types/node": "^26.1.0",
53
- "tsdown": "^0.22.3",
54
- "vite": "^8.1.3"
53
+ "@types/node": "^26.1.1",
54
+ "tsdown": "^0.22.4",
55
+ "vite": "^8.1.4"
55
56
  },
56
57
  "peerDependencies": {
57
58
  "vite": ">=8"
@@ -1,34 +0,0 @@
1
- //#region src/__internal/shared/matchRoute.d.ts
2
- declare function matchRoute(urlPathname: string, routes: typeof import('virtual:routes').routes): {
3
- route: any;
4
- routeParams: Record<string, string>;
5
- } | null;
6
- //#endregion
7
- //#region src/__internal/shared/index.d.ts
8
- interface RenderContext {
9
- pageContext: any;
10
- Page: unknown;
11
- Head?: unknown;
12
- Layout?: unknown;
13
- pageTitleTag: string;
14
- serializedContext: string;
15
- assets: {
16
- cssLinks: string;
17
- jsPreloads: string;
18
- entryClient: string;
19
- };
20
- nonce?: string;
21
- }
22
- type Config = {
23
- onRenderHtml: () => Promise<{
24
- default: (ctx: RenderContext) => Promise<string>;
25
- }>;
26
- onRenderClient: () => Promise<{
27
- default: (opts: {
28
- routes: any[];
29
- errorRoute: any;
30
- }) => void;
31
- }>;
32
- };
33
- //#endregion
34
- export { RenderContext as n, matchRoute as r, Config as t };
@@ -1,4 +0,0 @@
1
- //#region src/shared/index.ts
2
- const { BASE_URL } = import.meta.env;
3
- //#endregion
4
- export { BASE_URL as t };