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

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,6 +1,6 @@
1
1
  import { t as ensureSlash } from "../../../chunks/pathname.mjs";
2
2
  import { viteAdapter } from "../../shared/vite/index.mjs";
3
- import { v as normalizePathSlash } from "../../../chunks/fs.mjs";
3
+ import { y as normalizePathSlash } from "../../../chunks/fs.mjs";
4
4
  import fs from "node:fs";
5
5
  import { join, relative } from "node:path";
6
6
  /** @public */ function cloudflarePagesAdapter(opts = {}) {
@@ -31,21 +31,27 @@ var injectStatics = async (staticPathsCode, outDir) => {
31
31
  await walk(outDir);
32
32
  await Promise.all(promises);
33
33
  };
34
- /** Cleans the client output SSG results if needed and injects the SSG metadata into the build output */ async function postBuild(clientOutDir, serverOutDir, pathName, userStaticPaths, cleanStatic) {
34
+ /** Cleans the client output SSG results if needed and injects the SSG metadata into the build output */ async function postBuild(clientOutDir, serverOutDir, pathName, userStaticPaths, cleanStatic, noTrailingSlash) {
35
35
  if (pathName && !pathName.endsWith("/")) pathName = ensureSlash(pathName);
36
36
  const pathNameBase = pathName || "/";
37
37
  const ignorePathnames = /* @__PURE__ */ new Set([pathNameBase + (globalThis.__QWIK_BUILD_DIR__ || "build") + "/", pathNameBase + (globalThis.__QWIK_ASSETS_DIR__ || "assets") + "/"]);
38
- const staticPaths = new Set(userStaticPaths.map(ensureSlash));
38
+ const getStaticPathname = (pathname) => {
39
+ pathname = ensureSlash(pathname);
40
+ if (noTrailingSlash && pathname !== pathNameBase) return pathname.slice(0, -1);
41
+ return pathname;
42
+ };
43
+ const staticPaths = new Set(userStaticPaths.map(getStaticPathname));
39
44
  const loadItem = async (fsDir, fsName, pathname) => {
40
45
  pathname = ensureSlash(pathname);
41
46
  if (ignorePathnames.has(pathname)) return;
42
47
  const fsPath = join(fsDir, fsName);
43
48
  if (fsName === "index.html") {
44
- if (!staticPaths.has(pathname) && cleanStatic) await fs.promises.unlink(fsPath);
49
+ if (!staticPaths.has(getStaticPathname(pathname)) && cleanStatic) await fs.promises.unlink(fsPath);
45
50
  return;
46
51
  }
47
52
  if (LOADER_REGEX.test("/" + fsName)) {
48
- if (staticPaths.has(pathname)) staticPaths.add(pathname + fsName);
53
+ const staticPathname = getStaticPathname(pathname);
54
+ if (staticPaths.has(staticPathname)) staticPaths.add(ensureSlash(staticPathname) + fsName);
49
55
  else if (cleanStatic) await fs.promises.unlink(fsPath);
50
56
  return;
51
57
  }
@@ -217,6 +223,7 @@ var QWIK_SSG_WORKER_ENTRY_RESOLVED = "\0@qwik-ssg-worker-entry";
217
223
  const basePathname = qwikRouterPlugin.api.getBasePathname();
218
224
  const clientOutDir = qwikVitePlugin.api.getClientOutDir();
219
225
  const clientPublicOutDir = qwikVitePlugin.api.getClientPublicOutDir();
226
+ const noTrailingSlash = builder.config.define?.["globalThis.__NO_TRAILING_SLASH__"] === "true";
220
227
  const ssgEnv = builder.environments.ssg;
221
228
  const ssgOutDir = ssgOutDirFor(builder.config.root);
222
229
  if (ssgEnv && clientOutDir && clientPublicOutDir) {
@@ -248,7 +255,7 @@ var QWIK_SSG_WORKER_ENTRY_RESOLVED = "\0@qwik-ssg-worker-entry";
248
255
  force: true
249
256
  });
250
257
  }
251
- await postBuild(clientPublicOutDir, serverOutDir, basePathname, staticPaths, !!opts.cleanStaticGenerated);
258
+ await postBuild(clientPublicOutDir, serverOutDir, basePathname, staticPaths, !!opts.cleanStaticGenerated, noTrailingSlash);
252
259
  if (typeof opts.generate === "function") await opts.generate({
253
260
  outputEntries,
254
261
  serverOutDir,
package/lib/chunks/fs.mjs CHANGED
@@ -51,6 +51,13 @@ function getMenuPathname(opts, filePath) {
51
51
  const result = normalizePathname(pathname, opts.basePathname);
52
52
  return ensureSlash(result);
53
53
  }
54
+ /** Normalize equivalent filesystem paths for string-key comparisons. */ function normalizePathKey(path) {
55
+ if (path.startsWith("\\\\?\\UNC\\")) path = `\\\\${path.slice(8)}`;
56
+ else if (path.startsWith("\\\\?\\")) path = path.slice(4);
57
+ else if (path.startsWith("//?/UNC/")) path = `//${path.slice(8)}`;
58
+ else if (path.startsWith("//?/")) path = path.slice(4);
59
+ return normalizePath(path).replace(/\\/g, "/");
60
+ }
54
61
  /**
55
62
  * Creates an id for the module, based on its path.
56
63
  *
@@ -130,4 +137,4 @@ function getPathnameFromDirPath(opts, dirPath) {
130
137
  if (pathname === "") return "/";
131
138
  return pathname;
132
139
  }
133
- export { normalizePath as _, getPathnameFromDirPath as a, removeExtension as b, isIndexModule as c, isMenuFileName as d, isModuleExt as f, isServiceWorkerName as g, isPluginModule as h, getMenuPathname as i, isLayoutModule as l, isPageModuleExt as m, errorBoundaryName as n, isEntryName as o, isPageExt as p, getExtension as r, isErrorName as s, createFileId as t, isMarkdownExt as u, normalizePathSlash as v, parseRouteIndexName as y };
140
+ export { normalizePath as _, getPathnameFromDirPath as a, parseRouteIndexName as b, isIndexModule as c, isMenuFileName as d, isModuleExt as f, isServiceWorkerName as g, isPluginModule as h, getMenuPathname as i, isLayoutModule as l, isPageModuleExt as m, errorBoundaryName as n, isEntryName as o, isPageExt as p, getExtension as r, isErrorName as s, createFileId as t, isMarkdownExt as u, normalizePathKey as v, removeExtension as x, normalizePathSlash as y };
@@ -208,24 +208,12 @@ function getRouteLoaderCtx(requestEv) {
208
208
  }
209
209
  return ctx;
210
210
  }
211
- /**
212
- * Update the loader paths store on client-side navigation.
213
- *
214
- * Only adds/updates entries for loaders present on the new route. Entries for loaders that are NOT
215
- * on the new route are left untouched — their ComputedSignals keep their prior values, and no
216
- * track() fires to invalidate them. If the user navigates back to a route where the loader IS
217
- * present, the path updates and the signal re-fetches. This is the "stale is fine by default"
218
- * contract: readers see old data until new data arrives.
219
- */ var updateRouteLoaderPaths = (ctx, loaderPaths, pageUrl) => {
211
+ /** Update route loader matching state on navigation. */ var updateRouteLoaderPaths = (ctx, loaderPaths, pageUrl) => {
220
212
  if (!isServer) {
221
213
  ctx.pagePathname = pageUrl.pathname;
222
214
  ctx.pageSearch = pageUrl.search;
223
- if (isDev) ctx.devRouteLoaderPaths = {};
224
- }
225
- if (loaderPaths) for (const key in loaderPaths) {
226
- ctx.loaderPaths[key] = loaderPaths[key];
227
- if (!isServer && isDev) ctx.devRouteLoaderPaths[key] = true;
228
- }
215
+ ctx.routeLoaderCandidates = loaderPaths;
216
+ } else if (loaderPaths) for (const key in loaderPaths) ctx.loaderPaths[key] = loaderPaths[key];
229
217
  };
230
218
  var getModuleRouteLoaders = (mods) => {
231
219
  const routeLoaders = [];
@@ -262,6 +250,11 @@ var isImmutableLoader = (loaderId) => immutableLoaderIds.has(loaderId);
262
250
  */ var invalidateNavRouteLoaders = (state) => {
263
251
  for (const id in state) if (!id.startsWith(ROUTE_LOADER_VALUE_PREFIX) && !immutableLoaderIds.has(id)) state[id].invalidate();
264
252
  };
253
+ var applyClientRouteLoaderPath = (loaderId, ctx) => {
254
+ const currentPath = ctx.routeLoaderCandidates?.[loaderId];
255
+ if (currentPath) ctx.loaderPaths[loaderId] = currentPath;
256
+ else if (isDev && ctx.pagePathname) ctx.loaderPaths[loaderId] = ctx.pagePathname;
257
+ };
265
258
  /**
266
259
  * Inject a pre-loaded value into a ComputedSignal while preserving subscriptions. Delegates to the
267
260
  * core helper which calls `invalidate({__v})` + `$computeIfNeeded$()` so the compute function runs
@@ -304,7 +297,7 @@ var resolveRouteLoaderByHash = (routeLoaders, loaderId) => {
304
297
  enumerable: true
305
298
  },
306
299
  params: {
307
- value: {},
300
+ value: rootRequestEv.params,
308
301
  enumerable: true
309
302
  },
310
303
  pathname: {
@@ -518,20 +511,14 @@ var createRouteLoaderSignal = (loader, routeLoaderCtx, state) => {
518
511
  return signal;
519
512
  };
520
513
  var ensureRouteLoaderSignal = (loader, state, routeLoaderCtx) => {
514
+ if (!isServer && routeLoaderCtx.pagePathname) applyClientRouteLoaderPath(loader.__id, routeLoaderCtx);
521
515
  if (loader.__cacheControl === "immutable") immutableLoaderIds.add(loader.__id);
522
516
  if (isServer && loader.__serializationStrategy === "never") state[getRouteLoaderValueStateKey(loader.__id)] = _UNINITIALIZED;
523
517
  return state[loader.__id] ||= createRouteLoaderSignal(loader, routeLoaderCtx, state);
524
518
  };
525
519
  var ensureRouteLoaderSignals = (mods, state, routeLoaderCtx) => {
526
520
  const loaders = getModuleRouteLoaders(mods);
527
- for (let i = 0; i < loaders.length; i++) {
528
- const loader = loaders[i];
529
- if (isDev && !isServer) {
530
- const devCtx = routeLoaderCtx;
531
- if (devCtx.pagePathname && !devCtx.devRouteLoaderPaths?.[loader.__id]) devCtx.loaderPaths[loader.__id] = devCtx.pagePathname;
532
- }
533
- ensureRouteLoaderSignal(loader, state, routeLoaderCtx);
534
- }
521
+ for (let i = 0; i < loaders.length; i++) ensureRouteLoaderSignal(loaders[i], state, routeLoaderCtx);
535
522
  return loaders;
536
523
  };
537
524
  var loadRouteLoader = (loader, requestEv) => {
@@ -554,9 +541,7 @@ var loadRouteLoader = (loader, requestEv) => {
554
541
  /** @internal */ var routeLoaderQrl = (loaderQrl, ...rest) => {
555
542
  const { id, validators, serializationStrategy, cacheControl, eTag, cacheKey, search, blockSSR } = getLoaderOptions(rest);
556
543
  function loader() {
557
- const state = _resolveContextWithoutSequentialScope(RouteStateContext);
558
- let signal = state[loader.__id];
559
- if (!signal) signal = ensureRouteLoaderSignal(loader, state, _resolveContextWithoutSequentialScope(RouteLoaderCtxContext));
544
+ const signal = ensureRouteLoaderSignal(loader, _resolveContextWithoutSequentialScope(RouteStateContext), _resolveContextWithoutSequentialScope(RouteLoaderCtxContext));
560
545
  signal.promise();
561
546
  return signal;
562
547
  }
@@ -851,6 +851,19 @@ async function runValidators(requestEv, validators, data, devMode) {
851
851
  requestEv.send(requestEv.status(), serialized);
852
852
  };
853
853
  }
854
+ var staticPaths = /* @__PURE__ */ new Set(["__QWIK_ROUTER_STATIC_PATHS_ARRAY__"]);
855
+ /**
856
+ * Decide whether a given request path is a static path, for optimizing file access.
857
+ *
858
+ * @internal
859
+ */ function isStaticPath(method, url) {
860
+ method = method.toUpperCase();
861
+ if (method !== "GET" && method !== "HEAD") return false;
862
+ const p = url.pathname;
863
+ if (p.startsWith("/" + (globalThis.__QWIK_BUILD_DIR__ || "build") + "/")) return true;
864
+ if (p.startsWith("/" + (globalThis.__QWIK_ASSETS_DIR__ || "assets") + "/")) return true;
865
+ return staticPaths.has(p);
866
+ }
854
867
  /**
855
868
  * Hypertext Transfer Protocol (HTTP) response status codes.
856
869
  *
@@ -1167,7 +1180,7 @@ function createResolveRequestHandlers() {
1167
1180
  if (isPageRoute) {
1168
1181
  requestHandlers.push(loaderHandler(routeLoaders, route.$loaderPaths$));
1169
1182
  requestHandlers.push(actionHandler(routeActions));
1170
- if (!route.$notFound$) requestHandlers.push(fixTrailingSlash);
1183
+ requestHandlers.push(route.$notFound$ ? fixStaticTrailingSlash : fixTrailingSlash);
1171
1184
  }
1172
1185
  if (isPageRoute) {
1173
1186
  requestHandlers.push((ev) => {
@@ -1367,6 +1380,7 @@ function createResolveRequestHandlers() {
1367
1380
  if (accept && !accept.includes("text/html")) throw e;
1368
1381
  const status = e.status;
1369
1382
  requestEv.status(status);
1383
+ requestEv.headers.set("Cache-Control", "no-store");
1370
1384
  const errorLoader = route.$errorLoader$;
1371
1385
  route.$mods$ = errorLoader ? await Promise.all(errorLoader.map((load) => load())) : [await loadHttpError()];
1372
1386
  requestEv.sharedMap.set(RequestEvHttpStatusMessage, typeof e.data === "string" ? e.data : "Server Error");
@@ -1380,6 +1394,7 @@ function createResolveRequestHandlers() {
1380
1394
  requestEv.sharedMap.delete(RequestEvSharedActionId);
1381
1395
  requestEv.sharedMap.delete(RequestEvSharedActionFormData);
1382
1396
  requestEv.sharedMap.delete("@actionResult");
1397
+ requestEv.sharedMap.delete(RequestEvETagCacheKey);
1383
1398
  }
1384
1399
  async function runValidators(requestEv, validators, data) {
1385
1400
  let lastResult = {
@@ -1449,6 +1464,15 @@ function createResolveRequestHandlers() {
1449
1464
  } else if (pathname.endsWith("/")) throw ev.redirect(HttpStatus.MovedPermanently, pathname.slice(0, pathname.length - 1) + search);
1450
1465
  }
1451
1466
  }
1467
+ /**
1468
+ * Prerendered pages are pruned from the production server route plan, so a request missing the
1469
+ * canonical trailing slash lands here as not found. Redirect it when the canonical path was
1470
+ * prerendered, and leave a genuine 404 alone.
1471
+ */ function fixStaticTrailingSlash(ev) {
1472
+ const canonicalUrl = new URL(ev.originalUrl);
1473
+ canonicalUrl.pathname = globalThis.__NO_TRAILING_SLASH__ ? canonicalUrl.pathname.replace(/\/$/, "") : ensureSlash(canonicalUrl.pathname);
1474
+ if (isStaticPath(ev.request.method, canonicalUrl)) fixTrailingSlash(ev);
1475
+ }
1452
1476
  function verifySerializable(data, qrl) {
1453
1477
  try {
1454
1478
  _verifySerializable(data, void 0);
@@ -1495,7 +1519,11 @@ The request origin "${inputOrigin}" does not match the server origin "${origin}"
1495
1519
  if (!responseHeaders.has("Content-Type")) responseHeaders.set("Content-Type", "text/html; charset=utf-8");
1496
1520
  const cachePlan = requestEv.sharedMap.get(RequestEvETagCacheKey);
1497
1521
  const { readable, writable } = new TextEncoderStream();
1498
- const writableStream = requestEv.getWritableStream();
1522
+ let responseWriter;
1523
+ const lazyResponseSink = new WritableStream({ write(chunk) {
1524
+ responseWriter ||= requestEv.getWritableStream().getWriter();
1525
+ return responseWriter.write(chunk);
1526
+ } });
1499
1527
  let cacheChunks;
1500
1528
  let pipeSource = readable;
1501
1529
  if (cachePlan) {
@@ -1507,7 +1535,10 @@ The request origin "${inputOrigin}" does not match the server origin "${origin}"
1507
1535
  pipeSource = readable.pipeThrough(capture);
1508
1536
  }
1509
1537
  let pipeError;
1510
- const pipe = pipeSource.pipeTo(writableStream, { preventClose: true }).catch((error) => {
1538
+ let boundaryErrored = false;
1539
+ let noStoreSent = false;
1540
+ let overrodeCacheControl = false;
1541
+ const pipe = pipeSource.pipeTo(lazyResponseSink, { preventClose: true }).catch((error) => {
1511
1542
  pipeError = error;
1512
1543
  });
1513
1544
  const stream = writable.getWriter();
@@ -1522,16 +1553,30 @@ The request origin "${inputOrigin}" does not match the server origin "${origin}"
1522
1553
  containerAttributes: {
1523
1554
  ["q:render"]: isStatic ? "static" : "",
1524
1555
  ...serverData.containerAttributes
1556
+ },
1557
+ onBeforeFirstFlush: (info) => {
1558
+ if (info.errorBoundaryCaught) {
1559
+ boundaryErrored = true;
1560
+ noStoreSent = true;
1561
+ overrodeCacheControl = responseHeaders.has("Cache-Control");
1562
+ responseHeaders.set("Cache-Control", "no-store");
1563
+ }
1525
1564
  }
1526
1565
  });
1527
1566
  if (typeof result.html === "string") await stream.write(result.html);
1567
+ boundaryErrored ||= result.errorBoundaryCaught === true;
1528
1568
  } finally {
1529
- await stream.ready;
1530
- await stream.close();
1531
- await pipe;
1569
+ try {
1570
+ await stream.ready;
1571
+ await stream.close();
1572
+ } finally {
1573
+ await pipe;
1574
+ responseWriter?.releaseLock();
1575
+ }
1532
1576
  }
1533
1577
  if (pipeError) throw pipeError;
1534
- if (cachePlan && cacheChunks && cacheChunks.length > 0) {
1578
+ if (boundaryErrored && (noStoreSent ? cachePlan || overrodeCacheControl : !!cachePlan)) console.warn(`An <ErrorBoundary> caught during SSR of ${requestEv.url.pathname} — configured caching disabled: ` + (noStoreSent ? "the response was sent with Cache-Control: no-store." : "the SSR cache was skipped (response headers were already sent)."));
1579
+ if (cachePlan && cacheChunks && cacheChunks.length > 0 && !boundaryErrored) {
1535
1580
  const totalLength = cacheChunks.reduce((sum, chunk) => sum + chunk.length, 0);
1536
1581
  const combined = new Uint8Array(totalLength);
1537
1582
  let offset = 0;
@@ -1546,7 +1591,7 @@ The request origin "${inputOrigin}" does not match the server origin "${origin}"
1546
1591
  body: html
1547
1592
  });
1548
1593
  }
1549
- await writableStream.close();
1594
+ if (responseWriter) await requestEv.getWritableStream().close();
1550
1595
  };
1551
1596
  }
1552
1597
  function now() {
@@ -1567,6 +1612,7 @@ The request origin "${inputOrigin}" does not match the server origin "${origin}"
1567
1612
  actionsMiddleware,
1568
1613
  checkBrand,
1569
1614
  checkCSRF,
1615
+ fixStaticTrailingSlash,
1570
1616
  fixTrailingSlash,
1571
1617
  getPathname,
1572
1618
  isLastModulePageRoute,
@@ -1579,7 +1625,7 @@ The request origin "${inputOrigin}" does not match the server origin "${origin}"
1579
1625
  verifySerializable
1580
1626
  };
1581
1627
  }
1582
- var { actionsMiddleware, checkBrand, checkCSRF, fixTrailingSlash, getPathname, isLastModulePageRoute, isQrl, loadersMiddleware, measure, renderQwikMiddleware, resolveRequestHandlers, streamServerFunctionResult, verifySerializable } = createResolveRequestHandlers();
1628
+ var { actionsMiddleware, checkBrand, checkCSRF, fixStaticTrailingSlash, fixTrailingSlash, getPathname, isLastModulePageRoute, isQrl, loadersMiddleware, measure, renderQwikMiddleware, resolveRequestHandlers, streamServerFunctionResult, verifySerializable } = createResolveRequestHandlers();
1583
1629
  /** @public */ var ESCAPE_HTML = /[&<>]/g;
1584
1630
  var escapeHtml = (s) => {
1585
1631
  return s.replace(ESCAPE_HTML, (c) => {
@@ -1704,4 +1750,4 @@ function runQwikRouter(serverRequestEv, loadedRoute, requestHandlers, rebuildRou
1704
1750
  };
1705
1751
  }
1706
1752
  }
1707
- export { clearLoaderCache as a, mergeHeadersCookies as c, resolveRequestHandlers as i, getErrorHtml as n, clearSsrCache as o, renderQwikMiddleware as r, RewriteMessage as s, runQwikRouter as t };
1753
+ export { isStaticPath as a, RewriteMessage as c, resolveRequestHandlers as i, mergeHeadersCookies as l, getErrorHtml as n, clearLoaderCache as o, renderQwikMiddleware as r, clearSsrCache as s, runQwikRouter as t };
package/lib/index.d.ts CHANGED
@@ -391,14 +391,6 @@ declare interface EndpointResponse {
391
391
  loaderHashes?: string[];
392
392
  }
393
393
 
394
- /** @public */
395
- export declare const ErrorBoundary: Component<ErrorBoundaryProps>;
396
-
397
- /** @public */
398
- declare interface ErrorBoundaryProps {
399
- fallback$?: QRL<(error: any) => any>;
400
- }
401
-
402
394
  /**
403
395
  * Drops control-flow signals (`ev.redirect()`, `ev.error()`, etc.) from a loader/action return
404
396
  * type: those are thrown, not surfaced as data. `ev.fail()` is plain data and is kept.
@@ -1062,6 +1054,7 @@ declare type RouteLoaderCtx = {
1062
1054
  goto?: NoSerialize<RouteNavigate>;
1063
1055
  /** Client manifest hash for q-loader fetch URLs. */
1064
1056
  manifestHash?: string;
1057
+ routeLoaderCandidates?: Record<string, string>;
1065
1058
  };
1066
1059
 
1067
1060
  /* Excluded from this release type: routeLoaderQrl */
@@ -1316,16 +1309,16 @@ export declare const zod$: ZodConstructor;
1316
1309
  export declare type ZodConstructor = {
1317
1310
  <T extends z_2.ZodRawShape>(schema: T): ZodDataValidator<z_2.ZodObject<T>>;
1318
1311
  <T extends z_2.ZodRawShape>(schema: (zod: typeof z_2.z, ev: RequestEvent) => T): ZodDataValidator<z_2.ZodObject<T>>;
1319
- <T extends z_2.Schema>(schema: T): ZodDataValidator<T>;
1320
- <T extends z_2.Schema>(schema: (zod: typeof z_2.z, ev: RequestEvent) => T): ZodDataValidator<T>;
1312
+ <T extends z_2.ZodType>(schema: T): ZodDataValidator<T>;
1313
+ <T extends z_2.ZodType>(schema: (zod: typeof z_2.z, ev: RequestEvent) => T): ZodDataValidator<T>;
1321
1314
  };
1322
1315
 
1323
1316
  /** @public */
1324
1317
  declare type ZodConstructorQRL = {
1325
1318
  <T extends z_2.ZodRawShape>(schema: QRL<T>): ZodDataValidator<z_2.ZodObject<T>>;
1326
1319
  <T extends z_2.ZodRawShape>(schema: QRL<(zod: typeof z_2.z, ev: RequestEvent) => T>): ZodDataValidator<z_2.ZodObject<T>>;
1327
- <T extends z_2.Schema>(schema: QRL<T>): ZodDataValidator<T>;
1328
- <T extends z_2.Schema>(schema: QRL<(zod: typeof z_2.z, ev: RequestEvent) => T>): ZodDataValidator<T>;
1320
+ <T extends z_2.ZodType>(schema: QRL<T>): ZodDataValidator<T>;
1321
+ <T extends z_2.ZodType>(schema: QRL<(zod: typeof z_2.z, ev: RequestEvent) => T>): ZodDataValidator<T>;
1329
1322
  };
1330
1323
 
1331
1324
  /** @public */
@@ -1,24 +1,15 @@
1
1
  import { t as ensureSlash } from "./chunks/pathname.mjs";
2
- import { A as routeLoader$, B as RouteLoaderCtxContext, E as isImmutableLoader, F as ContentContext, G as QACTION_KEY, H as RouteNavigateContext, I as ContentInternalContext, J as Q_ROUTE, K as QDATA_KEY, L as DocumentHeadContext, M as setLoaderSignalValue, P as updateRouteLoaderPaths, R as HttpStatusContext, T as invalidateNavRouteLoaders, U as RoutePreventNavigateContext, V as RouteLocationContext, W as RouteStateContext, _ as fetchRouteLoaderData, b as getRequestEvent, c as shouldPreload, d as loadRoute, g as ensureRouteLoaderSignals, i as getClientNavPath, j as routeLoaderQrl, l as toPath, m as clearNavFetchCache, n as resolveHead, o as isSameOrigin, q as QFN_KEY, s as isSamePath, t as createDocumentHead, u as toUrl, y as getModuleRouteLoaders, z as RouteActionContext } from "./chunks/head.qwik.mjs";
3
- import { SkipRender, Slot, _captures, _fnSignal, _getConstProps, _getVarProps, _jsxSorted, _jsxSplit, _qrlSync, _restProps, _wrapProp, componentQrl, createComputedQrl, createElement, eventQrl, getLocale, implicit$FirstArg, inlinedQrl, isBrowser, isDev, isServer, noSerialize, untrack, useContext, useContextProvider, useErrorBoundary, useOnWindow, useServerData, useSignal, useStore, useTaskQrl, useVisibleTaskQrl, withLocale } from "@qwik.dev/core";
4
- import { Fragment } from "@qwik.dev/core/jsx-runtime";
2
+ import { A as routeLoader$, B as RouteLoaderCtxContext, E as isImmutableLoader, F as ContentContext, G as QACTION_KEY, H as RouteNavigateContext, I as ContentInternalContext, J as Q_ROUTE, K as QDATA_KEY, L as DocumentHeadContext, M as setLoaderSignalValue, P as updateRouteLoaderPaths, R as HttpStatusContext, T as invalidateNavRouteLoaders, U as RoutePreventNavigateContext, V as RouteLocationContext, W as RouteStateContext, _ as fetchRouteLoaderData, a as isPromise, b as getRequestEvent, c as shouldPreload, d as loadRoute, g as ensureRouteLoaderSignals, i as getClientNavPath, j as routeLoaderQrl, l as toPath, m as clearNavFetchCache, n as resolveHead, o as isSameOrigin, q as QFN_KEY, s as isSamePath, t as createDocumentHead, u as toUrl, y as getModuleRouteLoaders, z as RouteActionContext } from "./chunks/head.qwik.mjs";
3
+ import { SkipRender, Slot, _captures, _fnSignal, _getConstProps, _getVarProps, _jsxSorted, _jsxSplit, _qrlSync, _restProps, _wrapProp, componentQrl, createComputedQrl, createElement, eventQrl, getLocale, implicit$FirstArg, inlinedQrl, isBrowser, isDev, isServer, noSerialize, untrack, useContext, useContextProvider, useServerData, useSignal, useStore, useTaskQrl, useVisibleTaskQrl, withLocale } from "@qwik.dev/core";
5
4
  import * as qwikRouterConfig from "@qwik-router-config";
6
5
  import { p } from "@qwik.dev/core/preloader";
7
6
  import { _deserialize, _getContextContainer, _getContextHostElement, _hasStoreEffects, _serialize, _waitUntilRendered, forceStoreEffects } from "@qwik.dev/core/internal";
7
+ import { Fragment } from "@qwik.dev/core/jsx-runtime";
8
8
  import * as v from "valibot";
9
9
  import * as z$1 from "zod";
10
10
  import { z } from "zod";
11
11
  import swRegister from "@qwik-router-sw-register";
12
12
  import { renderToStream } from "@qwik.dev/core/server";
13
- /** @public */ var ErrorBoundary = /*#__PURE__*/ componentQrl(/*#__PURE__*/ inlinedQrl((props) => {
14
- const store = useErrorBoundary();
15
- useOnWindow("qerror", /*#__PURE__*/ inlinedQrl((e) => {
16
- const store = _captures[0];
17
- store.error = e.detail.error;
18
- }, "ErrorBoundary_component_useOnWindow_G0jFRpoNY0M", [store]));
19
- if (store.error && props.fallback$) return /*#__PURE__*/ _jsxSorted(Fragment, null, null, props.fallback$(store.error), 1, "bA_0");
20
- return /*#__PURE__*/ _jsxSorted(Slot, null, null, null, 3, "bA_1");
21
- }, "ErrorBoundary_component_pOa6vjtC7ik"));
22
13
  /**
23
14
  * Prefetch a route's loader data in parallel (fire-and-forget). The per-nav fetch map dedupes
24
15
  * repeat hovers and shares the promise with the navigation's own fetch; the browser HTTP cache
@@ -1026,7 +1017,14 @@ function initializeSPA(goto, scroller) {
1026
1017
  const contentModules = track(contentInternal);
1027
1018
  if (!contentModules) return;
1028
1019
  const actionData = track(actionDataSignal);
1029
- const head = track(() => resolveHead(actionData, loaderState, routeLocation, contentModules, getLocale(""), serverHead));
1020
+ let head;
1021
+ try {
1022
+ head = track(() => resolveHead(actionData, loaderState, routeLocation, contentModules, getLocale(""), serverHead));
1023
+ } catch (error) {
1024
+ if (isServer || isPromise(error)) throw error;
1025
+ console.error(error);
1026
+ return;
1027
+ }
1030
1028
  documentHead.links = head.links;
1031
1029
  documentHead.meta = head.meta;
1032
1030
  documentHead.styles = head.styles;
@@ -1220,7 +1218,7 @@ var flattenZodIssues = (issues) => {
1220
1218
  async validate(ev, inputData) {
1221
1219
  const schema = await qrl.resolve().then((obj) => {
1222
1220
  if (typeof obj === "function") obj = obj(z$1, ev);
1223
- if (obj instanceof z$1.Schema) return obj;
1221
+ if (obj instanceof z$1.ZodType) return obj;
1224
1222
  else return z$1.object(obj);
1225
1223
  });
1226
1224
  const data = inputData ?? await ev.parseBody();
@@ -1685,4 +1683,4 @@ var GetForm = /*#__PURE__*/ componentQrl(/*#__PURE__*/ inlinedQrl((_rawProps) =>
1685
1683
  })
1686
1684
  ], 1, "r5_3");
