@qwik.dev/router 2.0.0-beta.31 → 2.0.0-beta.32

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.
@@ -509,7 +509,7 @@ function findChild(node, part, partLower, parts, partIndex, params) {
509
509
  return void 0;
510
510
  }
511
511
  function findIndexNode(node) {
512
- if (node._I || node._G) {
512
+ if (node._I || node._G != null) {
513
513
  return {
514
514
  target: node,
515
515
  groups: []
@@ -529,7 +529,7 @@ function findIndexNode(node) {
529
529
  return void 0;
530
530
  }
531
531
  function resolveLoaders(root, node, gatheredLayouts) {
532
- if (node._G) {
532
+ if (node._G != null) {
533
533
  const keys = node._G.split("/").filter((p) => p.length > 0);
534
534
  const target = walkTrieKeys(root, keys);
535
535
  if (!target) {
@@ -537,7 +537,7 @@ function resolveLoaders(root, node, gatheredLayouts) {
537
537
  }
538
538
  let targetNode = target.node;
539
539
  const targetLayouts = target.layouts;
540
- if (!targetNode._I && !targetNode._G) {
540
+ if (!targetNode._I && targetNode._G == null) {
541
541
  const indexResult = findIndexNode(targetNode);
542
542
  if (indexResult) {
543
543
  for (const g of indexResult.groups) {
@@ -650,14 +650,14 @@ function matchRouteTree(root = {}, pathname) {
650
650
  collectNodeMeta(node, found.groups, layouts, errorLoaderRef, notFoundLoaderRef, menuLoaderRef);
651
651
  }
652
652
  if (matched && !done && i === len) {
653
- if (!node._I && !node._G) {
653
+ if (!node._I && node._G == null) {
654
654
  const indexResult = findIndexNode(node);
655
655
  if (indexResult) {
656
656
  collectNodeMeta(indexResult.target, indexResult.groups, layouts, errorLoaderRef, notFoundLoaderRef, menuLoaderRef);
657
657
  node = indexResult.target;
658
658
  }
659
659
  }
660
- if (!node._I && !node._G && node._A) {
660
+ if (!node._I && node._G == null && node._A) {
661
661
  const next = node._A;
662
662
  const paramName = next._P;
663
663
  params[paramName] = "";
@@ -670,7 +670,7 @@ function matchRouteTree(root = {}, pathname) {
670
670
  menuLoaderRef.v = node._N;
671
671
  }
672
672
  }
673
- if (!node._I && !node._G) {
673
+ if (!node._I && node._G == null) {
674
674
  const restInfo = findRestNode(node);
675
675
  if (restInfo) {
676
676
  const next = restInfo.next;
@@ -280,7 +280,7 @@ function findChild(node, part, partLower, parts, partIndex, params) {
280
280
  return void 0;
281
281
  }
282
282
  function findIndexNode(node) {
283
- if (node._I || node._G) {
283
+ if (node._I || node._G != null) {
284
284
  return {
285
285
  target: node,
286
286
  groups: []
@@ -300,7 +300,7 @@ function findIndexNode(node) {
300
300
  return void 0;
301
301
  }
302
302
  function resolveLoaders(root, node, gatheredLayouts) {
303
- if (node._G) {
303
+ if (node._G != null) {
304
304
  const keys = node._G.split("/").filter((p) => p.length > 0);
305
305
  const target = walkTrieKeys(root, keys);
306
306
  if (!target) {
@@ -308,7 +308,7 @@ function resolveLoaders(root, node, gatheredLayouts) {
308
308
  }
309
309
  let targetNode = target.node;
310
310
  const targetLayouts = target.layouts;
311
- if (!targetNode._I && !targetNode._G) {
311
+ if (!targetNode._I && targetNode._G == null) {
312
312
  const indexResult = findIndexNode(targetNode);
313
313
  if (indexResult) {
314
314
  for (const g of indexResult.groups) {
@@ -421,14 +421,14 @@ function matchRouteTree(root = {}, pathname) {
421
421
  collectNodeMeta(node, found.groups, layouts, errorLoaderRef, notFoundLoaderRef, menuLoaderRef);
422
422
  }
423
423
  if (matched && !done && i === len) {
424
- if (!node._I && !node._G) {
424
+ if (!node._I && node._G == null) {
425
425
  const indexResult = findIndexNode(node);
426
426
  if (indexResult) {
427
427
  collectNodeMeta(indexResult.target, indexResult.groups, layouts, errorLoaderRef, notFoundLoaderRef, menuLoaderRef);
428
428
  node = indexResult.target;
429
429
  }
430
430
  }
431
- if (!node._I && !node._G && node._A) {
431
+ if (!node._I && node._G == null && node._A) {
432
432
  const next = node._A;
433
433
  const paramName = next._P;
434
434
  params[paramName] = "";
@@ -441,7 +441,7 @@ function matchRouteTree(root = {}, pathname) {
441
441
  menuLoaderRef.v = node._N;
442
442
  }
443
443
  }
444
- if (!node._I && !node._G) {
444
+ if (!node._I && node._G == null) {
445
445
  const restInfo = findRestNode(node);
446
446
  if (restInfo) {
447
447
  const next = restInfo.next;
package/lib/index.d.ts CHANGED
@@ -400,8 +400,6 @@ export declare interface FormProps<O, I> extends Omit<QwikJSX.IntrinsicElements[
400
400
  * Defaults to `false`
401
401
  */
402
402
  spaReset?: boolean;
403
- /** Event handler executed right when the form is submitted. */
404
- onSubmit$?: QRLEventHandlerMulti<SubmitEvent, HTMLFormElement> | undefined;
405
403
  /** Event handler executed right after the action is executed successfully and returns some data. */
406
404
  onSubmitCompleted$?: QRLEventHandlerMulti<CustomEvent<FormSubmitSuccessDetail<O>>, HTMLFormElement> | undefined;
407
405
  key?: string | number | null;
@@ -176,9 +176,27 @@ const saveScrollHistory = (scrollState) => {
176
176
  history.replaceState(state, "");
177
177
  };
178
178
 
179
+ const Q_ROUTER_POPSTATE_EVENT = "qrouterpopstate";
180
+ const createCurrentPathTracker = (initialPath) => {
181
+ let currentPath = initialPath;
182
+ return (nextPath) => {
183
+ if (currentPath !== nextPath) {
184
+ currentPath = nextPath;
185
+ return true;
186
+ }
187
+ return false;
188
+ };
189
+ };
190
+ const dispatchRouterPopstate = (document1, href) => {
191
+ document1.dispatchEvent(new CustomEvent(Q_ROUTER_POPSTATE_EVENT, {
192
+ detail: {
193
+ href
194
+ }
195
+ }));
196
+ };
179
197
  const spaInit = eventQrl(/* @__PURE__ */ inlinedQrl((_, el) => {
180
198
  if (!window._qRouterSPA && !window._qRouterInitPopstate) {
181
- const currentPath = location.pathname + location.search;
199
+ const hasPathChanged = createCurrentPathTracker(location.pathname + location.search);
182
200
  const checkAndScroll = (scrollState) => {
183
201
  if (scrollState) {
184
202
  window.scrollTo(scrollState.x, scrollState.y);
@@ -205,21 +223,8 @@ const spaInit = eventQrl(/* @__PURE__ */ inlinedQrl((_, el) => {
205
223
  }
206
224
  window._qRouterScrollEnabled = false;
207
225
  clearTimeout(window._qRouterScrollDebounce);
208
- if (currentPath !== location.pathname + location.search) {
209
- const getContainer = (el2) => el2.closest("[q\\:container]:not([q\\:container=html]):not([q\\:container=text])");
210
- const container = getContainer(el);
211
- const domContainer = container.qContainer;
212
- const hostElement = domContainer.vNodeLocate(el);
213
- const nav = domContainer?.resolveContext(hostElement, {
214
- id: "qr-n"
215
- });
216
- if (nav) {
217
- nav(location.href, {
218
- type: "popstate"
219
- });
220
- } else {
221
- location.reload();
222
- }
226
+ if (hasPathChanged(location.pathname + location.search)) {
227
+ dispatchRouterPopstate(document, location.href);
223
228
  } else {
224
229
  if (history.scrollRestoration === "manual") {
225
230
  const scrollState = history.state?._qRouterScroll;
@@ -957,12 +962,21 @@ const QwikRouterMockProvider = /* @__PURE__ */ componentQrl(/* @__PURE__ */ inli
957
962
  }, "QwikRouterMockProvider_component_IN4dVpT0x74"));
958
963
  const QwikCityMockProvider = QwikRouterMockProvider;
959
964
 
965
+ const handleRouterPopstate = (nav, event) => {
966
+ const href = event.detail?.href;
967
+ if (href) {
968
+ return nav(href, {
969
+ type: "popstate"
970
+ });
971
+ }
972
+ };
960
973
  const RouterOutlet = /* @__PURE__ */ componentQrl(/* @__PURE__ */ inlinedQrl(() => {
961
974
  const serverData = useServerData("containerAttributes");
962
975
  if (!serverData) {
963
976
  throw new Error("PrefetchServiceWorker component must be rendered on the server.");
964
977
  }
965
978
  const internalContext = useContext(ContentInternalContext);
979
+ const nav = useNavigate();
966
980
  const contents = internalContext.value;
967
981
  if (contents && contents.length > 0) {
968
982
  const contentsLen = contents.length;
@@ -977,19 +991,22 @@ const RouterOutlet = /* @__PURE__ */ componentQrl(/* @__PURE__ */ inlinedQrl(()
977
991
  !__EXPERIMENTAL__.noSPA && /* @__PURE__ */ _jsxSorted("script", {
978
992
  "q-d:qinit": _qrlSync(() => {
979
993
  ((w, h) => {
980
- if (!w._qcs && h.scrollRestoration === "manual") {
994
+ if (!w._qcs) {
981
995
  w._qcs = true;
982
996
  const s = h.state?._qRouterScroll;
983
997
  if (s) {
998
+ h.scrollRestoration = "manual";
984
999
  w.scrollTo(s.x, s.y);
985
1000
  }
986
1001
  document.dispatchEvent(new Event("qcinit"));
987
1002
  }
988
1003
  })(window, history);
989
- }, '()=>{((w,h)=>{if(!w._qcs&&h.scrollRestoration==="manual"){w._qcs=!0;const s=h.state?._qRouterScroll;if(s){w.scrollTo(s.x,s.y);}document.dispatchEvent(new Event("qcinit"));}})(window,history);}')
1004
+ }, '()=>{((w,h)=>{if(!w._qcs){w._qcs=!0;const s=h.state?._qRouterScroll;if(s){h.scrollRestoration="manual";w.scrollTo(s.x,s.y);}document.dispatchEvent(new Event("qcinit"));}})(window,history);}'),
1005
+ "q-d:qrouterpopstate": /* @__PURE__ */ inlinedQrl((event, _1, nav2) => handleRouterPopstate(nav2, event), "RouterOutlet_component_Fragment_script_q_d_qrouterpopstate_aRL6GvFyews"),
1006
+ "q:p": nav
990
1007
  }, {
991
1008
  "q-d:qcinit": spaInit
992
- }, null, 2, "Fn_1")
1009
+ }, null, 6, "Fn_1")
993
1010
  ], 1, "Fn_2");
994
1011
  }
995
1012
  return SkipRender;
@@ -1,10 +1,10 @@
1
- import { Http2ServerRequest } from 'http2';
2
- import { IncomingMessage } from 'http';
3
- import { NodeRequestNextFunction } from '@qwik.dev/router/middleware/node';
1
+ import type { Http2ServerRequest } from 'node:http2';
2
+ import type { IncomingMessage } from 'node:http';
3
+ import type { NodeRequestNextFunction } from '@qwik.dev/router/middleware/node';
4
4
  import type { QwikManifest } from '@qwik.dev/core/optimizer';
5
5
  import type { Render } from '@qwik.dev/core/server';
6
6
  import type { ServerRenderOptions } from '@qwik.dev/router/middleware/request-handler';
7
- import { ServerResponse } from 'http';
7
+ import type { ServerResponse } from 'node:http';
8
8
 
9
9
  declare interface AwsOpt {
10
10
  render: Render;
@@ -18,13 +18,7 @@ declare interface AwsOpt {
18
18
  export declare const createQwikCity: typeof createQwikRouter;
19
19
 
20
20
  /** @public */
21
- export declare function createQwikRouter(opts: AwsOpt): {
22
- fixPath: (pathT: string) => string;
23
- router: (req: IncomingMessage | Http2ServerRequest, res: ServerResponse, next: NodeRequestNextFunction) => Promise<void>;
24
- staticFile: (req: IncomingMessage | Http2ServerRequest, res: ServerResponse, next: (e?: any) => void) => Promise<void>;
25
- notFound: (req: IncomingMessage | Http2ServerRequest, res: ServerResponse, next: (e: any) => void) => Promise<void>;
26
- handle: (req: any, res: any) => void;
27
- };
21
+ export declare function createQwikRouter(opts: AwsOpt): QwikRouterAwsLambdaMiddleware;
28
22
 
29
23
  /** @public */
30
24
  export declare interface PlatformAwsLambda extends Object {
@@ -36,6 +30,16 @@ export declare interface PlatformAwsLambda extends Object {
36
30
  */
37
31
  export declare type QwikCityAwsLambdaOptions = QwikRouterAwsLambdaOptions;
38
32
 
33
+ /** @public */
34
+ export declare interface QwikRouterAwsLambdaMiddleware {
35
+ fixPath: (pathT: string) => string;
36
+ router: (req: IncomingMessage | Http2ServerRequest, res: ServerResponse, next: NodeRequestNextFunction) => Promise<void>;
37
+ staticFile: (req: IncomingMessage | Http2ServerRequest, res: ServerResponse, next: (e?: any) => void) => Promise<void>;
38
+ /** @deprecated `router` handles 404 responses. Will be removed in V3. */
39
+ notFound: (req: IncomingMessage | Http2ServerRequest, res: ServerResponse, next: (e?: any) => void) => Promise<void>;
40
+ handle: (req: any, res: any) => void;
41
+ }
42
+
39
43
  /** @public */
40
44
  export declare interface QwikRouterAwsLambdaOptions extends ServerRenderOptions {
41
45
  }
@@ -2,7 +2,7 @@ import { createQwikRouter as createQwikRouter$1 } from '@qwik.dev/router/middlew
2
2
 
3
3
  function createQwikRouter(opts) {
4
4
  try {
5
- const { router, staticFile, notFound } = createQwikRouter$1({
5
+ const { router, staticFile } = createQwikRouter$1({
6
6
  render: opts.render,
7
7
  manifest: opts.manifest,
8
8
  static: {
@@ -31,11 +31,12 @@ function createQwikRouter(opts) {
31
31
  req.url = fixPath(req.url);
32
32
  staticFile(req, res, () => {
33
33
  router(req, res, () => {
34
- notFound(req, res, () => {
35
- });
36
34
  });
37
35
  });
38
36
  };
37
+ const notFound = async (_req, _res, next) => {
38
+ next();
39
+ };
39
40
  return {
40
41
  fixPath,
41
42
  router,
@@ -8,11 +8,7 @@ import type { ServerRenderOptions } from '@qwik.dev/router/middleware/request-ha
8
8
  export declare const createQwikCity: typeof createQwikRouter;
9
9
 
10
10
  /** @public */
11
- export declare function createQwikRouter(opts: QwikRouterBunOptions): {
12
- router: (request: Request) => Promise<Response | null>;
13
- notFound: (request: Request) => Promise<Response>;
14
- staticFile: (request: Request) => Promise<Response | null>;
15
- };
11
+ export declare function createQwikRouter(opts: QwikRouterBunOptions): QwikRouterBunMiddleware;
16
12
 
17
13
  /**
18
14
  * @deprecated Use `QwikRouterBunOptions` instead. Will be removed in V3
@@ -20,6 +16,14 @@ export declare function createQwikRouter(opts: QwikRouterBunOptions): {
20
16
  */
21
17
  export declare type QwikCityBunOptions = QwikRouterBunOptions;
22
18
 
19
+ /** @public */
20
+ export declare interface QwikRouterBunMiddleware {
21
+ router: (request: Request) => Promise<Response | null>;
22
+ /** @deprecated `router` handles 404 responses. Will be removed in V3. */
23
+ notFound: (request: Request) => Promise<Response>;
24
+ staticFile: (request: Request) => Promise<Response | null>;
25
+ }
26
+
23
27
  /** @public */
24
28
  export declare interface QwikRouterBunOptions extends ServerRenderOptions {
25
29
  /** Options for serving static files */
@@ -1,6 +1,6 @@
1
1
  import { isDev } from '@qwik.dev/core/build';
2
2
  import { setServerPlatform } from '@qwik.dev/core/server';
3
- import { _TextEncoderStream_polyfill, isStaticPath, getNotFound, mergeHeadersCookies, requestHandler } from '@qwik.dev/router/middleware/request-handler';
3
+ import { _TextEncoderStream_polyfill, isStaticPath, mergeHeadersCookies, requestHandler } from '@qwik.dev/router/middleware/request-handler';
4
4
  import { join, extname } from 'node:path';
5
5
  import { M as MIME_TYPES, n as normalizeRequestUrl } from '../../chunks/url.mjs';
6
6
 
@@ -77,27 +77,8 @@ function createQwikRouter(opts) {
77
77
  });
78
78
  }
79
79
  }
80
- const notFound = async (request) => {
81
- try {
82
- const url = getRequestUrl(request, opts);
83
- const notFoundHtml = !request.headers.get("accept")?.includes("text/html") || isStaticPath(request.method || "GET", url) ? "Not Found" : getNotFound(url.pathname);
84
- return new Response(notFoundHtml, {
85
- status: 404,
86
- headers: {
87
- "Content-Type": "text/html; charset=utf-8",
88
- "X-Not-Found": url.pathname
89
- }
90
- });
91
- } catch (e) {
92
- console.error(e);
93
- return new Response(isDev ? String(e || "Error") : "Internal Server Error", {
94
- status: 500,
95
- headers: {
96
- "Content-Type": "text/plain; charset=utf-8",
97
- "X-Error": "bun-server"
98
- }
99
- });
100
- }
80
+ const notFound = async (_request) => {
81
+ return null;
101
82
  };
102
83
  const openStaticFile = async (url) => {
103
84
  const pathname = url.pathname;
@@ -8,11 +8,7 @@ import type { ServerRenderOptions } from '@qwik.dev/router/middleware/request-ha
8
8
  export declare const createQwikCity: typeof createQwikRouter;
9
9
 
10
10
  /** @public */
11
- export declare function createQwikRouter(opts: QwikRouterDenoOptions): {
12
- router: (request: Request, info: ServeHandlerInfo) => Promise<Response | null>;
13
- notFound: (request: Request) => Promise<Response>;
14
- staticFile: (request: Request) => Promise<Response | null>;
15
- };
11
+ export declare function createQwikRouter(opts: QwikRouterDenoOptions): QwikRouterDenoMiddleware;
16
12
 
17
13
  /** @public */
18
14
  export declare interface NetAddr {
@@ -27,6 +23,14 @@ export declare interface NetAddr {
27
23
  */
28
24
  export declare type QwikCityDenoOptions = QwikRouterDenoOptions;
29
25
 
26
+ /** @public */
27
+ export declare interface QwikRouterDenoMiddleware {
28
+ router: (request: Request, info: ServeHandlerInfo) => Promise<Response | null>;
29
+ /** @deprecated `router` handles 404 responses. Will be removed in V3. */
30
+ notFound: (request: Request) => Promise<Response>;
31
+ staticFile: (request: Request) => Promise<Response | null>;
32
+ }
33
+
30
34
  /** @public */
31
35
  export declare interface QwikRouterDenoOptions extends ServerRenderOptions {
32
36
  /** Options for serving static files */
@@ -1,5 +1,5 @@
1
1
  import { setServerPlatform } from '@qwik.dev/core/server';
2
- import { isStaticPath, getNotFound, mergeHeadersCookies, requestHandler } from '@qwik.dev/router/middleware/request-handler';
2
+ import { isStaticPath, mergeHeadersCookies, requestHandler } from '@qwik.dev/router/middleware/request-handler';
3
3
  import { M as MIME_TYPES, n as normalizeRequestUrl } from '../../chunks/url.mjs';
4
4
  import { join, fromFileUrl, extname } from 'https://deno.land/std/path/mod.ts';
5
5
  import { isDev } from '@qwik.dev/core/build';
@@ -69,27 +69,8 @@ function createQwikRouter(opts) {
69
69
  });
70
70
  }
71
71
  }
72
- const notFound = async (request) => {
73
- try {
74
- const url = getRequestUrl(request, opts);
75
- const notFoundHtml = !request.headers.get("accept")?.includes("text/html") || isStaticPath(request.method || "GET", url) ? "Not Found" : getNotFound(url.pathname);
76
- return new Response(notFoundHtml, {
77
- status: 404,
78
- headers: {
79
- "Content-Type": "text/html; charset=utf-8",
80
- "X-Not-Found": url.pathname
81
- }
82
- });
83
- } catch (e) {
84
- console.error(e);
85
- return new Response(isDev ? String(e || "Error") : "Internal Server Error", {
86
- status: 500,
87
- headers: {
88
- "Content-Type": "text/plain; charset=utf-8",
89
- "X-Error": "deno-server"
90
- }
91
- });
92
- }
72
+ const notFound = async (_request) => {
73
+ return null;
93
74
  };
94
75
  const openStaticFile = async (url) => {
95
76
  const pathname = url.pathname;
@@ -1,7 +1,7 @@
1
1
  import { createQwikRouter as createQwikRouter$1 } from '@qwik.dev/router/middleware/node';
2
2
 
3
3
  function createQwikRouter(opts) {
4
- const { staticFile, notFound, router } = createQwikRouter$1({
4
+ const { staticFile, router } = createQwikRouter$1({
5
5
  render: opts.render,
6
6
  manifest: opts.manifest,
7
7
  static: {
@@ -16,8 +16,8 @@ function createQwikRouter(opts) {
16
16
  });
17
17
  const qwikApp = (req, res) => {
18
18
  return staticFile(req, res, () => {
19
- router(req, res, () => notFound(req, res, () => {
20
- }));
19
+ router(req, res, () => {
20
+ });
21
21
  });
22
22
  };
23
23
  return qwikApp;
@@ -11,11 +11,7 @@ import type { ServerResponse } from 'node:http';
11
11
  export declare const createQwikCity: typeof createQwikRouter;
12
12
 
13
13
  /** @public */
14
- export declare function createQwikRouter(opts: QwikRouterNodeRequestOptions | QwikCityNodeRequestOptions): {
15
- router: (req: IncomingMessage | Http2ServerRequest, res: ServerResponse, next: NodeRequestNextFunction) => Promise<void>;
16
- notFound: (req: IncomingMessage | Http2ServerRequest, res: ServerResponse, next: (e: any) => void) => Promise<void>;
17
- staticFile: (req: IncomingMessage | Http2ServerRequest, res: ServerResponse, next: (e?: any) => void) => Promise<void>;
18
- };
14
+ export declare function createQwikRouter(opts: QwikRouterNodeRequestOptions | QwikCityNodeRequestOptions): QwikRouterNodeMiddleware;
19
15
 
20
16
  /** @public */
21
17
  export declare interface NodeRequestNextFunction {
@@ -35,6 +31,14 @@ export declare interface PlatformNode {
35
31
  */
36
32
  export declare type QwikCityNodeRequestOptions = QwikRouterNodeRequestOptions;
37
33
 
34
+ /** @public */
35
+ export declare interface QwikRouterNodeMiddleware {
36
+ router: (req: IncomingMessage | Http2ServerRequest, res: ServerResponse, next: NodeRequestNextFunction) => Promise<void>;
37
+ /** @deprecated `router` handles 404 responses. Will be removed in V3. */
38
+ notFound: (req: IncomingMessage | Http2ServerRequest, res: ServerResponse, next: (e: any) => void) => Promise<void>;
39
+ staticFile: (req: IncomingMessage | Http2ServerRequest, res: ServerResponse, next: (e?: any) => void) => Promise<void>;
40
+ }
41
+
38
42
  /** @public */
39
43
  export declare interface QwikRouterNodeRequestOptions extends ServerRenderOptions {
40
44
  /** Options for serving static files */
@@ -1,6 +1,6 @@
1
1
  import { isDev } from '@qwik.dev/core';
2
2
  import { setServerPlatform } from '@qwik.dev/core/server';
3
- import { isStaticPath, getNotFound, requestHandler } from '@qwik.dev/router/middleware/request-handler';
3
+ import { isStaticPath, requestHandler } from '@qwik.dev/router/middleware/request-handler';
4
4
  import { createReadStream } from 'node:fs';
5
5
  import { join, basename, extname } from 'node:path';
6
6
  import { fileURLToPath } from 'node:url';
@@ -152,23 +152,7 @@ function createQwikRouter(opts) {
152
152
  next(e);
153
153
  }
154
154
  };
155
- const notFound = async (req, res, next) => {
156
- try {
157
- if (!res.headersSent) {
158
- const origin = computeOrigin(req, opts);
159
- const url = getUrl(req, origin);
160
- const notFoundHtml = !req.headers.accept?.includes("text/html") || isStaticPath(req.method || "GET", url) ? "Not Found" : getNotFound(url.pathname);
161
- res.writeHead(404, {
162
- "Content-Type": "text/html; charset=utf-8",
163
- "X-Not-Found": url.pathname
164
- });
165
- res.end(notFoundHtml);
166
- }
167
- } catch (e) {
168
- console.error(e);
169
- next(e);
170
- }
171
- };
155
+ const notFound = async (_req, _res, next) => next();
172
156
  const staticFile = async (req, res, next) => {
173
157
  try {
174
158
  const origin = computeOrigin(req, opts);
@@ -1,4 +1,4 @@
1
- import { mergeConfig, loadEnv } from 'vite';
1
+ import { mergeConfig, normalizePath as normalizePath$1, loadEnv } from 'vite';
2
2
  import fs, { existsSync } from 'node:fs';
3
3
  import path, { join, dirname, basename, resolve, isAbsolute, extname } from 'node:path';
4
4
  import { g as getExtension, r as removeExtension, i as isIndexModule, a as isErrorName, b as isLayoutModule, c as isEntryName, d as isMenuFileName, e as isServiceWorkerName, f as isPageModuleExt, h as isModuleExt, j as isMarkdownExt, k as normalizePath, l as getPathnameFromDirPath, m as getMenuPathname, o as createFileId, p as parseRouteIndexName, q as isPluginModule, s as isPageExt } from '../chunks/fs.mjs';
@@ -1326,7 +1326,7 @@ function serializeBuildTrie(ctx, qwikPlugin, node, layoutIdMap, routeIdMap, menu
1326
1326
  if (node._9) {
1327
1327
  lines.push(`${nextIndent}_9: ${JSON.stringify(node._9)},`);
1328
1328
  }
1329
- if (node._G) {
1329
+ if (node._G != null) {
1330
1330
  lines.push(`${nextIndent}_G: ${JSON.stringify(node._G)},`);
1331
1331
  }
1332
1332
  let layoutExpr;
@@ -1756,6 +1756,67 @@ function parseId(originalId) {
1756
1756
  };
1757
1757
  }
1758
1758
 
1759
+ const supportedExtensions = [
1760
+ ".jpg",
1761
+ ".jpeg",
1762
+ ".png",
1763
+ ".webp",
1764
+ ".gif",
1765
+ ".avif",
1766
+ ".tiff"
1767
+ ];
1768
+ const JSX_QUERY_PARAM = "jsx";
1769
+ const INTERNAL_IMAGE_JSX_QUERY_PARAM = "qwik-asset-jsx";
1770
+ const VIRTUAL_IMAGE_JSX_PREFIX = "virtual:";
1771
+ const VIRTUAL_IMAGE_JSX_SUFFIX = ".qwik.jsx";
1772
+ const TO_IMG_ID = "@to-img.qwik.jsx";
1773
+ const VIRTUAL_TO_IMG_ID = "virtual:to-img.qwik.jsx";
1774
+ function createVirtualImageJsxId(pathId, params) {
1775
+ const query = new URLSearchParams(params);
1776
+ query.delete(INTERNAL_IMAGE_JSX_QUERY_PARAM);
1777
+ const queryString = query.toString();
1778
+ return `${VIRTUAL_IMAGE_JSX_PREFIX}${normalizePath$1(pathId)}${VIRTUAL_IMAGE_JSX_SUFFIX}${queryString ? `?${queryString}` : ""}`;
1779
+ }
1780
+ function createImageJsxImportId(pathId, params) {
1781
+ const query = new URLSearchParams(params);
1782
+ query.set(INTERNAL_IMAGE_JSX_QUERY_PARAM, "");
1783
+ return `${normalizePath$1(pathId)}?${query.toString()}`;
1784
+ }
1785
+ function parseVirtualImageJsxId(id) {
1786
+ const parsed = parseId(id);
1787
+ if (!parsed.pathId.startsWith(VIRTUAL_IMAGE_JSX_PREFIX) || !parsed.pathId.endsWith(VIRTUAL_IMAGE_JSX_SUFFIX)) {
1788
+ return null;
1789
+ }
1790
+ const pathId = parsed.pathId.slice(VIRTUAL_IMAGE_JSX_PREFIX.length, -VIRTUAL_IMAGE_JSX_SUFFIX.length);
1791
+ return {
1792
+ ...parsed,
1793
+ extension: path.extname(pathId).toLowerCase(),
1794
+ pathId
1795
+ };
1796
+ }
1797
+ function getToImg() {
1798
+ return `
1799
+ import { _jsxSplit, _getVarProps as v, _getConstProps as c} from '@qwik.dev/core';
1800
+ const decoding = "async";
1801
+ const loading = "lazy";
1802
+ export default (s, w, h) =>
1803
+ // Try to preserve const attrs by not spreading when props are not provided
1804
+ p => p
1805
+ ? _jsxSplit("img", {decoding, loading, ...v(p)}, {...c(p), height: h, srcSet: s, width: w })
1806
+ : _jsxSplit("img", null, {decoding, height: h, loading, srcSet: s, width: w});
1807
+ `;
1808
+ }
1809
+ function createImageJsxModule(pathId, params) {
1810
+ return `
1811
+ import { srcSet, width, height } from ${JSON.stringify(createImageJsxImportId(pathId, params))};
1812
+ import toImg from ${JSON.stringify(TO_IMG_ID)};
1813
+
1814
+ export default toImg(srcSet, width, height);
1815
+ `;
1816
+ }
1817
+ function createSvgJsxModule(attrs) {
1818
+ return `export default p => <svg {...p} {...${JSON.stringify(attrs)}} />`;
1819
+ }
1759
1820
  function optimizeSvg({ code, path: path2 }, userOpts) {
1760
1821
  const svgAttributes = {};
1761
1822
  const prefixIdsConfiguration = userOpts?.imageOptimization?.svgo?.prefixIds;
@@ -1771,7 +1832,7 @@ function optimizeSvg({ code, path: path2 }, userOpts) {
1771
1832
  return false;
1772
1833
  }
1773
1834
  if (plugin === "prefixIds" || typeof plugin === "object" && plugin.name === "prefixIds") {
1774
- console.warn(`You are trying to use the preset-default SVGO plugin. This plugin is already included by default, you can customize it through the prefixIds option.`);
1835
+ console.warn(`You are trying to use the prefixIds SVGO plugin. This plugin is already included by default, you can customize it through the prefixIds option.`);
1775
1836
  return false;
1776
1837
  }
1777
1838
  return true;
@@ -1817,15 +1878,6 @@ function optimizeSvg({ code, path: path2 }, userOpts) {
1817
1878
  };
1818
1879
  }
1819
1880
  function imagePlugin(userOpts) {
1820
- const supportedExtensions = [
1821
- ".jpg",
1822
- ".jpeg",
1823
- ".png",
1824
- ".webp",
1825
- ".gif",
1826
- ".avif",
1827
- ".tiff"
1828
- ];
1829
1881
  return [
1830
1882
  import('vite-imagetools').then((_rawProps) => _rawProps.imagetools({
1831
1883
  exclude: [],
@@ -1853,8 +1905,10 @@ function imagePlugin(userOpts) {
1853
1905
  };
1854
1906
  },
1855
1907
  defaultDirectives: (url) => {
1856
- if (url.searchParams.has("jsx")) {
1857
- const { jsx: _, ...params } = Object.fromEntries(url.searchParams.entries());
1908
+ if (url.searchParams.has(JSX_QUERY_PARAM)) {
1909
+ const params = Object.fromEntries(url.searchParams.entries());
1910
+ delete params[JSX_QUERY_PARAM];
1911
+ delete params[INTERNAL_IMAGE_JSX_QUERY_PARAM];
1858
1912
  return new URLSearchParams({
1859
1913
  format: "webp",
1860
1914
  quality: "75",
@@ -1868,66 +1922,139 @@ function imagePlugin(userOpts) {
1868
1922
  return new URLSearchParams();
1869
1923
  }
1870
1924
  })).catch((err) => {
1871
- console.error("Error loading vite-imagetools, image imports are not available", err);
1925
+ console.error('Error loading vite-imagetools, image imports ("foo.png?jsx") are not available', err);
1872
1926
  return null;
1873
1927
  }),
1874
1928
  {
1875
1929
  name: "qwik-router-image-jsx",
1930
+ resolveId: {
1931
+ order: "pre",
1932
+ async handler(id, importer, options) {
1933
+ if (parseVirtualImageJsxId(id)) {
1934
+ return null;
1935
+ }
1936
+ if (id.endsWith(TO_IMG_ID)) {
1937
+ return VIRTUAL_TO_IMG_ID;
1938
+ }
1939
+ const { pathId, params } = parseId(id);
1940
+ if (!params.has(JSX_QUERY_PARAM) || params.has(INTERNAL_IMAGE_JSX_QUERY_PARAM)) {
1941
+ return null;
1942
+ }
1943
+ const resolved = await this.resolve(pathId, importer, {
1944
+ ...options,
1945
+ skipSelf: true
1946
+ });
1947
+ const resolvedId = parseId((resolved ?? {
1948
+ id: pathId
1949
+ }).id).pathId;
1950
+ const extension = path.extname(resolvedId).toLowerCase();
1951
+ if (extension !== ".svg" && !supportedExtensions.includes(extension)) {
1952
+ return null;
1953
+ }
1954
+ return {
1955
+ id: createVirtualImageJsxId(resolvedId, params),
1956
+ moduleSideEffects: false
1957
+ };
1958
+ }
1959
+ },
1876
1960
  load: {
1877
1961
  order: "pre",
1878
1962
  handler: async (id) => {
1879
- const { params, pathId } = parseId(id);
1880
- const extension = path.extname(pathId).toLowerCase();
1881
- if (extension === ".svg" && params.has("jsx")) {
1882
- const code = await fs.promises.readFile(pathId, "utf-8");
1963
+ if (id === VIRTUAL_TO_IMG_ID) {
1964
+ return getToImg();
1965
+ }
1966
+ const imageId = parseVirtualImageJsxId(id);
1967
+ if (!imageId) {
1968
+ return null;
1969
+ }
1970
+ if (imageId.extension === ".svg") {
1971
+ const code = await fs.promises.readFile(imageId.pathId, "utf-8");
1883
1972
  return {
1884
1973
  code,
1885
1974
  moduleSideEffects: false
1886
1975
  };
1887
1976
  }
1977
+ return {
1978
+ code: "export default undefined;",
1979
+ moduleSideEffects: false
1980
+ };
1888
1981
  }
1889
1982
  },
1890
- transform(code, id) {
1891
- id = id.toLowerCase();
1892
- const { params, pathId } = parseId(id);
1893
- if (params.has("jsx")) {
1894
- const extension = path.extname(pathId).toLowerCase();
1895
- if (supportedExtensions.includes(extension)) {
1896
- if (!code.includes("srcSet")) {
1897
- this.error(`Image '${id}' could not be optimized to JSX`);
1898
- }
1899
- const index = code.indexOf("export default");
1900
- return {
1901
- code: code.slice(0, index) + `
1902
- import { _jsxSorted } from '@qwik.dev/core';
1903
- const PROPS = {srcSet, width, height};
1904
- export default function (props, key, _, dev) {
1905
- return _jsxSorted('img', {...{decoding: 'async', loading: 'lazy'}, ...props}, PROPS, undefined, 3, key, dev);
1906
- }`,
1907
- map: null
1908
- };
1909
- } else if (extension === ".svg") {
1983
+ transform: {
1984
+ order: "pre",
1985
+ handler(code, id) {
1986
+ const imageId = parseVirtualImageJsxId(id);
1987
+ if (!imageId) {
1988
+ return null;
1989
+ }
1990
+ if (imageId.extension === ".svg") {
1910
1991
  const { svgAttributes } = optimizeSvg({
1911
1992
  code,
1912
- path: pathId
1993
+ path: imageId.pathId
1913
1994
  }, userOpts);
1914
1995
  return {
1915
- code: `
1916
- import { _jsxSorted } from '@qwik.dev/core';
1917
- const PROPS = ${JSON.stringify(svgAttributes)};
1918
- export default function (props, key, _, dev) {
1919
- return _jsxSorted('svg', props, PROPS, undefined, 3, key, dev);
1920
- }`,
1996
+ code: createSvgJsxModule(svgAttributes),
1997
+ map: null
1998
+ };
1999
+ }
2000
+ if (supportedExtensions.includes(imageId.extension)) {
2001
+ return {
2002
+ code: createImageJsxModule(imageId.pathId, imageId.params),
1921
2003
  map: null
1922
2004
  };
1923
2005
  }
2006
+ return null;
1924
2007
  }
1925
- return null;
1926
2008
  }
1927
2009
  }
1928
2010
  ];
1929
2011
  }
1930
2012
 
2013
+ async function collectServerFnModuleIds(ctx, resolvedVirtualId, loadModule) {
2014
+ const serverFnModules = /* @__PURE__ */ new Set();
2015
+ const queuedModuleIds = /* @__PURE__ */ new Set();
2016
+ const seenModuleIds = /* @__PURE__ */ new Set();
2017
+ const routes = ctx.routes;
2018
+ const layouts = ctx.layouts;
2019
+ const serverPlugins = ctx.serverPlugins;
2020
+ for (let i = 0; i < routes.length; i++) {
2021
+ const route = routes[i];
2022
+ queuedModuleIds.add(route.filePath);
2023
+ for (let j = 0; j < route.layouts.length; j++) {
2024
+ const layout = route.layouts[j];
2025
+ queuedModuleIds.add(layout.filePath);
2026
+ }
2027
+ }
2028
+ for (let i = 0; i < layouts.length; i++) {
2029
+ queuedModuleIds.add(layouts[i].filePath);
2030
+ }
2031
+ for (let i = 0; i < serverPlugins.length; i++) {
2032
+ queuedModuleIds.add(serverPlugins[i].filePath);
2033
+ }
2034
+ while (queuedModuleIds.size > 0) {
2035
+ const [id] = queuedModuleIds;
2036
+ queuedModuleIds.delete(id);
2037
+ if (seenModuleIds.has(id) || id === resolvedVirtualId) {
2038
+ continue;
2039
+ }
2040
+ seenModuleIds.add(id);
2041
+ const moduleInfo = await loadModule(id);
2042
+ if (moduleInfo.code?.includes("serverQrl(")) {
2043
+ serverFnModules.add(moduleInfo.id);
2044
+ }
2045
+ const resolvedImports = moduleInfo.importedIdResolutions.concat(moduleInfo.dynamicallyImportedIdResolutions);
2046
+ for (let i = 0; i < resolvedImports.length; i++) {
2047
+ const resolvedImport = resolvedImports[i];
2048
+ if (!resolvedImport.external && !seenModuleIds.has(resolvedImport.id)) {
2049
+ queuedModuleIds.add(resolvedImport.id);
2050
+ }
2051
+ }
2052
+ }
2053
+ return [
2054
+ ...serverFnModules
2055
+ ];
2056
+ }
2057
+
1931
2058
  async function validatePlugin(opts) {
1932
2059
  if (typeof opts.routesDir !== "string") {
1933
2060
  throw new Error(`qwikRouter plugin "routesDir" option missing`);
@@ -2187,16 +2314,14 @@ const makeRouterDevMiddleware = (server, ctx) => async (req, res, next) => {
2187
2314
  const render = async (opts) => {
2188
2315
  return await renderer(opts);
2189
2316
  };
2190
- const { router, staticFile, notFound } = createQwikRouter({
2317
+ const { router, staticFile } = createQwikRouter({
2191
2318
  render,
2192
2319
  // inject the platform from dev middleware options
2193
2320
  platform: ctx.opts.platform
2194
2321
  });
2195
2322
  const wrappedRes = wrapResponseForHtmlTransform(req, res, server);
2196
2323
  staticFile(req, wrappedRes, () => {
2197
- router(req, wrappedRes, () => {
2198
- notFound(req, wrappedRes, next);
2199
- });
2324
+ router(req, wrappedRes, next);
2200
2325
  });
2201
2326
  } catch (e) {
2202
2327
  if (e instanceof Error) {
@@ -2288,7 +2413,7 @@ async function generateServerPackageJson(outDir) {
2288
2413
  const serverPackageJsonCode = JSON.stringify(packageJson, null, 2);
2289
2414
  await fs.promises.writeFile(serverPackageJsonPath, serverPackageJsonCode);
2290
2415
  }
2291
- function qwikRouterPlugin(userOpts) {
2416
+ function qwikRouterPlugin(userOpts, buildContextRef) {
2292
2417
  let ctx = null;
2293
2418
  let mdxTransform = null;
2294
2419
  let rootDir = null;
@@ -2411,6 +2536,7 @@ function qwikRouterPlugin(userOpts) {
2411
2536
  rootDir = resolve(config.root);
2412
2537
  const target = config.build?.ssr || config.mode === "ssr" ? "ssr" : "client";
2413
2538
  ctx = createBuildContext(rootDir, config.base, userOpts, target, !userOpts?.staticImportRoutes);
2539
+ buildContextRef.current = ctx;
2414
2540
  await validatePlugin(ctx.opts);
2415
2541
  mdxTransform = await createMdxTransformer(ctx);
2416
2542
  qwikPlugin = config.plugins.find((p) => p.name === "vite-plugin-qwik");
@@ -2578,20 +2704,34 @@ function qwikRouterPlugin(userOpts) {
2578
2704
  };
2579
2705
  return plugin;
2580
2706
  }
2581
- function serverFnsPlugin() {
2707
+ function serverFnsPlugin(buildContextRef) {
2582
2708
  const RESOLVED_ID = "\0" + VIRTUAL_SERVER_FNS;
2583
2709
  const serverFnModules = /* @__PURE__ */ new Set();
2584
- let pendingModules = 0;
2585
- let resolveServerFns = null;
2586
- let serverFnsReady;
2710
+ let serverFnsReady = null;
2587
2711
  function reset() {
2588
2712
  serverFnModules.clear();
2589
- pendingModules = 0;
2590
- serverFnsReady = new Promise((r) => {
2591
- resolveServerFns = r;
2592
- });
2713
+ serverFnsReady = null;
2593
2714
  }
2594
2715
  reset();
2716
+ async function collectServerFnModules() {
2717
+ if (serverFnsReady) {
2718
+ await serverFnsReady;
2719
+ return;
2720
+ }
2721
+ serverFnsReady = (async () => {
2722
+ const ctx = buildContextRef.current;
2723
+ if (!ctx) {
2724
+ return;
2725
+ }
2726
+ const moduleIds = await collectServerFnModuleIds(ctx, RESOLVED_ID, (id) => this.load({
2727
+ id
2728
+ }));
2729
+ for (let i = 0; i < moduleIds.length; i++) {
2730
+ serverFnModules.add(moduleIds[i]);
2731
+ }
2732
+ })();
2733
+ await serverFnsReady;
2734
+ }
2595
2735
  return {
2596
2736
  name: "vite-plugin-qwik-router-server-fns",
2597
2737
  buildStart() {
@@ -2611,7 +2751,7 @@ function serverFnsPlugin() {
2611
2751
  const isServerBuild = this.environment.config.consumer === "server" && this.environment.mode === "build";
2612
2752
  if (id === RESOLVED_ID) {
2613
2753
  if (isServerBuild) {
2614
- await serverFnsReady;
2754
+ await collectServerFnModules.call(this);
2615
2755
  }
2616
2756
  if (!isServerBuild || serverFnModules.size === 0) {
2617
2757
  return "// No server$ functions";
@@ -2620,35 +2760,18 @@ function serverFnsPlugin() {
2620
2760
  ...serverFnModules
2621
2761
  ].map((mod) => `import ${JSON.stringify(mod)};`).join("\n");
2622
2762
  }
2623
- if (isServerBuild && id !== RESOLVED_ID) {
2624
- pendingModules++;
2625
- this.load({
2626
- id
2627
- }).then((result) => {
2628
- if (typeof result.code === "string" && result.code.includes("serverQrl(")) {
2629
- serverFnModules.add(id);
2630
- }
2631
- }).finally(() => {
2632
- pendingModules--;
2633
- if (pendingModules <= 0 && resolveServerFns) {
2634
- setTimeout(() => {
2635
- if (pendingModules <= 0 && resolveServerFns) {
2636
- resolveServerFns();
2637
- resolveServerFns = null;
2638
- }
2639
- }, 50);
2640
- }
2641
- });
2642
- }
2643
2763
  return null;
2644
2764
  }
2645
2765
  }
2646
2766
  };
2647
2767
  }
2648
2768
  function qwikRouter(userOpts) {
2769
+ const buildContextRef = {
2770
+ current: null
2771
+ };
2649
2772
  return [
2650
- qwikRouterPlugin(userOpts),
2651
- serverFnsPlugin(),
2773
+ qwikRouterPlugin(userOpts, buildContextRef),
2774
+ serverFnsPlugin(buildContextRef),
2652
2775
  ...imagePlugin(userOpts)
2653
2776
  ];
2654
2777
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@qwik.dev/router",
3
3
  "description": "The router for Qwik.",
4
- "version": "2.0.0-beta.31",
4
+ "version": "2.0.0-beta.32",
5
5
  "bugs": "https://github.com/QwikDev/qwik/issues",
6
6
  "dependencies": {
7
7
  "@azure/functions": "3.5.1",
@@ -40,7 +40,7 @@
40
40
  "tsm": "2.3.0",
41
41
  "typescript": "5.9.3",
42
42
  "uvu": "0.5.6",
43
- "@qwik.dev/core": "2.0.0-beta.31"
43
+ "@qwik.dev/core": "2.0.0-beta.32"
44
44
  },
45
45
  "engines": {
46
46
  "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
@@ -169,7 +169,7 @@
169
169
  "main": "./lib/index.qwik.mjs",
170
170
  "peerDependencies": {
171
171
  "vite": ">=6 <9",
172
- "@qwik.dev/core": "^2.0.0-beta.31"
172
+ "@qwik.dev/core": "^2.0.0-beta.32"
173
173
  },
174
174
  "publishConfig": {
175
175
  "access": "public"