@qwik.dev/router 2.0.0-beta.28 → 2.0.0-beta.29

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.
Files changed (42) hide show
  1. package/lib/adapters/azure-swa/vite/index.mjs +31 -36
  2. package/lib/adapters/bun-server/vite/index.mjs +0 -3
  3. package/lib/adapters/cloud-run/vite/index.mjs +0 -3
  4. package/lib/adapters/cloudflare-pages/vite/index.mjs +15 -9
  5. package/lib/adapters/deno-server/vite/index.mjs +7 -5
  6. package/lib/adapters/netlify-edge/vite/index.mjs +13 -23
  7. package/lib/adapters/node-server/vite/index.mjs +0 -3
  8. package/lib/adapters/shared/vite/index.d.ts +1 -7
  9. package/lib/adapters/shared/vite/index.mjs +164 -136
  10. package/lib/adapters/ssg/vite/index.mjs +3 -4
  11. package/lib/adapters/vercel-edge/vite/index.mjs +25 -9
  12. package/lib/chunks/error-handler.mjs +26 -26
  13. package/lib/chunks/fs.mjs +28 -138
  14. package/lib/chunks/http-error.qwik.mjs +27 -0
  15. package/lib/chunks/not-found-wrapper.qwik.mjs +25 -0
  16. package/lib/chunks/pathname.mjs +105 -0
  17. package/lib/chunks/routing.qwik.mjs +592 -216
  18. package/lib/chunks/system.mjs +328 -0
  19. package/lib/chunks/use-functions.qwik.mjs +35 -0
  20. package/lib/chunks/worker-thread.mjs +271 -0
  21. package/lib/index.d.ts +136 -102
  22. package/lib/index.qwik.mjs +699 -751
  23. package/lib/middleware/aws-lambda/index.mjs +7 -1
  24. package/lib/middleware/azure-swa/index.mjs +7 -2
  25. package/lib/middleware/bun/index.mjs +20 -5
  26. package/lib/middleware/cloudflare-pages/index.mjs +8 -2
  27. package/lib/middleware/deno/index.mjs +19 -5
  28. package/lib/middleware/netlify-edge/index.mjs +8 -2
  29. package/lib/middleware/node/index.mjs +10 -14
  30. package/lib/middleware/request-handler/index.d.ts +82 -12
  31. package/lib/middleware/request-handler/index.mjs +661 -524
  32. package/lib/middleware/vercel-edge/index.mjs +8 -2
  33. package/lib/modules.d.ts +7 -4
  34. package/lib/ssg/index.d.ts +48 -16
  35. package/lib/ssg/index.mjs +320 -7
  36. package/lib/vite/index.d.ts +6 -0
  37. package/lib/vite/index.mjs +1098 -641
  38. package/modules.d.ts +7 -4
  39. package/package.json +4 -4
  40. package/lib/chunks/format-error.mjs +0 -137
  41. package/lib/chunks/index.mjs +0 -896
  42. package/lib/chunks/types.qwik.mjs +0 -22
@@ -81,13 +81,19 @@ function createQwikRouter(opts) {
81
81
  const notFoundHtml = !request.headers.get("accept")?.includes("text/html") || isStaticPath(request.method || "GET", url) ? "Not Found" : getNotFound(url.pathname);
82
82
  return new Response(notFoundHtml, {
83
83
  status: 404,
84
- headers: { "Content-Type": "text/html; charset=utf-8", "X-Not-Found": url.pathname }
84
+ headers: {
85
+ "Content-Type": "text/html; charset=utf-8",
86
+ "X-Not-Found": url.pathname
87
+ }
85
88
  });
86
89
  } catch (e) {
87
90
  console.error(e);
88
91
  return new Response(isDev ? String(e || "Error") : "Internal Server Error", {
89
92
  status: 500,
90
- headers: { "Content-Type": "text/plain; charset=utf-8", "X-Error": "vercel-edge" }
93
+ headers: {
94
+ "Content-Type": "text/plain; charset=utf-8",
95
+ "X-Error": "vercel-edge"
96
+ }
91
97
  });
92
98
  }
93
99
  }
package/lib/modules.d.ts CHANGED
@@ -1,15 +1,18 @@
1
1
  declare module '@qwik-router-config' {
2
- export const routes: any[];
3
- export const menus: any[];
2
+ import type { RouteData, RouteModule } from '@qwik.dev/router';
3
+ export const routes: RouteData;
4
+ export const serverPlugins: RouteModule[];
4
5
  export const trailingSlash: boolean;
5
6
  export const basePathname: string;
6
7
  export const cacheModules: boolean;
8
+ export const fallthrough: boolean;
7
9
  const defaultExport: {
8
- routes: any[];
9
- menus: any[];
10
+ routes: RouteData;
11
+ serverPlugins: RouteModule[];
10
12
  trailingSlash: boolean;
11
13
  basePathname: string;
12
14
  cacheModules: boolean;
15
+ fallthrough: boolean;
13
16
  };
14
17
  export default defaultExport;
15
18
  }