1687
1685
  }, "DocumentHeadTags_component_9CrWYOoCpgY"));
1688
- export { DocumentHeadTags, ErrorBoundary, Form, Link, QWIK_CITY_SCROLLER, QWIK_ROUTER_SCROLLER, QwikCityMockProvider, QwikCityProvider, QwikRouterMockProvider, QwikRouterProvider, RouterOutlet, ServiceWorkerRegister, createRenderer, getRequestEvent, globalAction$, globalActionQrl, omitProps, routeAction$, routeActionQrl, routeLoader$, routeLoaderQrl, server$, serverQrl, untypedAppUrl, useContent, useDocumentHead, useHttpStatus, useLocation, useNavigate, usePreventNavigate$, usePreventNavigateQrl, useQwikRouter, valibot$, valibotQrl, validator$, validatorQrl, z, zod$, zodQrl };
1686
+ export { DocumentHeadTags, Form, Link, QWIK_CITY_SCROLLER, QWIK_ROUTER_SCROLLER, QwikCityMockProvider, QwikCityProvider, QwikRouterMockProvider, QwikRouterProvider, RouterOutlet, ServiceWorkerRegister, createRenderer, getRequestEvent, globalAction$, globalActionQrl, omitProps, routeAction$, routeActionQrl, routeLoader$, routeLoaderQrl, server$, serverQrl, untypedAppUrl, useContent, useDocumentHead, useHttpStatus, useLocation, useNavigate, usePreventNavigate$, usePreventNavigateQrl, useQwikRouter, valibot$, valibotQrl, validator$, validatorQrl, z, zod$, zodQrl };
@@ -28,11 +28,16 @@ import { _TextEncoderStream_polyfill, getErrorHtml, isStaticPath, mergeHeadersCo
28
28
  return env[key];
29
29
  } },
