@qwik.dev/router 2.0.0-beta.4 → 2.0.0-beta.40

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 (87) hide show
  1. package/adapters/static/vite.d.ts +1 -1
  2. package/lib/adapters/azure-swa/vite/index.d.ts +2 -2
  3. package/lib/adapters/azure-swa/vite/index.mjs +44 -58
  4. package/lib/adapters/bun-server/vite/index.d.ts +2 -2
  5. package/lib/adapters/bun-server/vite/index.mjs +16 -23
  6. package/lib/adapters/cloud-run/vite/index.d.ts +2 -2
  7. package/lib/adapters/cloud-run/vite/index.mjs +15 -20
  8. package/lib/adapters/cloudflare-pages/vite/index.d.ts +2 -2
  9. package/lib/adapters/cloudflare-pages/vite/index.mjs +45 -77
  10. package/lib/adapters/deno-server/vite/index.d.ts +2 -2
  11. package/lib/adapters/deno-server/vite/index.mjs +25 -35
  12. package/lib/adapters/netlify-edge/vite/index.d.ts +2 -2
  13. package/lib/adapters/netlify-edge/vite/index.mjs +49 -89
  14. package/lib/adapters/node-server/vite/index.d.ts +2 -2
  15. package/lib/adapters/node-server/vite/index.mjs +16 -23
  16. package/lib/adapters/shared/vite/index.d.ts +9 -22
  17. package/lib/adapters/shared/vite/index.mjs +273 -333
  18. package/lib/adapters/ssg/vite/index.d.ts +13 -0
  19. package/lib/adapters/ssg/vite/index.mjs +13 -0
  20. package/lib/adapters/vercel-edge/vite/index.d.ts +3 -3
  21. package/lib/adapters/vercel-edge/vite/index.mjs +73 -80
  22. package/lib/chunks/dev-preloaded-route-loader.mjs +2 -0
  23. package/lib/chunks/format.mjs +24 -0
  24. package/lib/chunks/fs.mjs +133 -0
  25. package/lib/chunks/head.qwik.mjs +1217 -0
  26. package/lib/chunks/http-error.qwik.mjs +27 -0
  27. package/lib/chunks/pathname.mjs +57 -0
  28. package/lib/chunks/request-body-limit.mjs +27 -0
  29. package/lib/chunks/request-path.mjs +49 -0
  30. package/lib/chunks/routes.mjs +26 -0
  31. package/lib/chunks/server-error.mjs +16 -0
  32. package/lib/chunks/system.mjs +265 -0
  33. package/lib/chunks/url.mjs +59 -0
  34. package/lib/chunks/user-response.mjs +1707 -0
  35. package/lib/chunks/worker-thread.mjs +264 -0
  36. package/lib/index.d.ts +626 -170
  37. package/lib/index.qwik.mjs +1649 -1964
  38. package/lib/middleware/aws-lambda/index.d.ts +15 -16
  39. package/lib/middleware/aws-lambda/index.mjs +45 -54
  40. package/lib/middleware/azure-swa/index.mjs +85 -295
  41. package/lib/middleware/bun/index.d.ts +20 -5
  42. package/lib/middleware/bun/index.mjs +119 -205
  43. package/lib/middleware/cloudflare-pages/index.mjs +83 -105
  44. package/lib/middleware/deno/index.d.ts +22 -5
  45. package/lib/middleware/deno/index.mjs +105 -192
  46. package/lib/middleware/firebase/index.mjs +22 -35
  47. package/lib/middleware/netlify-edge/index.mjs +63 -81
  48. package/lib/middleware/node/index.d.ts +17 -5
  49. package/lib/middleware/node/index.mjs +299 -273
  50. package/lib/middleware/request-handler/index.d.ts +206 -99
  51. package/lib/middleware/request-handler/index.mjs +117 -1648
  52. package/lib/middleware/vercel-edge/index.mjs +76 -101
  53. package/lib/modules.d.ts +11 -16
  54. package/lib/service-worker/index.mjs +13 -0
  55. package/lib/{static → ssg}/index.d.ts +46 -22
  56. package/lib/ssg/index.mjs +257 -0
  57. package/lib/vite/index.d.ts +58 -10
  58. package/lib/vite/index.mjs +2459 -27446
  59. package/modules.d.ts +11 -16
  60. package/package.json +63 -67
  61. package/ssg.d.ts +2 -0
  62. package/static.d.ts +1 -1
  63. package/lib/adapters/azure-swa/vite/index.cjs +0 -96
  64. package/lib/adapters/bun-server/vite/index.cjs +0 -50
  65. package/lib/adapters/cloud-run/vite/index.cjs +0 -47
  66. package/lib/adapters/cloudflare-pages/vite/index.cjs +0 -115
  67. package/lib/adapters/deno-server/vite/index.cjs +0 -62
  68. package/lib/adapters/netlify-edge/vite/index.cjs +0 -129
  69. package/lib/adapters/node-server/vite/index.cjs +0 -50
  70. package/lib/adapters/shared/vite/index.cjs +0 -378
  71. package/lib/adapters/static/vite/index.cjs +0 -368
  72. package/lib/adapters/static/vite/index.d.ts +0 -10
  73. package/lib/adapters/static/vite/index.mjs +0 -331
  74. package/lib/adapters/vercel-edge/vite/index.cjs +0 -118
  75. package/lib/index.qwik.cjs +0 -2022
  76. package/lib/middleware/node/index.cjs +0 -314
  77. package/lib/middleware/request-handler/index.cjs +0 -1691
  78. package/lib/service-worker.cjs +0 -17
  79. package/lib/service-worker.mjs +0 -15
  80. package/lib/static/deno.mjs +0 -8
  81. package/lib/static/index.cjs +0 -67
  82. package/lib/static/index.mjs +0 -48
  83. package/lib/static/node.cjs +0 -1126
  84. package/lib/static/node.mjs +0 -1088
  85. package/lib/vite/index.cjs +0 -27525
  86. package/middleware/request-handler/generated/not-found-paths.ts +0 -7
  87. package/middleware/request-handler/generated/static-paths.ts +0 -35
