@qwik.dev/router 2.0.0-beta.36 → 2.0.0-beta.38

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 (41) hide show
  1. package/lib/adapters/cloudflare-pages/vite/index.mjs +3 -5
  2. package/lib/adapters/shared/vite/index.d.ts +3 -4
  3. package/lib/adapters/shared/vite/index.mjs +167 -155
  4. package/lib/chunks/dev-preloaded-route-loader.mjs +3 -0
  5. package/lib/chunks/format.mjs +31 -0
  6. package/lib/chunks/fs.mjs +10 -8
  7. package/lib/chunks/head.qwik.mjs +1425 -0
  8. package/lib/chunks/http-error.qwik.mjs +2 -2
  9. package/lib/chunks/pathname.mjs +3 -32
  10. package/lib/chunks/request-body-limit.mjs +37 -0
  11. package/lib/chunks/request-path.mjs +60 -0
  12. package/lib/chunks/routes.mjs +47 -0
  13. package/lib/chunks/server-error.mjs +19 -0
  14. package/lib/chunks/system.mjs +8 -8
  15. package/lib/chunks/use-functions.qwik.mjs +3 -12
  16. package/lib/chunks/user-response.mjs +1875 -0
  17. package/lib/chunks/worker-thread.mjs +317 -0
  18. package/lib/index.d.ts +251 -44
  19. package/lib/index.qwik.mjs +734 -485
  20. package/lib/middleware/aws-lambda/index.mjs +2 -1
  21. package/lib/middleware/azure-swa/index.mjs +13 -9
  22. package/lib/middleware/cloudflare-pages/index.mjs +2 -2
  23. package/lib/middleware/deno/index.d.ts +2 -0
  24. package/lib/middleware/deno/index.mjs +2 -1
  25. package/lib/middleware/netlify-edge/index.mjs +2 -2
  26. package/lib/middleware/node/index.d.ts +9 -1
  27. package/lib/middleware/node/index.mjs +28 -3
  28. package/lib/middleware/request-handler/index.d.ts +65 -26
  29. package/lib/middleware/request-handler/index.mjs +38 -1679
  30. package/lib/middleware/vercel-edge/index.mjs +2 -2
  31. package/lib/ssg/index.d.ts +5 -13
  32. package/lib/ssg/index.mjs +14 -53
  33. package/lib/vite/index.d.ts +23 -0
  34. package/lib/vite/index.mjs +533 -122
  35. package/package.json +4 -3
  36. package/lib/chunks/deepFreeze.qwik.mjs +0 -18
  37. package/lib/chunks/error-handler.mjs +0 -57
  38. package/lib/chunks/not-found-wrapper.qwik.mjs +0 -25
  39. package/lib/chunks/redirect-handler.mjs +0 -6
  40. package/lib/chunks/routing.qwik.mjs +0 -821
  41. package/lib/chunks/worker-thread.qwik.mjs +0 -2585
@@ -1,8 +1,12 @@
1
1
  import { mergeConfig, normalizePath as normalizePath$1, loadEnv } from 'vite';
2
2
  import fs, { existsSync } from 'node:fs';
3
+ import { readFile } from 'node:fs/promises';
3
4
  import path, { join, dirname, basename, resolve, isAbsolute, extname } from 'node:path';
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';
5
- import { i as isSameOriginUrl, a as addError, b as addWarning } from '../chunks/pathname.mjs';
5
+ import { findExports, findStaticImports } from 'mlly';
6
+ import { g as getExtension, r as removeExtension, i as isIndexModule, a as isErrorName, b as isPageModuleExt, c as isModuleExt, d as isMarkdownExt, e as isLayoutModule, f as isEntryName, h as isMenuFileName, j as isServiceWorkerName, k as normalizePath, l as getPathnameFromDirPath, m as getMenuPathname, o as createFileId, p as parseRouteIndexName, q as errorBoundaryName, s as isPluginModule, t as isPageExt } from '../chunks/fs.mjs';
7
+ import { a as addError, b as addWarning } from '../chunks/format.mjs';
8
+ import { i as isSameOriginUrl, e as ensureSlash } from '../chunks/pathname.mjs';
9
+ import { c as createRouteTester } from '../chunks/routes.mjs';
6
10
  import { marked } from 'marked';
7
11
  import { createHash } from 'node:crypto';
8
12
  import { SourceMapGenerator } from 'source-map';
@@ -15,6 +19,8 @@ import { toString } from 'hast-util-to-string';
15
19
  import { refractor } from 'refractor';
16
20
  import tsxLang from 'refractor/lang/tsx.js';
17
21
  import { optimize } from 'svgo';
22
+ import { r as recognizeRequest, I as IsQLoader, t as trimRecognizedInternalPathname, a as resolveValidInternalFullPathname } from '../chunks/request-path.mjs';
23
+ import { d as devPreloadedRouteLoaders } from '../chunks/dev-preloaded-route-loader.mjs';
18
24
 
19
25
  function extendConfig(baseConfigExport, serverConfigExport) {
20
26
  return async (env) => {
@@ -96,7 +102,7 @@ The link "${url}", found within "${containingFilePath}" does not have a matching
96
102
  url = url.slice(0, -1);
97
103
  }
98
104
  } else if (!globalThis.__NO_TRAILING_SLASH__) {
99
- url += "/";
105
+ url = ensureSlash(url);
100
106
  }
101
107
  }
102
108
  return url;
@@ -262,43 +268,6 @@ function parseRoutePathname(basePathname, pathname) {
262
268
  };
263
269
  }
264
270
 
265
- function routeSortCompare(a, b) {
266
- const maxSegments = Math.max(a.segments.length, b.segments.length);
267
- for (let i = 0; i < maxSegments; i += 1) {
268
- const sa = a.segments[i];
269
- const sb = b.segments[i];
270
- if (!sa) {
271
- return a.pathname.includes("[...") ? 1 : -1;
272
- }
273
- if (!sb) {
274
- return b.pathname.includes("[...") ? -1 : 1;
275
- }
276
- const maxParts = Math.max(sa.length, sb.length);
277
- for (let i2 = 0; i2 < maxParts; i2 += 1) {
278
- const pa = sa[i2];
279
- const pb = sb[i2];
280
- if (pa === void 0) {
281
- return pb.dynamic ? -1 : 1;
282
- }
283
- if (pb === void 0) {
284
- return pa.dynamic ? 1 : -1;
285
- }
286
- if (pa.dynamic !== pb.dynamic) {
287
- return pa.dynamic ? 1 : -1;
288
- }
289
- if (pa.dynamic) {
290
- if (pa.rest !== pb.rest) {
291
- return pa.rest ? 1 : -1;
292
- }
293
- }
294
- }
295
- }
296
- if (a.pathname === b.pathname) {
297
- return a.ext > b.ext ? -1 : 1;
298
- }
299
- return a.pathname < b.pathname ? -1 : 1;
300
- }
301
-
302
271
  const LAYOUT_ID = "layout";
303
272
  const LAYOUT_NAMED_PREFIX = LAYOUT_ID + "-";
304
273
  const LAYOUT_TOP_SUFFIX = "!";