30
30
  getWritableStream: (status, headers, cookies, resolve) => {
31
- const { readable, writable } = new TransformStream();
32
- resolve(new Response(readable, {
31
+ const responseInit = {
33
32
  status,
34
33
  headers: mergeHeadersCookies(headers, cookies)
35
- }));
34
+ };
35
+ if (status === 204 || status === 205 || status === 304) {
36
+ resolve(new Response(null, responseInit));
37
+ return new WritableStream();
38
+ }
39
+ const { readable, writable } = new TransformStream();
40
+ resolve(new Response(readable, responseInit));
36
41
  return writable;
37
42
  },
38
43
  getClientConn: () => {
@@ -2,7 +2,7 @@ import { Y as _asyncRequestStore, d as loadRoute } from "../../chunks/head.qwik.
2
2
  import { c as resolveValidInternalFullPathname, l as trimInternalPathname, s as recognizeRequest, u as trimRecognizedInternalPathname } from "../../chunks/request-path.mjs";
3
3
  import { i as RedirectMessage, r as AbortMessage, t as ServerError } from "../../chunks/server-error.mjs";
4
4
  import { t as devPreloadedRouteLoaders } from "../../chunks/dev-preloaded-route-loader.mjs";
5
- import { a as clearLoaderCache, c as mergeHeadersCookies, i as resolveRequestHandlers, n as getErrorHtml, o as clearSsrCache, r as renderQwikMiddleware, s as RewriteMessage, t as runQwikRouter } from "../../chunks/user-response.mjs";
5
+ import { a as isStaticPath, c as RewriteMessage, i as resolveRequestHandlers, l as mergeHeadersCookies, n as getErrorHtml, o as clearLoaderCache, r as renderQwikMiddleware, s as clearSsrCache, t as runQwikRouter } from "../../chunks/user-response.mjs";
6
6
  import { isDev } from "@qwik.dev/core";
7
7
  var qwikRouterConfig;
8
8
  async function getConfig() {
@@ -46,18 +46,6 @@ async function loadRequestHandlers(qwikRouterConfig, pathname, method, checkOrig
46
46
  };
47
47
  return runQwikRouter(serverRequestEv, loadedRoute, requestHandlers, rebuildRouteInfo, config.basePathname);
48
48
  }
49
- var staticPaths = /* @__PURE__ */ new Set(["__QWIK_ROUTER_STATIC_PATHS_ARRAY__"]);
50
- /**
51
- * Decide whether a given request path is a static path, for optimizing file access.
52
- *
53
- * @internal
54
- */ function isStaticPath(method, url) {
55
- if (method.toUpperCase() !== "GET") return false;
56
- const p = url.pathname;
57
- if (p.startsWith("/" + (globalThis.__QWIK_BUILD_DIR__ || "build") + "/")) return true;
58
- if (p.startsWith("/" + (globalThis.__QWIK_ASSETS_DIR__ || "assets") + "/")) return true;
59
- return staticPaths.has(p);
60
- }
61
49
  /**
62
50
  * TextEncoderStream polyfill based on Node.js' implementation
63
51
  * https://github.com/nodejs/node/blob/3f3226c8e363a5f06c1e6a37abd59b6b8c1923f1/lib/internal/webstreams/encoding.js#L38-L119
@@ -1,14 +1,14 @@
1
1
  import { r as isSameOriginUrl, t as ensureSlash } from "../chunks/pathname.mjs";
2
2
  import { c as resolveValidInternalFullPathname, s as recognizeRequest, u as trimRecognizedInternalPathname } from "../chunks/request-path.mjs";
3
3
  import { n as addWarning, t as addError } from "../chunks/format.mjs";
4
- import { _ as normalizePath$1, a as getPathnameFromDirPath, b as removeExtension, c as isIndexModule, d as isMenuFileName, f as isModuleExt, g as isServiceWorkerName, h as isPluginModule, i as getMenuPathname, l as isLayoutModule, m as isPageModuleExt, n as errorBoundaryName, o as isEntryName, p as isPageExt, r as getExtension, s as isErrorName, t as createFileId, u as isMarkdownExt, y as parseRouteIndexName } from "../chunks/fs.mjs";
4
+ import { _ as normalizePath$1, a as getPathnameFromDirPath, b as parseRouteIndexName, c as isIndexModule, d as isMenuFileName, f as isModuleExt, g as isServiceWorkerName, h as isPluginModule, i as getMenuPathname, l as isLayoutModule, m as isPageModuleExt, n as errorBoundaryName, o as isEntryName, p as isPageExt, r as getExtension, s as isErrorName, t as createFileId, u as isMarkdownExt, v as normalizePathKey, x as removeExtension } from "../chunks/fs.mjs";
5
5
  import { t as devPreloadedRouteLoaders } from "../chunks/dev-preloaded-route-loader.mjs";
6
6
  import { t as createRouteTester } from "../chunks/routes.mjs";
7
7
  import fs, { existsSync } from "node:fs";
8
8
  import path, { basename, dirname, extname, isAbsolute, join, resolve } from "node:path";
9
9
  import { readFile } from "node:fs/promises";
10
10
  import { loadEnv, mergeConfig, normalizePath } from "vite";
11
- import { findExports, findStaticImports } from "mlly";
11
+ import { findExports, findStaticImports, parseStaticImport } from "mlly";
12
12
  import { marked } from "marked";
13
13
  import { createHash } from "node:crypto";
14
14
  import { SourceMapGenerator } from "source-map";
@@ -1093,7 +1093,7 @@ function getClientRouteBundleNames(qwikPlugin, r) {
1093
1093
  if (loadersByFile) {
1094
1094
  const nodeLoaderHashes = [];
1095
1095
  for (const filePath of routeLoaderFiles) {
1096
- const hashes = loadersByFile.get(filePath);
1096
+ const hashes = loadersByFile.get(normalizePathKey(filePath));
1097
1097
  if (hashes) nodeLoaderHashes.push(...hashes);
1098
1098
  }
1099
1099
  if (nodeLoaderHashes.length > 0) lines.push(`${nextIndent}_R: ${JSON.stringify(nodeLoaderHashes)},`);
@@ -2083,10 +2083,10 @@ var QWIK_ROUTER_SW_REGISTER = "@qwik-router-sw-register";
2083
2083
  var VIRTUAL_SERVER_FNS = "virtual:qwik-router-server-fns";
2084
2084
  /** Replace or strip `_R: "__LOADERS:path1|path2__"` placeholders in a bundle chunk. */ function replaceLoaderPlaceholders(code, loadersByFile) {
2085
2085
  return code.replace(/_R\s*:\s*(["'`])__LOADERS:([^"'`]+)__\1\s*,?/g, (_match, _q, paths) => {
2086
- const filePaths = paths.split("|");
2086
+ const filePaths = JSON.parse(`"${paths}"`).split("|");
2087
2087
  const hashes = [];
2088
2088
  for (const filePath of filePaths) {
2089
- const fileHashes = loadersByFile.get(filePath);
2089
+ const fileHashes = loadersByFile.get(normalizePathKey(filePath));
2090
2090
  if (fileHashes) hashes.push(...fileHashes);
2091
2091
  }
2092
2092
  if (hashes.length > 0) return `_R: ${JSON.stringify(hashes)},`;
@@ -2094,7 +2094,7 @@ var VIRTUAL_SERVER_FNS = "virtual:qwik-router-server-fns";
2094
2094
  });
2095
2095
  }
2096
2096
  function addRouteLoaderHash(loadersByFile, filePath, hash) {
2097
- const normalizedPath = normalizePath$1(filePath);
2097
+ const normalizedPath = normalizePathKey(filePath).split(/[?#]/, 1)[0];
2098
2098
  const existing = loadersByFile.get(normalizedPath);
2099
2099
  if (!existing) {
2100
2100
  loadersByFile.set(normalizedPath, [hash]);
@@ -2107,7 +2107,7 @@ function addRouteLoaderHash(loadersByFile, filePath, hash) {
2107
2107
  return false;
2108
2108
  }
2109
2109
  function clearRouteLoaderHashes(loadersByFile, filePath) {
2110
- return loadersByFile.delete(normalizePath$1(filePath));
2110
+ return loadersByFile.delete(normalizePathKey(filePath).split(/[?#]/, 1)[0]);
2111
2111
  }
2112
2112
  function isRouterSourceFilePath(filePath) {
2113
2113
  const fileName = basename(filePath);
@@ -2131,7 +2131,7 @@ function collectRouteLoaderOwners(node, owners) {
2131
2131
  for (const file of node._files) if (file.type === "layout" || file.type === "route") owners.add(file.filePath);
2132
2132
  for (const child of node.children.values()) collectRouteLoaderOwners(child, owners);
2133
2133
  }
2134
- async function collectReExportSources(owner, filePath, resolveSource, sources, seen = /* @__PURE__ */ new Set([owner])) {
2134
+ async function collectRouteLoaderSources(owner, filePath, resolveSource, sources, seen = /* @__PURE__ */ new Set([owner])) {
2135
2135
  let code, exportEntries;
2136
2136
  try {
2137
2137
  code = await readFile(filePath, "utf-8");
@@ -2140,26 +2140,38 @@ async function collectReExportSources(owner, filePath, resolveSource, sources, s
2140
2140
  } catch {
2141
2141
  return;
2142
2142
  }
2143
- await Promise.all(exportEntries.map(async (exp) => {
2144
- if (!exp.specifier) return;
2145
- const resolved = await resolveSource(exp.specifier, filePath);
2146
- if (!resolved || resolved.charCodeAt(0) === 0) return;
2143
+ const importedFactories = /* @__PURE__ */ new Map();
2144
+ for (const entry of findStaticImports(code)) {
2145
+ const parsed = parseStaticImport(entry);
2146
+ if (parsed.defaultImport) importedFactories.set(parsed.defaultImport, entry.specifier);
2147
+ if (parsed.namedImports) for (const localName of Object.values(parsed.namedImports)) importedFactories.set(localName, entry.specifier);
2148
+ }
2149
+ const specifiers = /* @__PURE__ */ new Set();
2150
+ for (const exp of exportEntries) if (exp.specifier) specifiers.add(exp.specifier);
2151
+ else if (/^export\s+(?:const|let|var)\s*{/.test(exp.code)) {
2152
+ const factoryName = code.slice(exp.end).match(/^\s*([\w$]+)(?:\s*<[^;()]*>)?\s*\(/)?.[1];
2153
+ const specifier = factoryName && importedFactories.get(factoryName);
2154
+ if (specifier) specifiers.add(specifier);
2155
+ }
2156
+ for (const specifier of specifiers) {
2157
+ const resolved = await resolveSource(specifier, filePath);
2158
+ if (!resolved || resolved.charCodeAt(0) === 0) continue;
2147
2159
  const resolvedPath = normalizePath$1(resolved.split(/[?#]/, 1)[0]);
2148
2160
  let ownerSources = sources.get(owner);
2149
2161
  if (!ownerSources) sources.set(owner, ownerSources = []);
2150
2162
  if (!ownerSources.includes(resolvedPath)) ownerSources.push(resolvedPath);
2151
2163
  if (!seen.has(resolvedPath)) {
2152
2164
  seen.add(resolvedPath);
2153
- await collectReExportSources(owner, resolvedPath, resolveSource, sources, seen);
2165
+ await collectRouteLoaderSources(owner, resolvedPath, resolveSource, sources, seen);
2154
2166
  }
2155
- }));
2167
+ }
2156
2168
  }
2157
2169
  async function findRouteLoaderSourceFiles(ctx, resolveSource) {
2158
2170
  const sources = /* @__PURE__ */ new Map();
2159
2171
  const owners = /* @__PURE__ */ new Set();
2160
2172
  collectRouteLoaderOwners(ctx.routeTrie, owners);
2161
2173
  for (const plugin of ctx.serverPlugins) owners.add(plugin.filePath);
2162
- await Promise.all([...owners].map((owner) => collectReExportSources(owner, owner, resolveSource, sources)));
2174
+ await Promise.all([...owners].map((owner) => collectRouteLoaderSources(owner, owner, resolveSource, sources)));
2163
2175
  return sources;
2164
2176
  }
2165
2177
  function invalidateRouterConfigModules(server) {
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.40",
4
+ "version": "2.0.0-beta.42",
5
5
  "bugs": "https://github.com/QwikDev/qwik/issues",
6
6
  "dependencies": {
7
7
  "@azure/functions": "3.5.1",
@@ -41,7 +41,7 @@
41
41
  "typescript": "5.9.3",
42
42
  "uvu": "0.5.6",
43
43
  "vite": "8.2.1",
44
- "@qwik.dev/core": "2.0.0-beta.40"
44
+ "@qwik.dev/core": "2.0.0-beta.42"
45
45
  },
46
46
  "engines": {
47
47
  "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
@@ -147,10 +147,6 @@
147
147
  "types": "./lib/vite/index.d.ts",
148
148
  "import": "./lib/vite/index.mjs"
149
149
  },
150
- "./service-worker": {
151
- "types": "./service-worker.d.ts",
152
- "import": "./lib/service-worker.mjs"
153
- },
154
150
  "./package.json": "./package.json"
155
151
  },
156
152
  "files": [
@@ -170,7 +166,7 @@
170
166
  "main": "./lib/index.qwik.mjs",
171
167
  "peerDependencies": {
172
168
  "vite": ">=8 <9",
173
- "@qwik.dev/core": "^2.0.0-beta.40"
169
+ "@qwik.dev/core": "2.0.0-beta.42"
174
170
  },
175
171
  "publishConfig": {
176
172
  "access": "public"