@@ -1,109 +1,84 @@
1
- // packages/qwik-router/src/middleware/vercel-edge/index.ts
2
- import { getNotFound } from "@qwik-router-not-found-paths";
3
- import { isStaticPath } from "@qwik-router-static-paths";
4
- import { _deserialize, _serialize, _verifySerializable } from "@qwik.dev/core/internal";
1
+ import { isDev } from "@qwik.dev/core/build";
5
2
  import { setServerPlatform } from "@qwik.dev/core/server";
6
- import { mergeHeadersCookies, requestHandler } from "../request-handler/index.mjs";
3
+ import { getErrorHtml, isStaticPath, mergeHeadersCookies, requestHandler } from "@qwik.dev/router/middleware/request-handler";
7
4
  var COUNTRY_HEADER_NAME = "x-vercel-ip-country";
8
5
  var IP_HEADER_NAME = "x-real-ip";
9
6
  var VERCEL_COOKIE = "__vdpl";
10
7
  var VERCEL_SKEW_PROTECTION_ENABLED = "VERCEL_SKEW_PROTECTION_ENABLED";
11
8
  var VERCEL_DEPLOYMENT_ID = "VERCEL_DEPLOYMENT_ID";
12
9
  var BASE_URL = "BASE_URL";
13
- function createQwikRouter(opts) {
14
- if (opts.qwikCityPlan && !opts.qwikRouterConfig) {
15
- console.warn("qwikCityPlan is deprecated. Use qwikRouterConfig instead.");
16
- opts.qwikRouterConfig = opts.qwikCityPlan;
17
- } else if (!opts.qwikRouterConfig) {
18
- throw new Error("qwikRouterConfig is required.");
19
- }
20
- const qwikSerializer = {
21
- _deserialize,
22
- _serialize,
23
- _verifySerializable
24
- };
25
- if (opts.manifest) {
26
- setServerPlatform(opts.manifest);
27
- }
28
- async function onVercelEdgeRequest(request) {
29
- try {
30
- const url = new URL(request.url);
31
- if (isStaticPath(request.method, url)) {
32
- return new Response(null, {
33
- headers: {
34
- "x-middleware-next": "1"
35
- }
36
- });
37
- }
38
- const p = (() => globalThis.process)();
39
- const serverRequestEv = {
40
- mode: "server",
41
- locale: void 0,
42
- url,
43
- request,
44
- env: {
45
- get(key) {
46
- return p.env[key];
47
- }
48
- },
49
- getWritableStream: (status, headers, cookies, resolve) => {
50
- const { readable, writable } = new TransformStream();
51
- if (serverRequestEv.env.get(VERCEL_SKEW_PROTECTION_ENABLED)) {
52
- const deploymentId = serverRequestEv.env.get(VERCEL_DEPLOYMENT_ID) || "";
53
- const baseUrl = serverRequestEv.env.get(BASE_URL) || "/";
54
- if (request.headers.has("Sec-Fetch-Dest")) {
55
- cookies.set(VERCEL_COOKIE, deploymentId, {
56
- path: baseUrl,
57
- secure: true,
58
- sameSite: true,
59
- httpOnly: true
60
- });
61
- }
62
- }
63
- const response = new Response(readable, {
64
- status,
65
- headers: mergeHeadersCookies(headers, cookies)
66
- });
67
- resolve(response);
68
- return writable;
69
- },
70
- platform: {},
71
- getClientConn: () => {
72
- return {
73
- ip: request.headers.get(IP_HEADER_NAME) ?? void 0,
74
- country: request.headers.get(COUNTRY_HEADER_NAME) ?? void 0
75
- };
76
- }
77
- };
78
- const handledResponse = await requestHandler(serverRequestEv, opts, qwikSerializer);
79
- if (handledResponse) {
80
- handledResponse.completion.then((v) => {
81
- if (v) {
82
- console.error(v);
83
- }
84
- });
85
- const response = await handledResponse.response;
86
- if (response) {
87
- return response;
88
- }
89
- }
90
- const notFoundHtml = isStaticPath(request.method || "GET", url) ? "Not Found" : getNotFound(url.pathname);
91
- return new Response(notFoundHtml, {
92
- status: 404,
93
- headers: { "Content-Type": "text/html; charset=utf-8", "X-Not-Found": url.pathname }
94
- });
95
- } catch (e) {
96
- console.error(e);
97
- return new Response(String(e || "Error"), {
98
- status: 500,
99
- headers: { "Content-Type": "text/plain; charset=utf-8", "X-Error": "vercel-edge" }
100
- });
101
- }
102
- }
103
- return onVercelEdgeRequest;
10
+ /** @public */ function createQwikRouter(opts) {
11
+ if (opts.manifest) setServerPlatform(opts.manifest);
12
+ async function onVercelEdgeRequest(request) {
13
+ try {
14
+ const url = new URL(request.url);
15
+ if (isStaticPath(request.method, url)) return new Response(null, { headers: { "x-middleware-next": "1" } });
16
+ const p = (() => globalThis.process)();
17
+ const serverRequestEv = {
18
+ mode: "server",
19
+ locale: void 0,
20
+ url,
21
+ request,
22
+ env: { get(key) {
23
+ return p.env[key];
24
+ } },
25
+ getWritableStream: (status, headers, cookies, resolve) => {
26
+ const { readable, writable } = new TransformStream();
27
+ if (serverRequestEv.env.get(VERCEL_SKEW_PROTECTION_ENABLED)) {
28
+ const deploymentId = serverRequestEv.env.get(VERCEL_DEPLOYMENT_ID) || "";
29
+ const baseUrl = serverRequestEv.env.get(BASE_URL) || "/";
30
+ if (request.headers.has("Sec-Fetch-Dest")) cookies.set(VERCEL_COOKIE, deploymentId, {
31
+ path: baseUrl,
32
+ secure: true,
33
+ sameSite: true,
34
+ httpOnly: true
35
+ });
36
+ }
37
+ resolve(new Response(readable, {
38
+ status,
39
+ headers: mergeHeadersCookies(headers, cookies)
40
+ }));
41
+ return writable;
42
+ },
43
+ platform: {},
44
+ getClientConn: () => {
45
+ return {
46
+ ip: request.headers.get(IP_HEADER_NAME) ?? void 0,
47
+ country: request.headers.get(COUNTRY_HEADER_NAME) ?? void 0
48
+ };
49
+ }
50
+ };
51
+ const handledResponse = await requestHandler(serverRequestEv, opts);
52
+ if (handledResponse) {
53
+ handledResponse.completion.then((completion) => {
54
+ if (completion) console.error(completion);
55
+ });
56
+ const response = await handledResponse.response;
57
+ if (response) return response;
58
+ }
59
+ const notFoundHtml = !request.headers.get("accept")?.includes("text/html") || isStaticPath(request.method || "GET", url) ? "Not Found" : getErrorHtml(404, "Not Found");
60
+ return new Response(notFoundHtml, {
61
+ status: 404,
62
+ headers: {
63
+ "Content-Type": "text/html; charset=utf-8",
64
+ "X-Not-Found": url.pathname
65
+ }
66
+ });
67
+ } catch (e) {
68
+ console.error(e);
69
+ return new Response(isDev ? String(e || "Error") : "Internal Server Error", {
70
+ status: 500,
71
+ headers: {
72
+ "Content-Type": "text/plain; charset=utf-8",
73
+ "X-Error": "vercel-edge"
74
+ }
75
+ });
76
+ }
77
+ }
78
+ return onVercelEdgeRequest;
104
79
  }
105
- var createQwikCity = createQwikRouter;
106
- export {
107
- createQwikCity,
108
- createQwikRouter
109
- };
80
+ /**
81
+ * @deprecated Use `createQwikRouter` instead. Will be removed in V3
82
+ * @public
83
+ */ var createQwikCity = createQwikRouter;
84
+ export { createQwikCity, createQwikRouter };
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
  }