@@ -1,3 +1,5 @@
1
+ import type { QwikRouterConfig } from '@qwik.dev/router';
2
+ import type { Render } from '@qwik.dev/core/server';
1
3
  import type { RenderOptions } from '@qwik.dev/core/server';
2
4
 
3
5
  /**
@@ -6,7 +8,43 @@ import type { RenderOptions } from '@qwik.dev/core/server';
6
8
  *
7
9
  * @public
8
10
  */
9
- export declare function generate(opts: StaticGenerateOptions): Promise<StaticGenerateResult>;
11
+ export declare function generate(opts: SsgInternalOptions): Promise<StaticGenerateResult>;
12
+
13
+ /**
14
+ * Run SSG as a standalone process. Writes `_static-paths.json` to `outDir` and exits with code 0 on
15
+ * success, 1 on error.
16
+ *
17
+ * Intended to be called from a generated `run-ssg.js` wrapper that provides render/config.
18
+ *
19
+ * @public
20
+ */
21
+ export declare function runSsg(opts: SsgInternalOptions): Promise<never>;
22
+
23
+ /**
24
+ * Internal SSG options that include the render function and router config.
25
+ *
26
+ * @public
27
+ */
28
+ export declare interface SsgInternalOptions extends SsgOptions {
29
+ /** The SSR render function (default export from entry.ssr). */
30
+ render: Render;
31
+ /** The Qwik Router Config object (default export from `@qwik-router-config`). */
32
+ qwikRouterConfig: QwikRouterConfig;
33
+ /**
34
+ * Path or URL to the worker entry file. Workers are spawned using this file. When run-ssg.js
35
+ * serves as both main and worker entry, this should be `import.meta.url` of that file.
36
+ */
37
+ workerFilePath?: string | URL;
38
+ }
39
+
40
+ /** @public */
41
+ declare interface SsgOptions extends SsgRenderOptions {
42
+ /** Defaults to `/` */
43
+ basePathname?: string;
44
+ rootDir?: string;
45
+ }
46
+ export { SsgOptions }
47
+ export { SsgOptions as StaticGenerateOptions }
10
48
 
11
49
  /** @public */