@@ -334,8 +303,9 @@ function resolveRoute(opts, appLayouts, sourceFile) {
334
303
  const routesDir = opts.routesDir;
335
304
  const { layoutName, layoutStop } = parseRouteIndexName(sourceFile.extlessName);
336
305
  let pathname = getPathnameFromDirPath(opts, sourceFile.dirPath);
337
- if (sourceFile.extlessName === "404" || sourceFile.extlessName === "error") {
338
- pathname += sourceFile.extlessName + ".html";
306
+ const boundary = errorBoundaryName(sourceFile.extlessName);
307
+ if (boundary) {
308
+ pathname += boundary + ".html";
339
309
  }
340
310
  if (!layoutStop) {
341
311
  let currentDir = normalizePath(dirname(filePath));
@@ -373,6 +343,43 @@ function resolveRoute(opts, appLayouts, sourceFile) {
373
343
  };
374
344
  }
375
345
 
346
+ function routeSortCompare(a, b) {
347
+ const maxSegments = Math.max(a.segments.length, b.segments.length);
348
+ for (let i = 0; i < maxSegments; i += 1) {
349
+ const sa = a.segments[i];
350
+ const sb = b.segments[i];
351
+ if (!sa) {
352
+ return a.pathname.includes("[...") ? 1 : -1;
353
+ }
354
+ if (!sb) {
355
+ return b.pathname.includes("[...") ? -1 : 1;
356
+ }
357
+ const maxParts = Math.max(sa.length, sb.length);
358
+ for (let i2 = 0; i2 < maxParts; i2 += 1) {
359
+ const pa = sa[i2];
360
+ const pb = sb[i2];
361
+ if (pa === void 0) {
362
+ return pb.dynamic ? -1 : 1;
363
+ }
364
+ if (pb === void 0) {
365
+ return pa.dynamic ? 1 : -1;
366
+ }
367
+ if (pa.dynamic !== pb.dynamic) {
368
+ return pa.dynamic ? 1 : -1;
369
+ }
370
+ if (pa.dynamic) {
371
+ if (pa.rest !== pb.rest) {
372
+ return pa.rest ? 1 : -1;
373
+ }
374
+ }
375
+ }
376
+ }
377
+ if (a.pathname === b.pathname) {
378
+ return a.ext > b.ext ? -1 : 1;
379
+ }
380
+ return a.pathname < b.pathname ? -1 : 1;
381
+ }
382
+
376
383
  function parseDirName(name) {
377
384
  if (name.startsWith("(") && name.endsWith(")")) {
378
385
  return {
@@ -411,7 +418,6 @@ async function walkRouteDir(node, dirPath) {
411
418
  if (!child) {
412
419
  child = {
413
420
  _files: [],
414
- _dirPath: itemPath,
415
421
  children: /* @__PURE__ */ new Map()
416
422
  };
417
423
  node.children.set(itemName, child);
@@ -422,7 +428,6 @@ async function walkRouteDir(node, dirPath) {
422
428
  if (!child) {
423
429
  child = {
424
430
  _files: [],
425
- _dirPath: itemPath,
426
431
  children: /* @__PURE__ */ new Map()
427
432
  };
428
433
  if (parsed.paramName) {
@@ -456,7 +461,6 @@ async function walkRoutes(routesDir) {
456
461
  const dirPath = normalizePath(routesDir);
457
462
  const root = {
458
463
  _files: [],
459
- _dirPath: dirPath,
460
464
  children: /* @__PURE__ */ new Map()
461
465
  };
462
466
  await walkRouteDir(root, dirPath);
@@ -569,7 +573,7 @@ function deriveFromTrie(opts, root) {
569
573
  function translateRoute(route, config, configIndex) {
570
574
  const replacePath = (part) => (config.paths || {})[part] ?? part;
571
575
  const pathnamePrefix = config.prefix ? "/" + config.prefix : "";
572
- const routeNamePrefix = config.prefix ? config.prefix + "/" : "";
576
+ const routeNamePrefix = config.prefix ? ensureSlash(config.prefix) : "";
573
577
  const idSuffix = config.prefix?.toUpperCase().replace(/-/g, "");
574
578
  const patternInfix = config.prefix ? [
575
579
  config.prefix
@@ -641,7 +645,7 @@ function rewriteRoutes(ctx, routes) {
641
645
  function applyRewriteRoutes(root, rewriteConfigs) {
642
646
  const routables = [];
643
647
  function walk(node, steps) {
644
- const hasRoute = node._files.some((f) => f.type === "route" && f.extlessName !== "error" && f.extlessName !== "404");
648
+ const hasRoute = node._files.some((f) => f.type === "route" && !isErrorName(f.extlessName));
645
649
  if (hasRoute) {
646
650
  routables.push({
647
651
  steps: [
@@ -671,7 +675,12 @@ function applyRewriteRoutes(root, rewriteConfigs) {
671
675
  for (const config of rewriteConfigs) {
672
676
  const translations = config.paths || {};
673
677
  const translatable = new Set(Object.keys(translations).map((k) => k.toLowerCase()));
678
+ const isExcluded = config.exclude?.length ? createRouteTester("/", config.exclude, void 0) : void 0;
674
679
  for (const { steps } of routables) {
680
+ const originalKeyPath = steps.map((s) => s.key).join("/");
681
+ if (isExcluded?.("/" + originalKeyPath)) {
682
+ continue;
683
+ }
675
684
  const hasTranslatable = steps.some((s) => translatable.has(s.key));
676
685
  if (!hasTranslatable && !config.prefix) {
677
686
  continue;
@@ -679,7 +688,6 @@ function applyRewriteRoutes(root, rewriteConfigs) {
679
688
  if (steps.length === 0 && !config.prefix) {
680
689
  continue;
681
690
  }
682
- const originalKeyPath = steps.map((s) => s.key).join("/");
683
691
  const translatedSteps = [];
684
692
  if (config.prefix) {
685
693
  translatedSteps.push({
@@ -703,7 +711,6 @@ function applyRewriteRoutes(root, rewriteConfigs) {
703
711
  if (!child) {
704
712
  child = {
705
713
  _files: [],
706
- _dirPath: "",
707
714
  children: /* @__PURE__ */ new Map()
708
715
  };
709
716
  if (step.paramName) {
@@ -788,7 +795,7 @@ function normalizeOptions(rootDir, viteBasePath, userOpts) {
788
795
  if (!(viteBasePath.startsWith("/") && viteBasePath.endsWith("/"))) {
789
796
  console.error(`warning: vite's config.base must begin and end with /. This will be an error in v2. If you have a valid use case, please open an issue.`);
790
797
  if (!viteBasePath.endsWith("/")) {
791
- viteBasePath += "/";
798
+ viteBasePath = ensureSlash(viteBasePath);
792
799
  }
793
800
  }
794
801
  const opts = {
@@ -814,12 +821,15 @@ function normalizeOptions(rootDir, viteBasePath, userOpts) {
814
821
  }
815
822
  if (!opts.basePathname.endsWith("/")) {
816
823
  console.error(`Warning: qwik-router plugin basePathname must end with /. This will be an error in v2`);
817
- opts.basePathname += "/";
824
+ opts.basePathname = ensureSlash(opts.basePathname);
818
825
  }
819
826
  const url = new URL(opts.basePathname, "https://qwik.dev/");
820
827
  opts.basePathname = url.pathname;
821
828
  opts.mdx = opts.mdx || {};
822
829
  opts.platform = opts.platform || {};
830
+ if (opts.strictLoaders === void 0) {
831
+ opts.strictLoaders = true;
832
+ }
823
833
  return opts;
824
834
  }
825
835
  function createBuildContext(rootDir, viteBasePath, userOpts, target, dynamicImports) {
@@ -828,7 +838,6 @@ function createBuildContext(rootDir, viteBasePath, userOpts, target, dynamicImpo
828
838
  opts: normalizeOptions(rootDir, viteBasePath, userOpts),
829
839
  routeTrie: {
830
840
  _files: [],
831
- _dirPath: "",
832
841
  children: /* @__PURE__ */ new Map()
833
842
  },
834
843
  routes: [],
@@ -1286,6 +1295,20 @@ function resolveNamedLayoutChain(ancestorLayouts, nodeLayouts, targetName) {
1286
1295
  }
1287
1296
  return result;
1288
1297
  }
1298
+ function buildLoaderChainExpr(extlessName, loaderExpr, ancestorLayouts, nodeLayouts) {
1299
+ const { layoutName, layoutStop } = parseRouteIndexName(extlessName);
1300
+ if (layoutStop) {
1301
+ return `[ ${loaderExpr} ]`;
1302
+ }
1303
+ if (layoutName) {
1304
+ const chain = resolveNamedLayoutChain(ancestorLayouts, nodeLayouts, layoutName);
1305
+ return `[ ${[
1306
+ ...chain.map((l) => l.id),
1307
+ loaderExpr
1308
+ ].join(", ")} ]`;
1309
+ }
1310
+ return loaderExpr;
1311
+ }
1289
1312
  function getClientRouteBundleNames(qwikPlugin, r) {
1290
1313
  const bundlesNames = [];
1291
1314
  const manifest = qwikPlugin.api.getManifest();
@@ -1314,7 +1337,7 @@ function getClientRouteBundleNames(qwikPlugin, r) {
1314
1337
  }
1315
1338
  return bundlesNames;
1316
1339
  }
1317
- function serializeBuildTrie(ctx, qwikPlugin, node, layoutIdMap, routeIdMap, menuIdMap, errorFiles, notFoundFiles, ancestorLayouts, isSSR, indent) {
1340
+ function serializeBuildTrie(ctx, qwikPlugin, node, layoutIdMap, routeIdMap, menuIdMap, ancestorLayouts, isSSR, indent, loadersByFile, routeLoaderSourceFiles) {
1318
1341
  const lines = [];
1319
1342
  const nextIndent = indent + " ";
1320
1343
  if (node._P) {
@@ -1331,7 +1354,6 @@ function serializeBuildTrie(ctx, qwikPlugin, node, layoutIdMap, routeIdMap, menu
1331
1354
  }
1332
1355
  let layoutExpr;
1333
1356
  let indexExpr;
1334
- let indexIsOverride = false;
1335
1357
  let errorExpr;
1336
1358
  let notFoundExpr;
1337
1359
  let menuExpr;
@@ -1372,28 +1394,14 @@ function serializeBuildTrie(ctx, qwikPlugin, node, layoutIdMap, routeIdMap, menu
1372
1394
  if (!loaderExpr) {
1373
1395
  continue;
1374
1396
  }
1375
- const isError = file.extlessName === "error";
1376
- const is404 = file.extlessName === "404";
1377
- if (isError) {
1378
- errorExpr = loaderExpr;
1379
- errorFiles.set(node._dirPath, file.filePath);
1380
- } else if (is404) {
1381
- notFoundExpr = loaderExpr;
1382
- notFoundFiles.set(node._dirPath, file.filePath);
1397
+ const expr = buildLoaderChainExpr(file.extlessName, loaderExpr, ancestorLayouts, nodeLayouts);
1398
+ const boundary = errorBoundaryName(file.extlessName);
1399
+ if (boundary === "404") {
1400
+ notFoundExpr = expr;
1401
+ } else if (boundary === "error") {
1402
+ errorExpr = expr;
1383
1403
  } else {
1384
- const { layoutName, layoutStop } = parseRouteIndexName(file.extlessName);
1385
- if (layoutStop) {
1386
- indexExpr = `[ ${loaderExpr} ]`;
1387
- indexIsOverride = true;
1388
- } else if (layoutName) {
1389
- const chain = resolveNamedLayoutChain(ancestorLayouts, nodeLayouts, layoutName);
1390
- const chainExprs = chain.map((l) => l.id);
1391
- chainExprs.push(loaderExpr);
1392
- indexExpr = `[ ${chainExprs.join(", ")} ]`;
1393
- indexIsOverride = true;
1394
- } else {
1395
- indexExpr = loaderExpr;
1396
- }
1404
+ indexExpr = expr;
1397
1405
  bundleRoute = ctx.routes.find((r) => r.filePath === file.filePath);
1398
1406
  }
1399
1407
  }
@@ -1401,11 +1409,7 @@ function serializeBuildTrie(ctx, qwikPlugin, node, layoutIdMap, routeIdMap, menu
1401
1409
  lines.push(`${nextIndent}_L: ${layoutExpr},`);
1402
1410
  }
1403
1411
  if (indexExpr) {
1404
- if (indexIsOverride) {
1405
- lines.push(`${nextIndent}_I: ${indexExpr},`);
1406
- } else {
1407
- lines.push(`${nextIndent}_I: ${indexExpr},`);
1408
- }
1412
+ lines.push(`${nextIndent}_I: ${indexExpr},`);
1409
1413
  if (isSSR && bundleRoute) {
1410
1414
  const bundleNames = getClientRouteBundleNames(qwikPlugin, bundleRoute);
1411
1415
  if (bundleNames.length > 0) {
@@ -1413,6 +1417,37 @@ function serializeBuildTrie(ctx, qwikPlugin, node, layoutIdMap, routeIdMap, menu
1413
1417
  }
1414
1418
  }
1415
1419
  }
1420
+ {
1421
+ const routeFiles = [];
1422
+ for (const file of node._files.filter((f) => f.type === "layout" || f.type === "route" && routeIdMap.has(f.filePath))) {
1423
+ routeFiles.push(file.filePath, ...routeLoaderSourceFiles?.get(file.filePath) ?? []);
1424
+ }
1425
+ if (node === ctx.routeTrie) {
1426
+ for (const plugin of ctx.serverPlugins) {
1427
+ routeFiles.push(plugin.filePath, ...routeLoaderSourceFiles?.get(plugin.filePath) ?? []);
1428
+ }
1429
+ }
1430
+ const routeLoaderFiles = [
1431
+ ...new Set(routeFiles)
1432
+ ];
1433
+ if (routeLoaderFiles.length > 0) {
1434
+ if (loadersByFile) {
1435
+ const nodeLoaderHashes = [];
1436
+ for (const filePath of routeLoaderFiles) {
1437
+ const hashes = loadersByFile.get(filePath);
1438
+ if (hashes) {
1439
+ nodeLoaderHashes.push(...hashes);
1440
+ }
1441
+ }
1442
+ if (nodeLoaderHashes.length > 0) {
1443
+ lines.push(`${nextIndent}_R: ${JSON.stringify(nodeLoaderHashes)},`);
1444
+ }
1445
+ } else {
1446
+ const placeholder = `__LOADERS:${routeLoaderFiles.join("|")}__`;
1447
+ lines.push(`${nextIndent}_R: ${JSON.stringify(placeholder)},`);
1448
+ }
1449
+ }
1450
+ }
1416
1451
  if (errorExpr) {
1417
1452
  lines.push(`${nextIndent}_E: ${errorExpr},`);
1418
1453
  }
@@ -1445,7 +1480,7 @@ function serializeBuildTrie(ctx, qwikPlugin, node, layoutIdMap, routeIdMap, menu
1445
1480
  groupChildren.sort((a, b) => a[0].localeCompare(b[0]));
1446
1481
  const groupStrs = [];
1447
1482
  for (const [_key, child] of groupChildren) {
1448
- const childStr = serializeBuildTrie(ctx, qwikPlugin, child, layoutIdMap, routeIdMap, menuIdMap, errorFiles, notFoundFiles, childAncestors, isSSR, nextIndent);
1483
+ const childStr = serializeBuildTrie(ctx, qwikPlugin, child, layoutIdMap, routeIdMap, menuIdMap, childAncestors, isSSR, nextIndent, loadersByFile, routeLoaderSourceFiles);
1449
1484
  if (childStr !== "{}") {
1450
1485
  groupStrs.push(childStr);
1451
1486
  }
@@ -1455,7 +1490,7 @@ function serializeBuildTrie(ctx, qwikPlugin, node, layoutIdMap, routeIdMap, menu
1455
1490
  }
1456
1491
  }
1457
1492
  for (const [key, child] of regularChildren) {
1458
- const childStr = serializeBuildTrie(ctx, qwikPlugin, child, layoutIdMap, routeIdMap, menuIdMap, errorFiles, notFoundFiles, childAncestors, isSSR, nextIndent);
1493
+ const childStr = serializeBuildTrie(ctx, qwikPlugin, child, layoutIdMap, routeIdMap, menuIdMap, childAncestors, isSSR, nextIndent, loadersByFile, routeLoaderSourceFiles);
1459
1494
  if (childStr !== "{}") {
1460
1495
  const keyStr = JSON.stringify(key);
1461
1496
  lines.push(`${nextIndent}${keyStr}: ${childStr},`);
@@ -1468,14 +1503,12 @@ function serializeBuildTrie(ctx, qwikPlugin, node, layoutIdMap, routeIdMap, menu
1468
1503
  ${lines.join("\n")}
1469
1504
  ${indent}}`;
1470
1505
  }
1471
- function createRoutes(ctx, qwikPlugin, c, esmImports, isSSR) {
1506
+ function createRoutes(ctx, qwikPlugin, c, esmImports, isSSR, loadersByFile, serverExcludePaths, routeLoaderSourceFiles) {
1472
1507
  const includeEndpoints = isSSR;
1473
1508
  const dynamicImports = ctx.dynamicImports;
1474
1509
  const layoutIdMap = /* @__PURE__ */ new Map();
1475
1510
  const routeIdMap = /* @__PURE__ */ new Map();
1476
1511
  const menuIdMap = /* @__PURE__ */ new Map();
1477
- const errorFiles = /* @__PURE__ */ new Map();
1478
- const notFoundFiles = /* @__PURE__ */ new Map();
1479
1512
  let layoutCount = 0;
1480
1513
  let routeCount = 0;
1481
1514
  let menuCount = 0;
@@ -1527,6 +1560,9 @@ function createRoutes(ctx, qwikPlugin, c, esmImports, isSSR) {
1527
1560
  /** Qwik Router Routes (${routeCount}) */`);
1528
1561
  for (const route of ctx.routes) {
1529
1562
  if (isPageExt(route.ext)) {
1563
+ if (serverExcludePaths?.has(route.filePath)) {
1564
+ continue;
1565
+ }
1530
1566
  const importPath = getImportPath(route.filePath);
1531
1567
  let loaderExpr;
1532
1568
  if (dynamicImports) {
@@ -1542,7 +1578,7 @@ function createRoutes(ctx, qwikPlugin, c, esmImports, isSSR) {
1542
1578
  routeIdMap.set(route.filePath, `()=>${route.id}`);
1543
1579
  }
1544
1580
  }
1545
- const trieStr = serializeBuildTrie(ctx, qwikPlugin, ctx.routeTrie, layoutIdMap, routeIdMap, menuIdMap, errorFiles, notFoundFiles, [], isSSR, "");
1581
+ const trieStr = serializeBuildTrie(ctx, qwikPlugin, ctx.routeTrie, layoutIdMap, routeIdMap, menuIdMap, [], isSSR, "", loadersByFile, routeLoaderSourceFiles);
1546
1582
  const baseSegments = ctx.opts.basePathname.split("/").filter((s) => s.length > 0);
1547
1583
  let routesExpr = trieStr;
1548
1584
  for (let j = baseSegments.length - 1; j >= 0; j--) {
@@ -1567,7 +1603,7 @@ function createServerPlugins(ctx, _qwikPlugin, c, esmImports, isSSR) {
1567
1603
  c.push(`];`);
1568
1604
  }
1569
1605
 
1570
- function generateQwikRouterConfig(ctx, qwikPlugin, isSSR) {
1606
+ function generateQwikRouterConfig(ctx, qwikPlugin, isSSR, loadersByFile, serverExcludePaths, routeLoaderSourceFiles) {
1571
1607
  const esmImports = [];
1572
1608
  const c = [];
1573
1609
  c.push(`
@@ -1578,7 +1614,7 @@ import { isDev } from '@qwik.dev/core/build';`);
1578
1614
  esmImports.push(`import 'virtual:qwik-router-server-fns';`);
1579
1615
  }
1580
1616
  createServerPlugins(ctx, qwikPlugin, c, esmImports, isSSR);
1581
- createRoutes(ctx, qwikPlugin, c, esmImports, isSSR);
1617
+ createRoutes(ctx, qwikPlugin, c, esmImports, isSSR, loadersByFile, serverExcludePaths, routeLoaderSourceFiles);
1582
1618
  createEntries(ctx, c);
1583
1619
  c.push(`export const trailingSlash = ${JSON.stringify(!globalThis.__NO_TRAILING_SLASH__)};`);
1584
1620
  c.push(`export const basePathname = ${JSON.stringify(ctx.opts.basePathname)};`);
@@ -1604,6 +1640,76 @@ function generateServiceWorkerRegister(ctx, swRegister) {
1604
1640
  return `export default ${JSON.stringify(swReg)};`;
1605
1641
  }
1606
1642
 
1643
+ const NON_GET_HANDLERS = /* @__PURE__ */ new Set([
1644
+ "onRequest",
1645
+ "onPost",
1646
+ "onPut",
1647
+ "onPatch",
1648
+ "onDelete",
1649
+ "onHead",
1650
+ "onOptions"
1651
+ ]);
1652
+ const ROUTER_MODULE = "@qwik.dev/router";
1653
+ const SERVER_QRL = /\b(?:routeLoader|routeAction|globalAction|server)(?:\$|Qrl)/;
1654
+ function isServerFreeSource(code) {
1655
+ for (const e of findExports(code)) {
1656
+ if (e.specifier) {
1657
+ return false;
1658
+ }
1659
+ const names = [
1660
+ ...e.name ? [
1661
+ e.name
1662
+ ] : [],
1663
+ ...e.names ?? []
1664
+ ];
1665
+ if (names.some((n) => NON_GET_HANDLERS.has(n))) {
1666
+ return false;
1667
+ }
1668
+ }
1669
+ for (const imp of findStaticImports(code)) {
1670
+ if (imp.specifier === ROUTER_MODULE && SERVER_QRL.test(imp.code)) {
1671
+ return false;
1672
+ }
1673
+ }
1674
+ return true;
1675
+ }
1676
+ function isFileServerFree(filePath, cache) {
1677
+ let pending = cache.get(filePath);
1678
+ if (!pending) {
1679
+ pending = readFile(filePath, "utf-8").then(isServerFreeSource).catch(() => false);
1680
+ cache.set(filePath, pending);
1681
+ }
1682
+ return pending;
1683
+ }
1684
+ async function isRouteServerFree(route, cache) {
1685
+ if (!isMarkdownExt(route.ext) && !await isFileServerFree(route.filePath, cache)) {
1686
+ return false;
1687
+ }
1688
+ for (const layout of route.layouts) {
1689
+ if (!await isFileServerFree(layout.filePath, cache)) {
1690
+ return false;
1691
+ }
1692
+ }
1693
+ return true;
1694
+ }
1695
+ async function getServerExcludedRoutes(ctx, ssg) {
1696
+ const excluded = /* @__PURE__ */ new Set();
1697
+ if (!ssg?.include?.length) {
1698
+ return excluded;
1699
+ }
1700
+ const isPrerendered = createRouteTester(ctx.opts.basePathname, ssg.include, ssg.exclude);
1701
+ const serverFreeByFile = /* @__PURE__ */ new Map();
1702
+ await Promise.all(ctx.routes.map(async (route) => {
1703
+ if (!isPageExt(route.ext) || route.paramNames.length > 0 || /\/(?:404|error)\.html$/.test(route.pathname) || !isPrerendered(route.pathname)) {
1704
+ return;
1705
+ }
1706
+ if (await isRouteServerFree(route, serverFreeByFile)) {
1707
+ excluded.add(route.filePath);
1708
+ }
1709
+ }));
1710
+ return excluded;
1711
+ }
1712
+
1607
1713
  function isBundlePartOfRoute(bundle, routeAndLayoutPaths) {
1608
1714
  if (!bundle.origins) {
1609
1715
  return false;
@@ -1777,8 +1883,22 @@ function createVirtualImageJsxId(pathId, params) {
1777
1883
  const queryString = query.toString();
1778
1884
  return `${VIRTUAL_IMAGE_JSX_PREFIX}${normalizePath$1(pathId)}${VIRTUAL_IMAGE_JSX_SUFFIX}${queryString ? `?${queryString}` : ""}`;
1779
1885
  }
1780
- function createImageJsxImportId(pathId, params) {
1781
- const query = new URLSearchParams(params);
1886
+ function createImageJsxDirectives(params, userOpts) {
1887
+ const overrides = Object.fromEntries(params.entries());
1888
+ delete overrides[JSX_QUERY_PARAM];
1889
+ delete overrides[INTERNAL_IMAGE_JSX_QUERY_PARAM];
1890
+ return new URLSearchParams({
1891
+ format: "webp",
1892
+ quality: "75",
1893
+ w: "200;400;600;800;1200",
1894
+ withoutEnlargement: "",
1895
+ ...userOpts?.imageOptimization?.jsxDirectives,
1896
+ ...overrides,
1897
+ as: "jsx"
1898
+ });
1899
+ }
1900
+ function createImageJsxImportId(pathId, params, userOpts) {
1901
+ const query = createImageJsxDirectives(params, userOpts);
1782
1902
  query.set(INTERNAL_IMAGE_JSX_QUERY_PARAM, "");
1783
1903
  return `${normalizePath$1(pathId)}?${query.toString()}`;
1784
1904
  }
@@ -1806,9 +1926,10 @@ function getToImg() {
1806
1926
  : _jsxSplit("img", null, {decoding, height: h, loading, srcSet: s, width: w});
1807
1927
  `;
1808
1928
  }
1809
- function createImageJsxModule(pathId, params) {
1929
+ function createImageJsxModule(pathId, params, userOpts) {
1930
+ const importId = createImageJsxImportId(pathId, params, userOpts);
1810
1931
  return `
1811
- import { srcSet, width, height } from ${JSON.stringify(createImageJsxImportId(pathId, params))};
1932
+ import { srcSet, width, height } from ${JSON.stringify(importId)};
1812
1933
  import toImg from ${JSON.stringify(TO_IMG_ID)};
1813
1934
 
1814
1935
  export default toImg(srcSet, width, height);
@@ -1906,18 +2027,7 @@ function imagePlugin(userOpts) {
1906
2027
  },
1907
2028
  defaultDirectives: (url) => {
1908
2029
  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];
1912
- return new URLSearchParams({
1913
- format: "webp",
1914
- quality: "75",
1915
- w: "200;400;600;800;1200",
1916
- withoutEnlargement: "",
1917
- ...userOpts?.imageOptimization?.jsxDirectives,
1918
- ...params,
1919
- as: "jsx"
1920
- });
2030
+ return createImageJsxDirectives(url.searchParams, userOpts);
1921
2031
  }
1922
2032
  return new URLSearchParams();
1923
2033
  }
@@ -1999,7 +2109,7 @@ function imagePlugin(userOpts) {
1999
2109
  }
2000
2110
  if (supportedExtensions.includes(imageId.extension)) {
2001
2111
  return {
2002
- code: createImageJsxModule(imageId.pathId, imageId.params),
2112
+ code: createImageJsxModule(imageId.pathId, imageId.params, userOpts),
2003
2113
  map: null
2004
2114
  };
2005
2115
  }
@@ -2045,15 +2155,21 @@ async function collectServerFnModuleIds(routingContext, resolvedVirtualId, plugi
2045
2155
  continue;
2046
2156
  }
2047
2157
  const moduleInfo = await pluginContext.load({
2048
- id: resolved.id
2158
+ id: resolved.id,
2159
+ resolveDependencies: true
2049
2160
  });
2050
2161
  if (moduleInfo.code == null) {
2051
2162
  continue;
2052
2163
  }
2164
+ const qwikDeps = moduleInfo.meta?.qwikdeps ?? [];
2053
2165
  if (moduleInfo.code.includes("serverQrl(")) {
2054
2166
  serverFnModules.add(moduleInfo.id);
2055
2167
  }
2056
- const resolvedImports = moduleInfo.importedIds.concat(moduleInfo.dynamicallyImportedIds);
2168
+ const resolvedImports = [
2169
+ ...moduleInfo.importedIds,
2170
+ ...moduleInfo.dynamicallyImportedIds,
2171
+ ...qwikDeps
2172
+ ];
2057
2173
  for (let i = 0; i < resolvedImports.length; i++) {
2058
2174
  const resolvedImport = resolvedImports[i];
2059
2175
  if (resolvedImport && !seenModuleIds.has(resolvedImport)) {
@@ -2215,7 +2331,7 @@ class HtmlTransformPatcher {
2215
2331
  try {
2216
2332
  const fakeHtml = `<html><head>[FAKE_HEAD]</head><body>[FAKE_BODY]</body></html>`;
2217
2333
  const transformedHtml = await this.server.transformIndexHtml(this.request.url || "/", fakeHtml);
2218
- const hmrBridge = this.server.hot ? `<script type="module" src="/@id/@qwik-hmr-bridge"><\/script>` : "";
2334
+ const hmrBridge = this.server.hot ? `<script type="module" src="${this.server.config.base}@id/@qwik-hmr-bridge"><\/script>` : "";
2219
2335
  const fakeHeadIndex = transformedHtml.indexOf("[FAKE_HEAD]");
2220
2336
  const fakeHeadCloseIndex = transformedHtml.indexOf("</head>", fakeHeadIndex);
2221
2337
  if (fakeHeadIndex === -1 || fakeHeadCloseIndex === -1) {
@@ -2296,6 +2412,66 @@ function wrapResponseForHtmlTransform(request, response, server) {
2296
2412
  return response;
2297
2413
  }
2298
2414
 
2415
+ const FULLPATH_HEADER = "X-Qwik-fullpath";
2416
+ const ROUTE_PATH_HEADER = "X-Qwik-route-path";
2417
+ function getDevMiddlewareRequestPath(req) {
2418
+ return req.originalUrl || req.url;
2419
+ }
2420
+ async function loadSsrModule(server, filePath) {
2421
+ return server.ssrLoadModule(getImportPath(normalizePath(filePath)));
2422
+ }
2423
+ function matchesLoaderId(loader, loaderId) {
2424
+ return loader.__id === loaderId || loader.__qrl.getHash() === loaderId;
2425
+ }
2426
+ function isLoaderInternal(value) {
2427
+ return typeof value === "function" && value.__brand === "server_loader";
2428
+ }
2429
+ function findMatchingLoader(mod, loaderId) {
2430
+ for (const value of Object.values(mod)) {
2431
+ if (isLoaderInternal(value) && matchesLoaderId(value, loaderId)) {
2432
+ return value;
2433
+ }
2434
+ }
2435
+ }
2436
+ async function preloadQLoaderRouteLoader(server, ctx, req) {
2437
+ const requestPath = getDevMiddlewareRequestPath(req);
2438
+ if (!requestPath) {
2439
+ return void 0;
2440
+ }
2441
+ let requestUrl;
2442
+ try {
2443
+ requestUrl = new URL(requestPath, "http://qwik.dev");
2444
+ } catch {
2445
+ return void 0;
2446
+ }
2447
+ const recognized = recognizeRequest(requestUrl.pathname);
2448
+ if (recognized?.type !== IsQLoader || !recognized.data?.loaderId) {
2449
+ return void 0;
2450
+ }
2451
+ const loaderId = recognized.data.loaderId;
2452
+ const headerValue = req.headers[FULLPATH_HEADER.toLowerCase()] ?? req.headers[ROUTE_PATH_HEADER.toLowerCase()];
2453
+ const fullPathHeader = Array.isArray(headerValue) ? headerValue[0] : headerValue ?? null;
2454
+ const loaderPathname = trimRecognizedInternalPathname(requestUrl.pathname, recognized);
2455
+ const pathname = resolveValidInternalFullPathname(loaderPathname, fullPathHeader) ?? loaderPathname;
2456
+ const route = ctx.routes.filter((route2) => isPageExt(route2.ext)).slice().sort(routeSortCompare).find((route2) => route2.pattern.test(pathname));
2457
+ if (!route) {
2458
+ return void 0;
2459
+ }
2460
+ const routeRootFiles = [
2461
+ ...route.layouts.map((layout) => layout.filePath),
2462
+ route.filePath
2463
+ ];
2464
+ const modules = await Promise.all([
2465
+ ...ctx.serverPlugins.map((plugin) => loadSsrModule(server, plugin.filePath)),
2466
+ ...routeRootFiles.map((filePath) => loadSsrModule(server, filePath))
2467
+ ]);
2468
+ for (const mod of modules) {
2469
+ const loader = findMatchingLoader(mod, loaderId);
2470
+ if (loader) {
2471
+ return loader;
2472
+ }
2473
+ }
2474
+ }
2299
2475
  const makeRouterDevMiddleware = (server, ctx) => async (req, res, next) => {
2300
2476
  let mod;
2301
2477
  try {
@@ -2315,6 +2491,11 @@ const makeRouterDevMiddleware = (server, ctx) => async (req, res, next) => {
2315
2491
  await updateRoutingContext(ctx);
2316
2492
  ctx.isDirty = false;
2317
2493
  }
2494
+ const preloadedRouteLoader = await preloadQLoaderRouteLoader(server, ctx, req);
2495
+ if (preloadedRouteLoader) {
2496
+ await server.ssrLoadModule("@qwik-router-config");
2497
+ devPreloadedRouteLoaders.set(req, preloadedRouteLoader);
2498
+ }
2318
2499
  const entry = ctx.entries.find((e) => req.url === `${server.config.base}${e.chunkFileName}`);
2319
2500
  if (entry) {
2320
2501
  const entryContents = await server.transformRequest(`/@fs${entry.filePath.startsWith("/") ? "" : "/"}${entry.filePath}`);
@@ -2392,6 +2573,9 @@ const getCssUrls = (server) => {
2392
2573
  });
2393
2574
  if ((isEntryCSS || hasJSImporter) && !hasCSSImporter && !cssImportedByCSS.has(mod.url)) {
2394
2575
  cssModules.add(`${mod.url}${mod.lastHMRTimestamp ? `?t=${mod.lastHMRTimestamp}` : ""}`);
2576
+ if (mod.file) {
2577
+ server.watcher.add(mod.file);
2578
+ }
2395
2579
  }
2396
2580
  }
2397
2581
  }
@@ -2406,16 +2590,179 @@ const getRouterIndexTags = (server) => {
2406
2590
  tag: "link",
2407
2591
  attrs: {
2408
2592
  rel: "stylesheet",
2409
- href: url
2593
+ href: server.config.base + url.slice(1)
2410
2594
  }
2411
2595
  }));
2412
2596
  };
2597
+ const sendRouterCssHotUpdate = (server, file, timestamp) => {
2598
+ const { client, ssr } = server.environments;
2599
+ if (!isCssPath(file) || client.moduleGraph.getModulesByFile(file)?.size) {
2600
+ return false;
2601
+ }
2602
+ const paths = /* @__PURE__ */ new Set();
2603
+ for (const mod of ssr.moduleGraph.getModulesByFile(file) ?? []) {
2604
+ mod.lastHMRTimestamp = timestamp;
2605
+ paths.add(mod.url.split("?")[0]);
2606
+ }
2607
+ if (!paths.size) {
2608
+ return false;
2609
+ }
2610
+ client.hot.send({
2611
+ type: "update",
2612
+ updates: [
2613
+ ...paths
2614
+ ].map((path) => ({
2615
+ type: "css-update",
2616
+ path,
2617
+ acceptedPath: path,
2618
+ timestamp
2619
+ }))
2620
+ });
2621
+ return true;
2622
+ };
2413
2623
 
2414
2624
  const QWIK_ROUTER_CONFIG_ID = "@qwik-router-config";
2415
2625
  const QWIK_ROUTER_ENTRIES_ID = "@qwik-router-entries";
2416
2626
  const QWIK_ROUTER = "@qwik.dev/router";
2417
2627
  const QWIK_ROUTER_SW_REGISTER = "@qwik-router-sw-register";
2418
2628
  const VIRTUAL_SERVER_FNS = "virtual:qwik-router-server-fns";
2629
+ function replaceLoaderPlaceholders(code, loadersByFile) {
2630
+ return code.replace(/_R\s*:\s*"__LOADERS:([^"]+)__"\s*,?/g, (_match, paths) => {
2631
+ const filePaths = paths.split("|");
2632
+ const hashes = [];
2633
+ for (const filePath of filePaths) {
2634
+ const fileHashes = loadersByFile.get(filePath);
2635
+ if (fileHashes) {
2636
+ hashes.push(...fileHashes);
2637
+ }
2638
+ }
2639
+ if (hashes.length > 0) {
2640
+ return `_R: ${JSON.stringify(hashes)},`;
2641
+ }
2642
+ return "";
2643
+ });
2644
+ }
2645
+ function addRouteLoaderHash(loadersByFile, filePath, hash) {
2646
+ const normalizedPath = normalizePath(filePath);
2647
+ const existing = loadersByFile.get(normalizedPath);
2648
+ if (!existing) {
2649
+ loadersByFile.set(normalizedPath, [
2650
+ hash
2651
+ ]);
2652
+ return true;
2653
+ }
2654
+ if (!existing.includes(hash)) {
2655
+ existing.push(hash);
2656
+ return true;
2657
+ }
2658
+ return false;
2659
+ }
2660
+ function clearRouteLoaderHashes(loadersByFile, filePath) {
2661
+ return loadersByFile.delete(normalizePath(filePath));
2662
+ }
2663
+ function isRouterSourceFilePath(filePath) {
2664
+ const fileName = basename(filePath);
2665
+ const extlessName = removeExtension(fileName);
2666
+ return isMenuFileName(fileName) || isIndexModule(extlessName) || isErrorName(extlessName) || isLayoutModule(extlessName) || isEntryName(extlessName) || isServiceWorkerName(extlessName) || isPluginModule(extlessName);
2667
+ }
2668
+ function isPathInDir(filePath, dirPath) {
2669
+ return filePath === dirPath || filePath.startsWith(`${dirPath}/`);
2670
+ }
2671
+ function isRouterSourceFileForContext(filePath, ctx) {
2672
+ const normalizedPath = normalizePath(filePath);
2673
+ return isRouterSourceFilePath(normalizedPath) && (isPathInDir(normalizedPath, ctx.opts.routesDir) || isPathInDir(normalizedPath, ctx.opts.serverPluginsDir));
2674
+ }
2675
+ function isDiscoveredRouteLoaderSource(filePath, sourceFiles) {
2676
+ if (!sourceFiles) {
2677
+ return false;
2678
+ }
2679
+ const normalizedPath = normalizePath(filePath);
2680
+ for (const files of sourceFiles.values()) {
2681
+ if (files.includes(normalizedPath)) {
2682
+ return true;
2683
+ }
2684
+ }
2685
+ return false;
2686
+ }
2687
+ function collectRouteLoaderOwners(node, owners) {
2688
+ for (const file of node._files) {
2689
+ if (file.type === "layout" || file.type === "route") {
2690
+ owners.add(file.filePath);
2691
+ }
2692
+ }
2693
+ for (const child of node.children.values()) {
2694
+ collectRouteLoaderOwners(child, owners);
2695
+ }
2696
+ }
2697
+ async function collectReExportSources(owner, filePath, resolveSource, sources, seen = /* @__PURE__ */ new Set([
2698
+ owner
2699
+ ])) {
2700
+ let code, exportEntries;
2701
+ try {
2702
+ code = await readFile(filePath, "utf-8");
2703
+ exportEntries = findExports(code);
2704
+ if (!exportEntries) {
2705
+ return;
2706
+ }
2707
+ } catch {
2708
+ return;
2709
+ }
2710
+ await Promise.all(exportEntries.map(async (exp) => {
2711
+ if (!exp.specifier) {
2712
+ return;
2713
+ }
2714
+ const resolved = await resolveSource(exp.specifier, filePath);
2715
+ if (!resolved || resolved.charCodeAt(0) === 0) {
2716
+ return;
2717
+ }
2718
+ const resolvedPath = normalizePath(resolved.split(/[?#]/, 1)[0]);
2719
+ let ownerSources = sources.get(owner);
2720
+ if (!ownerSources) {
2721
+ sources.set(owner, ownerSources = []);
2722
+ }
2723
+ if (!ownerSources.includes(resolvedPath)) {
2724
+ ownerSources.push(resolvedPath);
2725
+ }
2726
+ if (!seen.has(resolvedPath)) {
2727
+ seen.add(resolvedPath);
2728
+ await collectReExportSources(owner, resolvedPath, resolveSource, sources, seen);
2729
+ }
2730
+ }));
2731
+ }
2732
+ async function findRouteLoaderSourceFiles(ctx, resolveSource) {
2733
+ const sources = /* @__PURE__ */ new Map();
2734
+ const owners = /* @__PURE__ */ new Set();
2735
+ collectRouteLoaderOwners(ctx.routeTrie, owners);
2736
+ for (const plugin of ctx.serverPlugins) {
2737
+ owners.add(plugin.filePath);
2738
+ }
2739
+ await Promise.all([
2740
+ ...owners
2741
+ ].map((owner) => collectReExportSources(owner, owner, resolveSource, sources)));
2742
+ return sources;
2743
+ }
2744
+ function invalidateRouterConfigModules(server) {
2745
+ const modules = [];
2746
+ const moduleGraphs = /* @__PURE__ */ new Set();
2747
+ moduleGraphs.add(server.moduleGraph);
2748
+ const environments = server.environments;
2749
+ if (environments) {
2750
+ for (const environment of Object.values(environments)) {
2751
+ const graph = environment?.moduleGraph;
2752
+ if (graph) {
2753
+ moduleGraphs.add(graph);
2754
+ }
2755
+ }
2756
+ }
2757
+ for (const graph of moduleGraphs) {
2758
+ const mod = graph.getModuleById(QWIK_ROUTER_CONFIG_ID);
2759
+ if (mod) {
2760
+ graph.invalidateModule(mod);
2761
+ modules.push(mod);
2762
+ }
2763
+ }
2764
+ return modules;
2765
+ }
2419
2766
  async function generateServerPackageJson(outDir) {
2420
2767
  await fs.promises.mkdir(outDir, {
2421
2768
  recursive: true
@@ -2447,6 +2794,9 @@ function qwikRouterPlugin(userOpts, buildContextRef) {
2447
2794
  let devSsrServer = userOpts?.devSsrServer;
2448
2795
  const routesDir = userOpts?.routesDir ?? "src/routes";
2449
2796
  const serverPluginsDir = userOpts?.serverPluginsDir ?? routesDir;
2797
+ const loadersByFile = /* @__PURE__ */ new Map();
2798
+ let reExportedRouteLoaderSources;
2799
+ let ssgRoutePatterns;
2450
2800
  const api = {
2451
2801
  getBasePathname: () => ctx?.opts.basePathname ?? "/",
2452
2802
  getRoutes: () => {
@@ -2454,6 +2804,12 @@ function qwikRouterPlugin(userOpts, buildContextRef) {
2454
2804
  },
2455
2805
  getServiceWorkers: () => {
2456
2806
  return ctx?.serviceWorkers.slice() ?? [];
2807
+ },
2808
+ _setSsgRoutes: (include, exclude) => {
2809
+ ssgRoutePatterns = include?.length ? {
2810
+ include,
2811
+ exclude
2812
+ } : void 0;
2457
2813
  }
2458
2814
  };
2459
2815
  const plugin = {
@@ -2466,7 +2822,8 @@ function qwikRouterPlugin(userOpts, buildContextRef) {
2466
2822
  define: {
2467
2823
  "globalThis.__DEFAULT_LOADERS_SERIALIZATION_STRATEGY__": JSON.stringify(userOpts?.defaultLoadersSerializationStrategy || "never"),
2468
2824
  "globalThis.__NO_TRAILING_SLASH__": JSON.stringify(userOpts?.trailingSlash === false),
2469
- "globalThis.__SSR_CACHE_SIZE__": JSON.stringify(viteEnv.command === "serve" ? 0 : userOpts?.ssrCacheSize ?? 50)
2825
+ "globalThis.__SSR_CACHE_SIZE__": JSON.stringify(viteEnv.command === "serve" ? 0 : userOpts?.ssrCacheSize ?? 50),
2826
+ "globalThis.__STRICT_LOADERS__": JSON.stringify(userOpts?.strictLoaders !== false)
2470
2827
  },
2471
2828
  appType: "custom",
2472
2829
  resolve: {
@@ -2536,7 +2893,7 @@ function qwikRouterPlugin(userOpts, buildContextRef) {
2536
2893
  return updatedViteConfig;
2537
2894
  },
2538
2895
  configEnvironment(name, _config, _env) {
2539
- if (name === "ssr") {
2896
+ if (name === "ssr" || name === "ssg") {
2540
2897
  return {
2541
2898
  resolve: {
2542
2899
  external: [
@@ -2566,6 +2923,14 @@ function qwikRouterPlugin(userOpts, buildContextRef) {
2566
2923
  if (!qwikPlugin) {
2567
2924
  throw new Error("Missing vite-plugin-qwik");
2568
2925
  }
2926
+ qwikPlugin.api.onSegment((parentId, segment) => {
2927
+ if (segment.ctxName === "routeLoader$") {
2928
+ const changed = addRouteLoaderHash(loadersByFile, parentId, segment.hash);
2929
+ if (changed && devServer) {
2930
+ invalidateRouterConfigModules(devServer);
2931
+ }
2932
+ }
2933
+ });
2569
2934
  if (typeof devSsrServer !== "boolean") {
2570
2935
  devSsrServer = qwikPlugin.api._oldDevSsrServer();
2571
2936
  }
@@ -2576,21 +2941,19 @@ function qwikRouterPlugin(userOpts, buildContextRef) {
2576
2941
  },
2577
2942
  async configureServer(server) {
2578
2943
  devServer = server;
2579
- const toWatch = resolve(rootDir, "src/routes/**/{index,layout,entry,service-worker}{.,@,-}*");
2944
+ const toWatch = [
2945
+ join(ctx.opts.routesDir, "**/{index,index!,index@*,layout,layout!,layout-*,error,404,entry,service-worker,menu}.{ts,tsx,js,jsx,md,mdx}"),
2946
+ join(ctx.opts.serverPluginsDir, "plugin{,@*}.{ts,tsx,js,jsx}")
2947
+ ];
2580
2948
  server.watcher.add(toWatch);
2581
2949
  await new Promise((resolve2) => setTimeout(resolve2, 1e3));
2582
2950
  server.watcher.on("change", (path) => {
2583
- if (!/\/(index[.@]|layout[.-]|entry\.|service-worker\.)[^/]*$/.test(path)) {
2951
+ if (!isRouterSourceFileForContext(path, ctx)) {
2584
2952
  return;
2585
2953
  }
2954
+ clearRouteLoaderHashes(loadersByFile, path);
2586
2955
  ctx.isDirty = true;
2587
- const graph = server.environments?.ssr?.moduleGraph;
2588
- if (graph) {
2589
- const mod = graph.getModuleById("@qwik-router-config");
2590
- if (mod) {
2591
- graph.invalidateModule(mod);
2592
- }
2593
- }
2956
+ invalidateRouterConfigModules(server);
2594
2957
  });
2595
2958
  if (userOpts?.devSsrServer !== false) {
2596
2959
  return () => {
@@ -2598,6 +2961,31 @@ function qwikRouterPlugin(userOpts, buildContextRef) {
2598
2961
  };
2599
2962
  }
2600
2963
  },
2964
+ handleHotUpdate({ file, modules, server, timestamp }) {
2965
+ if (sendRouterCssHotUpdate(server, file, timestamp)) {
2966
+ return [];
2967
+ }
2968
+ const clearedLoaderHashes = clearRouteLoaderHashes(loadersByFile, file);
2969
+ if (!ctx) {
2970
+ return;
2971
+ }
2972
+ if (!isRouterSourceFileForContext(file, ctx)) {
2973
+ if (clearedLoaderHashes || isDiscoveredRouteLoaderSource(file, reExportedRouteLoaderSources)) {
2974
+ const configModules2 = invalidateRouterConfigModules(server);
2975
+ return [
2976
+ ...modules,
2977
+ ...configModules2
2978
+ ];
2979
+ }
2980
+ return;
2981
+ }
2982
+ ctx.isDirty = true;
2983
+ const configModules = invalidateRouterConfigModules(server);
2984
+ return [
2985
+ ...modules,
2986
+ ...configModules
2987
+ ];
2988
+ },
2601
2989
  transformIndexHtml() {
2602
2990
  if (viteCommand !== "serve") {
2603
2991
  return;
@@ -2638,7 +3026,15 @@ function qwikRouterPlugin(userOpts, buildContextRef) {
2638
3026
  });
2639
3027
  }
2640
3028
  if (isRouterConfig) {
2641
- return generateQwikRouterConfig(ctx, qwikPlugin, this.environment.config.consumer === "server");
3029
+ const isServerConsumer = this.environment.config.consumer === "server";
3030
+ const serverExcludePaths = isServerConsumer && !devServer && this.environment.name !== "ssg" ? await getServerExcludedRoutes(ctx, ssgRoutePatterns) : void 0;
3031
+ reExportedRouteLoaderSources = await findRouteLoaderSourceFiles(ctx, async (specifier, importer) => {
3032
+ const resolved = await this.resolve(specifier, importer, {
3033
+ skipSelf: true
3034
+ });
3035
+ return resolved?.id;
3036
+ });
3037
+ return generateQwikRouterConfig(ctx, qwikPlugin, isServerConsumer, devServer ? loadersByFile : void 0, serverExcludePaths, reExportedRouteLoaderSources);
2642
3038
  }
2643
3039
  if (isSwRegister) {
2644
3040
  return generateServiceWorkerRegister(ctx, swRegister);
@@ -2692,6 +3088,20 @@ function qwikRouterPlugin(userOpts, buildContextRef) {
2692
3088
  return null;
2693
3089
  },
2694
3090
  generateBundle(_, bundles) {
3091
+ const manifest = qwikPlugin.api.getManifest();
3092
+ if (manifest) {
3093
+ const srcDir = qwikPlugin.api.getOptions().srcDir;
3094
+ for (const symbol of Object.values(manifest.symbols)) {
3095
+ if (symbol.ctxName === "routeLoader$" && symbol.origin) {
3096
+ addRouteLoaderHash(loadersByFile, resolve(srcDir, symbol.origin), symbol.hash);
3097
+ }
3098
+ }
3099
+ }
3100
+ for (const chunk of Object.values(bundles)) {
3101
+ if (chunk.type === "chunk" && chunk.code.includes("__LOADERS:")) {
3102
+ chunk.code = replaceLoaderPlaceholders(chunk.code, loadersByFile);
3103
+ }
3104
+ }
2695
3105
  if (this.environment.config.consumer === "client") {
2696
3106
  const entries = [
2697
3107
  ...ctx.entries,
@@ -2779,7 +3189,8 @@ function serverFnsPlugin(buildContextRef) {
2779
3189
  }
2780
3190
  return [
2781
3191
  ...serverFnModules
2782
- ].map((mod) => `import ${JSON.stringify(mod)};`).join("\n");
3192
+ ].map((mod, index) => `import * as serverFnModule${index} from ${JSON.stringify(mod)};
3193
+ Object.values(serverFnModule${index});`).join("\n");
2783
3194
  }
2784
3195
  return null;
2785
3196
  }