@@ -19,20 +22,12 @@ declare module '@qwik-city-plan' {
19
22
  export { default } from '@qwik-router-config';
20
23
  }
21
24
 
22
- declare module '@qwik-router-not-found-paths' {
23
- function getNotFound(_pathname: string): string;
24
- export { getNotFound };
25
- }
26
-
27
25
  declare module '@qwik-city-not-found-paths' {
28
- export * from '@qwik-router-not-found-paths';
29
- }
30
-
31
- declare module '@qwik-router-static-paths' {
32
- function isStaticPath(method: string, url: URL): boolean;
33
- export { isStaticPath };
26
+ /** @deprecated Use `getNotFound` from `@qwik.dev/router/middleware/request-handler` instead. */
27
+ export { getNotFound } from '@qwik.dev/router/middleware/request-handler';
34
28
  }
35
29
 
36
30
  declare module '@qwik-city-static-paths' {
37
- export * from '@qwik-router-static-paths';
31
+ /** @deprecated Use `isStaticPath` from `@qwik.dev/router/middleware/request-handler` instead. */
32
+ export { isStaticPath } from '@qwik.dev/router/middleware/request-handler';
38
33
  }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @deprecated This is no longer needed, Qwik now automatically embeds preloading logic into the
3
+ * application.
4
+ *
5
+ * If your service-worker.ts file contains no custom code, you should deploy to production until
6
+ * you're sure that all users picked up the new version, then you can remove it and also remove
7
+ * the `<ServiceWorkerRegister />` component from your `Root.tsx`.
8
+ *
9
+ * If you do have custom service worker logic, you should keep the `service-worker.ts` file and
10
+ * `<ServiceWorkerRegister />` component, but remove the `setupServiceWorker()` call.
11
+ * @public
12
+ */ var setupServiceWorker = () => {};
13
+ export { setupServiceWorker };
@@ -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,26 +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
+ /** SSR render function (default export from `entry.ssr`); worker-only — the main entry omits it. */
30
+ render: Render;
31
+ /** The Qwik Router Config object (default export from `@qwik-router-config`). */
32
+ qwikRouterConfig: QwikRouterConfig;
33
+ /** Path or URL to the worker entry file (`run-ssg-worker.js`); workers are spawned from it. */
34
+ workerFilePath?: string | URL;
35
+ }
10
36
 