12
50
  export declare interface SsgRenderOptions extends RenderOptions {
@@ -69,21 +107,15 @@ export declare interface SsgRenderOptions extends RenderOptions {
69
107
  exclude?: string[];
70
108
  }
71
109
 
72
- /** @public */
73
- export declare interface StaticGenerateOptions extends SsgRenderOptions {
74
- /**
75
- * Path to the SSR module exporting the default render function. In most cases it'll be
76
- * `./src/entry.ssr.tsx`.
77
- */
78
- renderModulePath: string;
79
- /** Path to the Qwik Router Config module exporting the default `@qwik-router-config`. */
80
- qwikRouterConfigModulePath: string;
81
- /** @deprecated Use `qwikRouterConfigModulePath` instead. Will be removed in V3 */
82
- qwikCityPlanModulePath?: string;
83
- /** Defaults to `/` */
84
- basePathname?: string;
85
- rootDir?: string;
86
- }
110
+ /**
111
+ * Bootstrap a worker thread for SSG rendering. Called from the generated `run-ssg.js` when it
112
+ * detects it's running as a worker thread.
113
+ *
114
+ * @param opts - Must include `render` and `qwikRouterConfig` (imported by the worker entry). The
115
+ * remaining serializable options come from `workerData`.
116
+ * @public
117
+ */
118
+ export declare function startWorker(opts: Pick<SsgInternalOptions, 'render' | 'qwikRouterConfig'>): Promise<void>;
87
119
 
88
120
  /** @public */
89
121
  export declare interface StaticGenerateResult {
package/lib/ssg/index.mjs CHANGED
@@ -1,14 +1,327 @@
1
+ import { bold, green, dim, red, magenta } from 'kleur/colors';
2
+ import { relative } from 'node:path';
3
+ import { m as msToString, g as getPathnameForDynamicRoute } from '../chunks/pathname.mjs';
4
+
5
+ function routeToRegExp(rule) {
6
+ let transformedRule;
7
+ if (rule === "/" || rule === "/*") {
8
+ transformedRule = rule;
9
+ } else if (rule.endsWith("/*")) {
10
+ transformedRule = `${rule.substring(0, rule.length - 2)}(/*)?`;
11
+ } else if (rule.endsWith("/")) {
12
+ transformedRule = `${rule.substring(0, rule.length - 1)}(/)?`;
13
+ } else if (rule.endsWith("*")) {
14
+ transformedRule = rule;
15
+ } else {
16
+ transformedRule = `${rule}(/)?`;
17
+ }
18
+ transformedRule = `^${transformedRule.replace(/\*/g, ".*")}$`;
19
+ return new RegExp(transformedRule);
20
+ }
21
+ function routesToRegExps(routes) {
22
+ if (!Array.isArray(routes)) {
23
+ return [];
24
+ }
25
+ return routes.filter((r) => typeof r === "string").map(routeToRegExp);
26
+ }
27
+ function createRouteTester(basePathname, includeRoutes, excludeRoutes) {
28
+ const includes = routesToRegExps(includeRoutes);
29
+ const excludes = routesToRegExps(excludeRoutes);
30
+ return (pathname) => {
31
+ if (pathname.endsWith("404.html")) {
32
+ return true;
33
+ }
34
+ if (basePathname !== "/") {
35
+ pathname = pathname.slice(basePathname.length - 1);
36
+ }
37
+ for (const exclude of excludes) {
38
+ if (exclude.test(pathname)) {
39
+ return false;
40
+ }
41
+ }
42
+ for (const include of includes) {
43
+ if (include.test(pathname)) {
44
+ return true;
45
+ }
46
+ }
47
+ return false;
48
+ };
49
+ }
50
+
51
+ function validateOptions(opts) {
52
+ if (!opts.render) {
53
+ throw new Error(`Missing "render" option`);
54
+ }
55
+ if (!opts.qwikRouterConfig) {
56
+ throw new Error(`Missing "qwikRouterConfig" option`);
57
+ }
58
+ let siteOrigin = opts.origin;
59
+ if (typeof siteOrigin !== "string" || siteOrigin.trim().length === 0) {
60
+ throw new Error(`Missing "origin" option`);
61
+ }
62
+ siteOrigin = siteOrigin.trim();
63
+ if (!/:\/\//.test(siteOrigin) || siteOrigin.startsWith("://")) {
64
+ throw new Error(`"origin" must start with a valid protocol, such as "https://" or "http://", received "${siteOrigin}"`);
65
+ }
66
+ try {
67
+ new URL(siteOrigin);
68
+ } catch (e) {
69
+ throw new Error(`Invalid "origin"`, {
70
+ cause: e
71
+ });
72
+ }
73
+ }
74
+ async function mainThread(sys) {
75
+ const opts = sys.getOptions();
76
+ validateOptions(opts);
77
+ const main = await sys.createMainProcess();
78
+ const log = await sys.createLogger();
79
+ log.info("\n" + bold(green("Starting Qwik Router SSG...")));
80
+ const qwikRouterConfig = opts.qwikRouterConfig;
81
+ const queue = [];
82
+ const active = /* @__PURE__ */ new Set();
83
+ const routes = qwikRouterConfig.routes;
84
+ const trailingSlash = !!qwikRouterConfig.trailingSlash;
85
+ const includeRoute = createRouteTester(opts.basePathname || "/", opts.include, opts.exclude);
86
+ return new Promise((resolve, reject) => {
87
+ try {
88
+ const timer = sys.createTimer();
89
+ const generatorResult = {
90
+ duration: 0,
91
+ rendered: 0,
92
+ errors: 0,
93
+ staticPaths: []
94
+ };
95
+ let isCompleted = false;
96
+ let isRoutesLoaded = false;
97
+ const completed = async () => {
98
+ const closePromise = main.close();
99
+ generatorResult.duration = timer();
100
+ if (generatorResult.errors === 0) {
101
+ log.info(`
102
+ ${green("SSG results")}`);
103
+ if (generatorResult.rendered > 0) {
104
+ log.info(`- Generated: ${dim(`${generatorResult.rendered} page${generatorResult.rendered === 1 ? "" : "s"}`)}`);
105
+ }
106
+ log.info(`- Duration: ${dim(msToString(generatorResult.duration))}`);
107
+ const total = generatorResult.rendered + generatorResult.errors;
108
+ if (total > 0) {
109
+ log.info(`- Average: ${dim(msToString(generatorResult.duration / total) + " per page")}`);
110
+ }
111
+ log.info(``);
112
+ }
113
+ closePromise.then(() => {
114
+ setTimeout(() => resolve(generatorResult));
115
+ }).catch(reject);
116
+ };
117
+ const next = () => {
118
+ while (!isCompleted && main.hasAvailableWorker() && queue.length > 0) {
119
+ const staticRoute = queue.shift();
120
+ if (staticRoute) {
121
+ render(staticRoute).catch((e) => {
122
+ console.error(`render failed for ${staticRoute.pathname}`, e);
123
+ });
124
+ }
125
+ }
126
+ if (!isCompleted && isRoutesLoaded && queue.length === 0 && active.size === 0) {
127
+ isCompleted = true;
128
+ completed().catch((e) => {
129
+ console.error("SSG completion failed", e);
130
+ });
131
+ }
132
+ };
133
+ let isPendingDrain = false;
134
+ const flushQueue = () => {
135
+ if (!isPendingDrain) {
136
+ isPendingDrain = true;
137
+ setTimeout(() => {
138
+ isPendingDrain = false;
139
+ next();
140
+ });
141
+ }
142
+ };
143
+ const render = async (staticRoute) => {
144
+ try {
145
+ active.add(staticRoute.pathname);
146
+ const result = await main.render({
147
+ type: "render",
148
+ ...staticRoute
149
+ });
150
+ active.delete(staticRoute.pathname);
151
+ if (result.error) {
152
+ const err = new Error(result.error.message);
153
+ err.stack = result.error.stack;
154
+ log.error(`
155
+ ${bold(red(`!!! ${result.pathname}: Error during SSG`))}`);
156
+ log.error(red(err.message));
157
+ log.error(` Pathname: ${magenta(staticRoute.pathname)}`);
158
+ if (err.stack) {
159
+ log.error(dim(err.stack));
160
+ }
161
+ generatorResult.errors++;
162
+ }
163
+ if (result.filePath != null) {
164
+ generatorResult.rendered++;
165
+ generatorResult.staticPaths.push(result.pathname);
166
+ const base = opts.rootDir ?? opts.outDir;
167
+ const path = relative(base, result.filePath);
168
+ const lastSlash = path.lastIndexOf("/");
169
+ log.info(`${dim(path.slice(0, lastSlash + 1))}${path.slice(lastSlash + 1)}`);
170
+ }
171
+ flushQueue();
172
+ } catch (e) {
173
+ console.error(`render failed for ${staticRoute.pathname}`, e);
174
+ isCompleted = true;
175
+ reject(e);
176
+ }
177
+ };
178
+ const addToQueue = (pathname, params) => {
179
+ if (pathname) {
180
+ pathname = new URL(pathname, `https://qwik.dev`).pathname;
181
+ if (pathname !== opts.basePathname) {
182
+ if (trailingSlash) {
183
+ if (!pathname.endsWith("/")) {
184
+ const segments = pathname.split("/");
185
+ const lastSegment = segments[segments.length - 1];
186
+ if (!lastSegment.includes(".")) {
187
+ pathname += "/";
188
+ }
189
+ }
190
+ } else {
191
+ if (pathname.endsWith("/")) {
192
+ pathname = pathname.slice(0, pathname.length - 1);
193
+ }
194
+ }
195
+ }
196
+ if (includeRoute(pathname) && !queue.some((s) => s.pathname === pathname)) {
197
+ queue.push({
198
+ pathname,
199
+ params
200
+ });
201
+ flushQueue();
202
+ }
203
+ }
204
+ };
205
+ const traverseRouteTree = async (node, pathParts, basePathname, ancestorLoaders) => {
206
+ const currentLoaders = node._L ? [
207
+ ...ancestorLoaders,
208
+ node._L
209
+ ] : ancestorLoaders;
210
+ const index = node._I;
211
+ if (index) {
212
+ const pageLoaders = Array.isArray(index) ? index : [
213
+ ...currentLoaders,
214
+ index
215
+ ];
216
+ const joinedParts = pathParts.join("/");
217
+ const originalPathname = basePathname + (joinedParts ? joinedParts + "/" : "");
218
+ const paramNames = pathParts.filter((p) => p.startsWith("[") && p.endsWith("]")).map((p) => p.startsWith("[...") ? p.slice(4, -1) : p.slice(1, -1));
219
+ const modules = await Promise.all(pageLoaders.filter((l) => typeof l === "function").map((l) => l()));
220
+ const pageModule = modules[modules.length - 1];
221
+ if (paramNames.length > 0) {
222
+ if (typeof pageModule.onStaticGenerate === "function") {
223
+ const staticGenerate = await pageModule.onStaticGenerate({
224
+ env: {
225
+ get(key) {
226
+ return sys.getEnv(key);
227
+ }
228
+ }
229
+ });
230
+ if (Array.isArray(staticGenerate.params)) {
231
+ for (const params of staticGenerate.params) {
232
+ const pathname = getPathnameForDynamicRoute(originalPathname, paramNames, params);
233
+ addToQueue(pathname, params);
234
+ }
235
+ }
236
+ }
237
+ } else {
238
+ addToQueue(originalPathname, void 0);
239
+ }
240
+ }
241
+ if (node._M) {
242
+ for (const group of node._M) {
243
+ await traverseRouteTree(group, pathParts, basePathname, currentLoaders);
244
+ }
245
+ }
246
+ if (node._W && typeof node._W === "object") {
247
+ const childNode = node._W;
248
+ const paramName = childNode._P;
249
+ const pathPart = paramName ? `[${paramName}]` : "[param]";
250
+ await traverseRouteTree(childNode, [
251
+ ...pathParts,
252
+ pathPart
253
+ ], basePathname, currentLoaders);
254
+ }
255
+ if (node._A && typeof node._A === "object") {
256
+ const childNode = node._A;
257
+ const paramName = childNode._P;
258
+ const pathPart = paramName ? `[...${paramName}]` : "[...rest]";
259
+ await traverseRouteTree(childNode, [
260
+ ...pathParts,
261
+ pathPart
262
+ ], basePathname, currentLoaders);
263
+ }
264
+ for (const [key, child] of Object.entries(node)) {
265
+ if (key.charCodeAt(0) === 95) {
266
+ continue;
267
+ }
268
+ const childNode = child;
269
+ await traverseRouteTree(childNode, [
270
+ ...pathParts,
271
+ key
272
+ ], basePathname, currentLoaders);
273
+ }
274
+ };
275
+ const loadStaticRoutes = async () => {
276
+ const basePathname = opts.basePathname || "/";
277
+ await traverseRouteTree(routes, [], basePathname, []);
278
+ isRoutesLoaded = true;
279
+ flushQueue();
280
+ };
281
+ loadStaticRoutes().catch((e) => {
282
+ console.error("SSG route loading failed", e);
283
+ reject(e);
284
+ });
285
+ } catch (e) {
286
+ console.error("SSG main thread failed", e);
287
+ reject(e);
288
+ }
289
+ });
290
+ }
291
+
1
292
  async function generate(opts) {
2
- const ssgPlatform = await getEntryModule();
3
- const result = await ssgPlatform.generate(opts);
4
- return result;
293
+ const { createSystem } = await import('../chunks/system.mjs');
294
+ const sys = await createSystem(opts);
295
+ return mainThread(sys);
5
296
  }
6
- async function getEntryModule() {
297
+ async function runSsg(opts) {
7
298
  try {
8
- return await import('../chunks/index.mjs');
299
+ const result = await generate(opts);
300
+ const fs = await import('node:fs');
301
+ const { join } = await import('node:path');
302
+ const staticPathsFile = join(opts.outDir, "_static-paths.json");
303
+ await fs.promises.writeFile(staticPathsFile, JSON.stringify(result.staticPaths));
304
+ if (result.errors > 0) {
305
+ console.error(`SSG completed with ${result.errors} error(s)`);
306
+ process.exit(1);
307
+ }
308
+ process.exit(0);
9
309
  } catch (e) {
10
- throw new Error(`Unsupported platform`, { cause: e });
310
+ console.error("SSG failed:", e);
311
+ process.exit(1);
11
312
  }
12
313
  }
314
+ async function startWorker(opts) {
315
+ const { workerData } = await import('node:worker_threads');
316
+ const mergedOpts = {
317
+ ...workerData,
318
+ render: opts.render,
319
+ qwikRouterConfig: opts.qwikRouterConfig
320
+ };
321
+ const { createSystem } = await import('../chunks/system.mjs');
322
+ const { workerThread } = await import('../chunks/worker-thread.mjs');
323
+ const sys = await createSystem(mergedOpts, workerData?.threadId);
324
+ await workerThread(sys);
325
+ }
13
326
 
14
- export { generate };
327
+ export { generate, runSsg, startWorker };
@@ -165,6 +165,12 @@ export declare interface QwikRouterVitePluginOptions extends Omit<PluginOptions,
165
165
  * Default: true
166
166
  */
167
167
  devSsrServer?: boolean;
168
+ /**
169
+ * Maximum number of SSR-rendered pages to keep in the in-memory cache. Set to 0 to disable.
170
+ *
171
+ * Default: 50
172
+ */
173
+ ssrCacheSize?: number;
168
174
  }
169
175
 
170
176
  /** @public */