11
37
  /** @public */
12
- export declare interface StaticGenerateOptions extends StaticGenerateRenderOptions {
13
- /**
14
- * Path to the SSR module exporting the default render function. In most cases it'll be
15
- * `./src/entry.ssr.tsx`.
16
- */
17
- renderModulePath: string;
18
- /** Path to the Qwik Router Config module exporting the default `@qwik-router-config`. */
19
- qwikRouterConfigModulePath: string;
20
- /** @deprecated Use `qwikRouterConfigModulePath` instead. Will be removed in V3 */
21
- qwikCityPlanModulePath?: string;
38
+ declare interface SsgOptions extends SsgRenderOptions {
22
39
  /** Defaults to `/` */
23
40
  basePathname?: string;
24
41
  rootDir?: string;
25
42
  }
43
+ export { SsgOptions }
44
+ export { SsgOptions as StaticGenerateOptions }
26
45
 
27
46
  /** @public */
28
- export declare interface StaticGenerateRenderOptions extends RenderOptions {
47
+ export declare interface SsgRenderOptions extends RenderOptions {
29
48
  /** File system directory where the static files should be written. */
30
49
  outDir: string;
31
50
  /**
@@ -53,24 +72,19 @@ export declare interface StaticGenerateRenderOptions extends RenderOptions {
53
72
  * root of the `outDir`. Setting to `null` will prevent the sitemap from being created.
54
73
  */
55
74
  sitemapOutFile?: string | null;
56
- /** Log level. */
57
- log?: 'debug';
75
+ /** Log level. `'quiet'` suppresses per-page output, `'debug'` enables verbose logging. */
76
+ log?: 'debug' | 'quiet';
58
77
  /**
59
78
  * Set to `false` if the generated static HTML files should not be written to disk. Setting to
60
- * `false` is useful if the SSG should only write the `q-data.json` files to disk. Defaults to
79
+ * `false` is useful if the SSG should only write the per-loader data files to disk. Defaults to
61
80
  * `true`.
62
81
  */
63
82
  emitHtml?: boolean;
64
83
  /**
65
- * Set to `false` if the generated `q-data.json` data files should not be written to disk.
66
- * Defaults to `true`.
84
+ * Set to `false` if the generated per-loader data files should not be written to disk. Defaults
85
+ * to `true`.
67
86
  */
68
87
  emitData?: boolean;
69
- /**
70
- * Set to `false` if the static build should not write custom or default `404.html` pages.
71
- * Defaults to `true`.
72
- */
73
- emit404Pages?: boolean;
74
88
  /**
75
89
  * Defines file system routes relative to the source `routes` directory that should be static
76
90
  * generated. Accepts wildcard behavior. This should not include the "base" pathname. If not
@@ -85,6 +99,16 @@ export declare interface StaticGenerateRenderOptions extends RenderOptions {
85
99
  exclude?: string[];
86
100
  }
87
101
 
102
+ /**
103
+ * Bootstrap a worker thread for SSG rendering. Called from the generated `run-ssg.js` when it
104
+ * detects it's running as a worker thread.
105
+ *
106
+ * @param opts - Must include `render` and `qwikRouterConfig` (imported by the worker entry). The
107
+ * remaining serializable options come from `workerData`.
108
+ * @public
109
+ */
110
+ export declare function startWorker(opts: Pick<SsgInternalOptions, 'render' | 'qwikRouterConfig'>): Promise<void>;
111
+
88
112
  /** @public */
89
113
  export declare interface StaticGenerateResult {
90
114
  duration: number;
@@ -0,0 +1,257 @@
1
+ import { n as getPathnameForDynamicRoute, t as ensureSlash } from "../chunks/pathname.mjs";
2
+ import { r as msToString } from "../chunks/format.mjs";
3
+ import { t as createRouteTester } from "../chunks/routes.mjs";
4
+ import { relative } from "node:path";
5
+ import { bold, dim, green, magenta, red } from "kleur/colors";
6
+ function validateOptions(opts) {
7
+ if (!opts.qwikRouterConfig) throw new Error(`Missing "qwikRouterConfig" option`);
8
+ let siteOrigin = opts.origin;
9
+ if (typeof siteOrigin !== "string" || siteOrigin.trim().length === 0) throw new Error(`Missing "origin" option`);
10
+ siteOrigin = siteOrigin.trim();
11
+ if (!/:\/\//.test(siteOrigin) || siteOrigin.startsWith("://")) throw new Error(`"origin" must start with a valid protocol, such as "https://" or "http://", received "${siteOrigin}"`);
12
+ try {
13
+ new URL(siteOrigin);
14
+ } catch (e) {
15
+ throw new Error(`Invalid "origin"`, { cause: e });
16
+ }
17
+ }
18
+ async function mainThread(sys) {
19
+ const opts = sys.getOptions();
20
+ validateOptions(opts);
21
+ const main = await sys.createMainProcess();
22
+ const log = await sys.createLogger();
23
+ log.info("\n" + bold(green("Starting Qwik Router SSG...")));
24
+ const qwikRouterConfig = opts.qwikRouterConfig;
25
+ const renderTimeout = 3e4;
26
+ const queue = [];
27
+ const active = /* @__PURE__ */ new Set();
28
+ const routes = qwikRouterConfig.routes;
29
+ const trailingSlash = !!qwikRouterConfig.trailingSlash;
30
+ const includeRoute = createRouteTester(opts.basePathname || "/", opts.include, opts.exclude);
31
+ return new Promise((resolve, reject) => {
32
+ try {
33
+ const timer = sys.createTimer();
34
+ const generatorResult = {
35
+ duration: 0,
36
+ rendered: 0,
37
+ errors: 0,
38
+ staticPaths: []
39
+ };
40
+ let isCompleted = false;
41
+ let isRoutesLoaded = false;
42
+ const completed = async () => {
43
+ const closePromise = main.close();
44
+ generatorResult.duration = timer();
45
+ if (generatorResult.errors === 0) {
46
+ log.info(`\n${green("SSG results")}`);
47
+ if (generatorResult.rendered > 0) log.info(`- Generated: ${dim(`${generatorResult.rendered} page${generatorResult.rendered === 1 ? "" : "s"}`)}`);
48
+ log.info(`- Duration: ${dim(msToString(generatorResult.duration))}`);
49
+ const total = generatorResult.rendered + generatorResult.errors;
50
+ if (total > 0) log.info(`- Average: ${dim(msToString(generatorResult.duration / total) + " per page")}`);
51
+ log.info(``);
52
+ }
53
+ closePromise.then(() => {
54
+ setTimeout(() => resolve(generatorResult));
55
+ }).catch(reject);
56
+ };
57
+ const next = () => {
58
+ while (!isCompleted && main.hasAvailableWorker() && queue.length > 0) {
59
+ const staticRoute = queue.shift();
60
+ if (staticRoute) render(staticRoute).catch((e) => {
61
+ console.error(`render failed for ${staticRoute.pathname}`, e);
62
+ });
63
+ }
64
+ if (!isCompleted && isRoutesLoaded && queue.length === 0 && active.size === 0) {
65
+ isCompleted = true;
66
+ completed().catch((e) => {
67
+ console.error("SSG completion failed", e);
68
+ });
69
+ }
70
+ };
71
+ let isPendingDrain = false;
72
+ const flushQueue = () => {
73
+ if (!isPendingDrain) {
74
+ isPendingDrain = true;
75
+ setTimeout(() => {
76
+ isPendingDrain = false;
77
+ next();
78
+ });
79
+ }
80
+ };
81
+ const render = async (staticRoute) => {
82
+ try {
83
+ active.add(staticRoute.pathname);
84
+ log.debug(`render start: ${staticRoute.pathname}`);
85
+ const result = await Promise.race([main.render({
86
+ type: "render",
87
+ ...staticRoute
88
+ }), new Promise((_, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error(`SSG render timed out after ${renderTimeout}ms`)), renderTimeout))]);
89
+ active.delete(staticRoute.pathname);
90
+ log.debug(`render done: ${staticRoute.pathname}`);
91
+ if (result.error) {
92
+ const err = new Error(result.error.message);
93
+ err.stack = result.error.stack;
94
+ log.error(`\n${bold(red(`!!! ${result.pathname}: Error during SSG`))}`);
95
+ log.error(red(err.message));
96
+ log.error(` Pathname: ${magenta(staticRoute.pathname)}`);
97
+ if (err.stack) log.error(dim(err.stack));
98
+ generatorResult.errors++;
99
+ }
100
+ if (result.filePath != null) {
101
+ generatorResult.rendered++;
102
+ generatorResult.staticPaths.push(result.pathname);
103
+ const base = opts.rootDir ?? opts.outDir;
104
+ const path = relative(base, result.filePath);
105
+ const lastSlash = path.lastIndexOf("/");
106
+ log.info(`${dim(path.slice(0, lastSlash + 1))}${path.slice(lastSlash + 1)}`);
107
+ }
108
+ flushQueue();
109
+ } catch (e) {
110
+ console.error(`render failed for ${staticRoute.pathname}`, e);
111
+ isCompleted = true;
112
+ reject(e);
113
+ }
114
+ };
115
+ const addToQueue = (pathname, params) => {
116
+ if (pathname) {
117
+ pathname = new URL(pathname, `https://qwik.dev`).pathname;
118
+ if (pathname !== opts.basePathname) {
119
+ if (trailingSlash) {
120
+ if (!pathname.endsWith("/")) {
121
+ const segments = pathname.split("/");
122
+ if (!segments[segments.length - 1].includes(".")) pathname = ensureSlash(pathname);
123
+ }
124
+ } else if (pathname.endsWith("/")) pathname = pathname.slice(0, pathname.length - 1);
125
+ }
126
+ if (includeRoute(pathname) && !queue.some((s) => s.pathname === pathname)) {
127
+ queue.push({
128
+ pathname,
129
+ params
130
+ });
131
+ flushQueue();
132
+ }
133
+ }
134
+ };
135
+ /**
136
+ * Traverse the route tree and call the callback for each route that has module loaders.
137
+ * Reconstructs the original pathname and param names from the tree path. Accumulates _L
138
+ * layout loaders from ancestors during traversal.
139
+ */ const traverseRouteTree = async (node, pathParts, basePathname, ancestorLoaders) => {
140
+ const currentLoaders = node._L ? [...ancestorLoaders, node._L] : ancestorLoaders;
141
+ const index = node._I;
142
+ if (index) {
143
+ const pageLoaders = Array.isArray(index) ? index : [...currentLoaders, index];
144
+ const joinedParts = pathParts.join("/");
145
+ const originalPathname = basePathname + (joinedParts ? ensureSlash(joinedParts) : "");
146
+ const paramNames = pathParts.filter((p) => p.startsWith("[") && p.endsWith("]")).map((p) => p.startsWith("[...") ? p.slice(4, -1) : p.slice(1, -1));
147
+ if (paramNames.length === 0) addToQueue(originalPathname, void 0);
148
+ else {
149
+ const pageLoader = pageLoaders[pageLoaders.length - 1];
150
+ if (typeof pageLoader === "function") {
151
+ const pageModule = await pageLoader();
152
+ if (typeof pageModule.onStaticGenerate === "function") {
153
+ const staticGenerate = await pageModule.onStaticGenerate({ env: { get(key) {
154
+ return sys.getEnv(key);
155
+ } } });
156
+ if (Array.isArray(staticGenerate.params)) for (const params of staticGenerate.params) {
157
+ const pathname = getPathnameForDynamicRoute(originalPathname, paramNames, params);
158
+ addToQueue(pathname, params);
159
+ }
160
+ }
161
+ }
162
+ }
163
+ }
164
+ if (node._4 || node._E) {
165
+ if (!pathParts.some((p) => p.startsWith("["))) {
166
+ const joinedParts = pathParts.join("/");
167
+ const dirPathname = basePathname + (joinedParts ? ensureSlash(joinedParts) : "");
168
+ addToQueue(dirPathname + "404.html", void 0);
169
+ }
170
+ }
171
+ if (node._M) for (const group of node._M) await traverseRouteTree(group, pathParts, basePathname, currentLoaders);
172
+ if (node._W && typeof node._W === "object") {
173
+ const childNode = node._W;
174
+ const paramName = childNode._P;
175
+ const pathPart = paramName ? `[${paramName}]` : "[param]";
176
+ await traverseRouteTree(childNode, [...pathParts, pathPart], basePathname, currentLoaders);
177
+ }
178
+ if (node._A && typeof node._A === "object") {
179
+ const childNode = node._A;
180
+ const paramName = childNode._P;
181
+ const pathPart = paramName ? `[...${paramName}]` : "[...rest]";
182
+ await traverseRouteTree(childNode, [...pathParts, pathPart], basePathname, currentLoaders);
183
+ }
184
+ for (const [key, child] of Object.entries(node)) {
185
+ if (key.charCodeAt(0) === 95) continue;
186
+ await traverseRouteTree(child, [...pathParts, key], basePathname, currentLoaders);
187
+ }
188
+ };
189
+ const loadStaticRoutes = async () => {
190
+ log.debug("traversing route tree...");
191
+ await traverseRouteTree(routes, [], "/", []);
192
+ log.debug(`route tree traversed, ${queue.length} routes queued, ${active.size} active`);
193
+ isRoutesLoaded = true;
194
+ flushQueue();
195
+ };
196
+ loadStaticRoutes().catch((e) => {
197
+ console.error("SSG route loading failed", e);
198
+ reject(e);
199
+ });
200
+ } catch (e) {
201
+ console.error("SSG main thread failed", e);
202
+ reject(e);
203
+ }
204
+ });
205
+ }
206
+ /**
207
+ * Use this function when SSG should be generated from another module, such as a Vite plugin. This
208
+ * function's should be passed the paths of the entry module and Qwik Router Plan.
209
+ *
210
+ * @public
211
+ */ async function generate(opts) {
212
+ const { createSystem } = await import("../chunks/system.mjs");
213
+ return mainThread(await createSystem(opts));
214
+ }
215
+ /**
216
+ * Run SSG as a standalone process. Writes `_static-paths.json` to `outDir` and exits with code 0 on
217
+ * success, 1 on error.
218
+ *
219
+ * Intended to be called from a generated `run-ssg.js` wrapper that provides render/config.
220
+ *
221
+ * @public
222
+ */ async function runSsg(opts) {
223
+ try {
224
+ const result = await generate(opts);
225
+ const fs = await import("node:fs");
226
+ const { join } = await import("node:path");
227
+ const staticPathsFile = join(opts.outDir, "_static-paths.json");
228
+ await fs.promises.writeFile(staticPathsFile, JSON.stringify(result.staticPaths));
229
+ if (result.errors > 0) {
230
+ console.error(`SSG completed with ${result.errors} error(s)`);
231
+ process.exit(1);
232
+ }
233
+ process.exit(0);
234
+ } catch (e) {
235
+ console.error("SSG failed:", e);
236
+ process.exit(1);
237
+ }
238
+ }
239
+ /**
240
+ * Bootstrap a worker thread for SSG rendering. Called from the generated `run-ssg.js` when it
241
+ * detects it's running as a worker thread.
242
+ *
243
+ * @param opts - Must include `render` and `qwikRouterConfig` (imported by the worker entry). The
244
+ * remaining serializable options come from `workerData`.
245
+ * @public
246
+ */ async function startWorker(opts) {
247
+ const { workerData } = await import("node:worker_threads");
248
+ const mergedOpts = {
249
+ ...workerData,
250
+ render: opts.render,
251
+ qwikRouterConfig: opts.qwikRouterConfig
252
+ };
253
+ const { createSystem } = await import("../chunks/system.mjs");
254
+ const { workerThread } = await import("../chunks/worker-thread.mjs");
255
+ await workerThread(await createSystem(mergedOpts, workerData?.threadId));
256
+ }
257
+ export { generate, runSsg, startWorker };