@reckona/mreact-router 0.0.3 → 0.0.5

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 (50) hide show
  1. package/README.md +53 -1
  2. package/dist/adapters/aws-lambda.d.ts +2 -0
  3. package/dist/adapters/aws-lambda.d.ts.map +1 -1
  4. package/dist/adapters/aws-lambda.js +2 -0
  5. package/dist/adapters/aws-lambda.js.map +1 -1
  6. package/dist/adapters/node.d.ts +2 -0
  7. package/dist/adapters/node.d.ts.map +1 -1
  8. package/dist/adapters/node.js +1 -0
  9. package/dist/adapters/node.js.map +1 -1
  10. package/dist/app-router-globals.d.ts +10 -0
  11. package/dist/app-router-globals.d.ts.map +1 -0
  12. package/dist/app-router-globals.js +2 -0
  13. package/dist/app-router-globals.js.map +1 -0
  14. package/dist/client.d.ts.map +1 -1
  15. package/dist/client.js +525 -45
  16. package/dist/client.js.map +1 -1
  17. package/dist/import-policy.d.ts.map +1 -1
  18. package/dist/import-policy.js +35 -8
  19. package/dist/import-policy.js.map +1 -1
  20. package/dist/index.d.ts +5 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +2 -0
  23. package/dist/index.js.map +1 -1
  24. package/dist/link.d.ts +18 -0
  25. package/dist/link.d.ts.map +1 -0
  26. package/dist/link.js +24 -0
  27. package/dist/link.js.map +1 -0
  28. package/dist/module-runner.d.ts.map +1 -1
  29. package/dist/module-runner.js +36 -13
  30. package/dist/module-runner.js.map +1 -1
  31. package/dist/navigation-state.d.ts +11 -0
  32. package/dist/navigation-state.d.ts.map +1 -0
  33. package/dist/navigation-state.js +65 -0
  34. package/dist/navigation-state.js.map +1 -0
  35. package/dist/render.d.ts +5 -0
  36. package/dist/render.d.ts.map +1 -1
  37. package/dist/render.js +77 -14
  38. package/dist/render.js.map +1 -1
  39. package/dist/route-source.d.ts.map +1 -1
  40. package/dist/route-source.js +16 -5
  41. package/dist/route-source.js.map +1 -1
  42. package/dist/serve.d.ts +4 -1
  43. package/dist/serve.d.ts.map +1 -1
  44. package/dist/serve.js +11 -3
  45. package/dist/serve.js.map +1 -1
  46. package/dist/workspace-packages.d.ts +9 -0
  47. package/dist/workspace-packages.d.ts.map +1 -0
  48. package/dist/workspace-packages.js +20 -0
  49. package/dist/workspace-packages.js.map +1 -0
  50. package/package.json +20 -7
package/dist/client.js CHANGED
@@ -1,12 +1,12 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { readFile, stat } from "node:fs/promises";
3
3
  import { dirname, extname, join } from "node:path";
4
- import { fileURLToPath } from "node:url";
5
4
  import { collectIdentifierReferenceNames, collectJsxComponentRootNames, collectStaticExportReferences, collectStaticImportReferences, formatDiagnostic, hasClientRuntimeSyntax, transform, } from "@reckona/mreact-compiler";
6
5
  import { build } from "esbuild";
7
6
  import { assetPath } from "./assets.js";
8
7
  import { stripRouteClientOnlyExports } from "./route-source.js";
9
8
  import { escapeHtmlQuotedAttribute as escapeHtmlAttribute } from "@reckona/mreact-shared/html-escape";
9
+ import { workspacePackageFile } from "./workspace-packages.js";
10
10
  export async function routeToClientManifestEntry(route) {
11
11
  if (route.kind === "server") {
12
12
  return { path: route.path, kind: route.kind, client: false };
@@ -77,6 +77,11 @@ async function inferClientRouteModuleSource(options) {
77
77
  filename: options.filename,
78
78
  }));
79
79
  for (const reference of await staticImportReferencesForSource(options)) {
80
+ const rendered = isRenderedImportReference(reference, jsxComponentRoots);
81
+ const referenced = isReferencedImportReference(reference, identifierReferences);
82
+ if (!rendered && !referenced) {
83
+ continue;
84
+ }
80
85
  const resolved = await resolveAppLocalModule({
81
86
  cache: options.cache,
82
87
  importer: options.filename,
@@ -97,7 +102,7 @@ async function inferClientRouteModuleSource(options) {
97
102
  if (!imported.client) {
98
103
  continue;
99
104
  }
100
- if (isRenderedImportReference(reference, jsxComponentRoots)) {
105
+ if (rendered) {
101
106
  clientBoundaryImports.push(reference.source);
102
107
  continue;
103
108
  }
@@ -172,6 +177,10 @@ function isRenderedImportReference(reference, jsxComponentRoots) {
172
177
  return (reference.sideEffect ||
173
178
  reference.localNames.some((localName) => jsxComponentRoots.has(localName)));
174
179
  }
180
+ function isReferencedImportReference(reference, identifierReferences) {
181
+ return (reference.sideEffect ||
182
+ reference.localNames.some((localName) => identifierReferences.has(localName)));
183
+ }
175
184
  function unsupportedClientImportReferenceDiagnostic(options) {
176
185
  if (options.reference.sideEffect) {
177
186
  return undefined;
@@ -353,13 +362,25 @@ export async function buildClientRouteOutput(options) {
353
362
  const clientNavigation = options.clientNavigation ?? detectClientNavigationHint(options.code);
354
363
  const clientReferenceManifest = options.clientReferenceManifest ?? await inferClientReferenceManifestForBundle(options);
355
364
  const clientReferenceRegistry = emitClientReferenceRegistry(clientReferenceManifest);
365
+ const routeComponentExpression = routeComponentExpressionForComponents(compiled.metadata.components);
356
366
  const routeId = routeIdForPath(options.routePath);
357
367
  const routeUsesCells = detectRouteCellStateHint(compiled.code);
358
368
  const routeStateSignature = routeUsesCells ? routeStateSignatureForSource(compiled.code) : "";
359
369
  const navigationStateDeclaration = clientNavigation
360
370
  ? `const __mreactNavigationState = __mreactGlobal.__mreactNavigationState ??= {
361
371
  cache: new Map(),
372
+ current: {
373
+ from: null,
374
+ pending: false,
375
+ to: null,
376
+ type: null,
377
+ },
362
378
  installed: false,
379
+ prefetchedScripts: new Set(),
380
+ routePrefetchManifest: undefined,
381
+ routePrefetchManifestText: undefined,
382
+ viewportAnchors: new WeakSet(),
383
+ viewportObserver: undefined,
363
384
  };`
364
385
  : "";
365
386
  const routeCellStateDeclaration = routeUsesCells
@@ -455,11 +476,7 @@ export function __mreactHydrateRoute() {
455
476
  : JSON.parse(__mreactClientReferencesElement.textContent);
456
477
  const __mreactClientReferenceManifests = __mreactGlobal.__mreactClientReferenceManifests ??= new Map();
457
478
  __mreactClientReferenceManifests.set(__mreactRouteId, __mreactClientReferences);
458
- const __mreactComponent = typeof Page === "function"
459
- ? Page
460
- : typeof DefaultExport === "function"
461
- ? DefaultExport
462
- : undefined;
479
+ const __mreactComponent = ${routeComponentExpression};
463
480
 
464
481
  if (__mreactMarker === null || __mreactComponent === undefined) {
465
482
  return;
@@ -478,7 +495,7 @@ __mreactHydrateRoute();
478
495
  ${clientNavigation ? "__mreactInstallNavigation();" : ""}
479
496
 
480
497
  ${clientNavigation
481
- ? `export function __mreactNavigateToHtml(html, url) {
498
+ ? `export function __mreactNavigateToHtml(html, url, options = {}) {
482
499
  __mreactSaveCurrentHistoryState();
483
500
  const applied = __mreactApplyNavigationHtml(html, url);
484
501
 
@@ -487,50 +504,189 @@ ${clientNavigation
487
504
  }
488
505
 
489
506
  __mreactPushHistoryState(url);
490
- __mreactScrollTo(0, 0);
507
+ if (options.scroll !== "preserve") {
508
+ __mreactScrollTo(0, 0);
509
+ }
491
510
  return true;
492
511
  }
493
512
 
494
513
  export async function __mreactPrefetch(url) {
495
- const href = __mreactNormalizeNavigationUrl(url);
514
+ if (!__mreactCanPrefetch()) {
515
+ return false;
516
+ }
517
+
518
+ const script = __mreactRouteScriptForNavigationUrl(url);
519
+
520
+ if (script === undefined) {
521
+ return false;
522
+ }
523
+
524
+ return __mreactPrefetchRouteScript(script);
525
+ }
526
+
527
+ function __mreactPrefetchRouteScript(script) {
528
+ if (typeof document === "undefined") {
529
+ return false;
530
+ }
531
+
532
+ const href = __mreactNormalizeAssetUrl(script);
496
533
 
497
534
  if (href === undefined) {
498
535
  return false;
499
536
  }
500
537
 
501
- if (__mreactNavigationState.cache.has(href)) {
538
+ if (__mreactNavigationState.prefetchedScripts.has(href)) {
502
539
  return true;
503
540
  }
504
541
 
505
- const response = await fetch(href, {
506
- headers: { "x-mreact-navigation": "1" },
507
- });
508
- __mreactApplyRevalidationHeader(response);
509
- const html = await response.text();
510
- __mreactNavigationState.cache.set(href, html);
542
+ for (const link of Array.from(document.querySelectorAll('link[rel="modulepreload"][href]'))) {
543
+ if (link.href === href) {
544
+ __mreactNavigationState.prefetchedScripts.add(href);
545
+ return true;
546
+ }
547
+ }
548
+
549
+ const link = document.createElement("link");
550
+ link.rel = "modulepreload";
551
+ link.href = href;
552
+ document.head.appendChild(link);
553
+ __mreactNavigationState.prefetchedScripts.add(href);
511
554
  return true;
512
555
  }
513
556
 
514
- export async function __mreactNavigate(url) {
557
+ export async function __mreactNavigate(url, options = {}) {
515
558
  const href = __mreactNormalizeNavigationUrl(url);
516
559
 
517
560
  if (href === undefined) {
518
561
  return false;
519
562
  }
520
563
 
521
- document.documentElement.setAttribute("data-mreact-navigation-pending", "true");
564
+ __mreactSetNavigationState(__mreactPendingNavigationState(href, options.type ?? "push"));
522
565
 
523
566
  try {
524
567
  const cachedHtml = __mreactNavigationState.cache.get(href);
525
568
  const html = cachedHtml ?? await __mreactFetchNavigationHtml(href);
526
569
 
527
570
  __mreactNavigationState.cache.set(href, html);
528
- return __mreactNavigateToHtml(html, href);
571
+ return await __mreactApplyNavigationHtmlWithOptionalTransition(html, href, options);
529
572
  } finally {
530
- document.documentElement.removeAttribute("data-mreact-navigation-pending");
573
+ __mreactSetNavigationState(__mreactIdleNavigationState());
574
+ }
575
+ }
576
+
577
+ export function __mreactGetNavigationState() {
578
+ return __mreactNavigationStateSnapshot(__mreactNavigationState.current);
579
+ }
580
+
581
+ function __mreactPendingNavigationState(to, type) {
582
+ return {
583
+ from: typeof location === "undefined" ? null : location.href,
584
+ pending: true,
585
+ to,
586
+ type: __mreactNavigationType(type),
587
+ };
588
+ }
589
+
590
+ function __mreactIdleNavigationState() {
591
+ return {
592
+ from: null,
593
+ pending: false,
594
+ to: null,
595
+ type: null,
596
+ };
597
+ }
598
+
599
+ function __mreactSetNavigationState(state) {
600
+ const snapshot = __mreactNavigationStateSnapshot(state);
601
+ __mreactNavigationState.current = snapshot;
602
+ __mreactApplyNavigationStateAttributes(snapshot);
603
+ __mreactDispatchNavigationStateChange(snapshot);
604
+ }
605
+
606
+ function __mreactNavigationStateSnapshot(state) {
607
+ return {
608
+ from: typeof state?.from === "string" ? state.from : null,
609
+ pending: state?.pending === true,
610
+ to: typeof state?.to === "string" ? state.to : null,
611
+ type: __mreactNavigationType(state?.type),
612
+ };
613
+ }
614
+
615
+ function __mreactNavigationType(type) {
616
+ return type === "push" || type === "replace" || type === "pop" || type === "refresh"
617
+ ? type
618
+ : null;
619
+ }
620
+
621
+ function __mreactApplyNavigationStateAttributes(state) {
622
+ if (typeof document === "undefined") {
623
+ return;
624
+ }
625
+
626
+ const root = document.documentElement;
627
+
628
+ if (state.pending !== true) {
629
+ root.removeAttribute("data-mreact-navigation-pending");
630
+ root.removeAttribute("data-mreact-navigation-from");
631
+ root.removeAttribute("data-mreact-navigation-to");
632
+ root.removeAttribute("data-mreact-navigation-type");
633
+ return;
634
+ }
635
+
636
+ root.setAttribute("data-mreact-navigation-pending", "true");
637
+
638
+ if (state.from === null) {
639
+ root.removeAttribute("data-mreact-navigation-from");
640
+ } else {
641
+ root.setAttribute("data-mreact-navigation-from", state.from);
642
+ }
643
+
644
+ if (state.to === null) {
645
+ root.removeAttribute("data-mreact-navigation-to");
646
+ } else {
647
+ root.setAttribute("data-mreact-navigation-to", state.to);
648
+ }
649
+
650
+ if (state.type === null) {
651
+ root.removeAttribute("data-mreact-navigation-type");
652
+ } else {
653
+ root.setAttribute("data-mreact-navigation-type", state.type);
531
654
  }
532
655
  }
533
656
 
657
+ function __mreactDispatchNavigationStateChange(state) {
658
+ if (typeof window === "undefined" || typeof CustomEvent !== "function") {
659
+ return;
660
+ }
661
+
662
+ window.dispatchEvent(new CustomEvent("mreact:navigation-state-change", {
663
+ detail: state,
664
+ }));
665
+ }
666
+
667
+ async function __mreactApplyNavigationHtmlWithOptionalTransition(html, href, options) {
668
+ if (
669
+ options.transition !== "auto" ||
670
+ typeof document === "undefined" ||
671
+ typeof document.startViewTransition !== "function"
672
+ ) {
673
+ return __mreactNavigateToHtml(html, href, options);
674
+ }
675
+
676
+ let navigated = false;
677
+ const transition = document.startViewTransition(() => {
678
+ navigated = __mreactNavigateToHtml(html, href, options);
679
+ });
680
+
681
+ try {
682
+ await transition.updateCallbackDone;
683
+ } catch {
684
+ return navigated;
685
+ }
686
+
687
+ return navigated;
688
+ }
689
+
534
690
  export function __mreactInvalidateNavigationCache(path) {
535
691
  const normalizedPath = __mreactNormalizeNavigationPath(path);
536
692
 
@@ -607,15 +763,12 @@ function __mreactApplyNavigationHtml(html, url) {
607
763
  return false;
608
764
  }
609
765
 
610
- __mreactResumeNode(currentMarker, nextMarker);
611
-
612
- for (const propsElement of Array.from(document.querySelectorAll('script[type="application/json"][id^="mreact-props-"], script[type="application/json"][id^="mreact-client-references-"]'))) {
613
- propsElement.remove();
614
- }
766
+ const currentRouteId = currentMarker.getAttribute("data-mreact-route-id");
767
+ const nextRouteId = nextMarker.getAttribute("data-mreact-route-id");
615
768
 
616
- for (const propsElement of Array.from(template.content.querySelectorAll('script[type="application/json"][id^="mreact-props-"], script[type="application/json"][id^="mreact-client-references-"]'))) {
617
- document.body.appendChild(propsElement);
618
- }
769
+ __mreactSyncHeadMetadata(template.content, html);
770
+ __mreactResumeNode(currentMarker, nextMarker);
771
+ __mreactSyncRouteDataScripts(template.content, currentRouteId, nextRouteId);
619
772
 
620
773
  const script = template.content.querySelector('script[type="module"][src]')?.getAttribute("src");
621
774
  if (script !== null && script !== undefined) {
@@ -623,10 +776,85 @@ function __mreactApplyNavigationHtml(html, url) {
623
776
  }
624
777
 
625
778
  __mreactApplyOutOfOrderFragments(document);
779
+ __mreactObserveViewportPrefetchAnchors(document);
626
780
 
627
781
  return true;
628
782
  }
629
783
 
784
+ function __mreactSyncHeadMetadata(root, html) {
785
+ const nextHead = root.querySelector("head");
786
+
787
+ if ((nextHead === null && !/<head(?:\\s[^>]*)?>/i.test(html)) || document.head === null) {
788
+ return;
789
+ }
790
+
791
+ const metadataRoot = nextHead ?? root;
792
+ const selector = __mreactManagedHeadMetadataSelector();
793
+
794
+ for (const element of Array.from(document.head.querySelectorAll(selector))) {
795
+ element.remove();
796
+ }
797
+
798
+ for (const element of Array.from(metadataRoot.querySelectorAll(selector))) {
799
+ document.head.appendChild(element);
800
+ }
801
+ }
802
+
803
+ function __mreactManagedHeadMetadataSelector() {
804
+ return [
805
+ "title",
806
+ 'meta[name="description"]',
807
+ 'link[rel="canonical"]',
808
+ 'meta[property="og:title"]',
809
+ 'meta[property="og:description"]',
810
+ 'meta[property="og:image"]',
811
+ 'link[rel="icon"]',
812
+ 'link[rel="apple-touch-icon"]',
813
+ 'meta[name="robots"]',
814
+ 'meta[name="theme-color"]',
815
+ 'meta[name="viewport"]',
816
+ ].join(",");
817
+ }
818
+
819
+ function __mreactSyncRouteDataScripts(root, currentRouteId, nextRouteId) {
820
+ const managedIds = __mreactRouteDataScriptIds(currentRouteId, nextRouteId);
821
+
822
+ if (managedIds.size === 0) {
823
+ return;
824
+ }
825
+
826
+ for (const element of Array.from(document.querySelectorAll(__mreactRouteDataScriptSelector()))) {
827
+ if (managedIds.has(element.id)) {
828
+ element.remove();
829
+ }
830
+ }
831
+
832
+ for (const element of Array.from(root.querySelectorAll(__mreactRouteDataScriptSelector()))) {
833
+ if (managedIds.has(element.id)) {
834
+ document.body.appendChild(element);
835
+ }
836
+ }
837
+ }
838
+
839
+ function __mreactRouteDataScriptIds(...routeIds) {
840
+ const ids = new Set();
841
+
842
+ for (const routeId of routeIds) {
843
+ if (typeof routeId !== "string" || routeId === "") {
844
+ continue;
845
+ }
846
+
847
+ ids.add(\`mreact-props-\${routeId}\`);
848
+ ids.add(\`mreact-client-references-\${routeId}\`);
849
+ }
850
+
851
+ return ids;
852
+ }
853
+
854
+ function __mreactRouteDataScriptSelector() {
855
+ return 'script[type="application/json"][id^="mreact-props-"], script[type="application/json"][id^="mreact-client-references-"]';
856
+ }
857
+
630
858
  function __mreactCurrentHistoryState(url) {
631
859
  return {
632
860
  __mreact: true,
@@ -661,6 +889,18 @@ function __mreactSaveCurrentHistoryState() {
661
889
  }
662
890
  }
663
891
 
892
+ function __mreactEnableManualScrollRestoration() {
893
+ if (typeof history === "undefined" || !("scrollRestoration" in history)) {
894
+ return;
895
+ }
896
+
897
+ try {
898
+ history.scrollRestoration = "manual";
899
+ } catch {
900
+ // Ignore read-only history implementations in non-browser runtimes.
901
+ }
902
+ }
903
+
664
904
  function __mreactNormalizeNavigationUrl(url) {
665
905
  if (typeof location === "undefined") {
666
906
  return typeof url === "string" ? url : undefined;
@@ -673,20 +913,156 @@ function __mreactNormalizeNavigationUrl(url) {
673
913
  }
674
914
  }
675
915
 
916
+ function __mreactNormalizeAssetUrl(url) {
917
+ if (typeof location === "undefined") {
918
+ return typeof url === "string" ? url : undefined;
919
+ }
920
+
921
+ try {
922
+ return new URL(url, location.href).href;
923
+ } catch {
924
+ return undefined;
925
+ }
926
+ }
927
+
928
+ function __mreactRouteScriptForNavigationUrl(url) {
929
+ const href = __mreactNormalizeNavigationUrl(url);
930
+
931
+ if (href === undefined || typeof location === "undefined") {
932
+ return undefined;
933
+ }
934
+
935
+ let nextUrl;
936
+
937
+ try {
938
+ nextUrl = new URL(href, location.href);
939
+ } catch {
940
+ return undefined;
941
+ }
942
+
943
+ if (nextUrl.origin !== location.origin) {
944
+ return undefined;
945
+ }
946
+
947
+ for (const route of __mreactClientRoutePrefetchManifest()) {
948
+ if (__mreactRoutePathMatches(route.path, nextUrl.pathname)) {
949
+ return route.script;
950
+ }
951
+ }
952
+
953
+ return undefined;
954
+ }
955
+
956
+ function __mreactClientRoutePrefetchManifest() {
957
+ const element = typeof document === "undefined"
958
+ ? null
959
+ : document.getElementById("mreact-route-prefetch-manifest");
960
+ const text = element?.textContent ?? "";
961
+
962
+ if (
963
+ __mreactNavigationState.routePrefetchManifest !== undefined &&
964
+ __mreactNavigationState.routePrefetchManifestText === text
965
+ ) {
966
+ return __mreactNavigationState.routePrefetchManifest;
967
+ }
968
+
969
+ __mreactNavigationState.routePrefetchManifestText = text;
970
+
971
+ if (element === null) {
972
+ __mreactNavigationState.routePrefetchManifest = [];
973
+ return __mreactNavigationState.routePrefetchManifest;
974
+ }
975
+
976
+ try {
977
+ const parsed = JSON.parse(text);
978
+ __mreactNavigationState.routePrefetchManifest = Array.isArray(parsed)
979
+ ? parsed.filter((route) =>
980
+ route !== null &&
981
+ typeof route === "object" &&
982
+ typeof route.path === "string" &&
983
+ typeof route.script === "string"
984
+ )
985
+ : [];
986
+ } catch {
987
+ __mreactNavigationState.routePrefetchManifest = [];
988
+ }
989
+
990
+ return __mreactNavigationState.routePrefetchManifest;
991
+ }
992
+
993
+ function __mreactRoutePathMatches(routePath, pathname) {
994
+ const routeSegments = __mreactNormalizeRoutePath(routePath);
995
+ const pathSegments = __mreactNormalizeRoutePath(pathname);
996
+
997
+ if (routeSegments.length === 0) {
998
+ return pathSegments.length === 0;
999
+ }
1000
+
1001
+ for (const [index, segment] of routeSegments.entries()) {
1002
+ const value = pathSegments[index];
1003
+
1004
+ if (segment.startsWith(":...")) {
1005
+ return pathSegments.length >= index + 1;
1006
+ }
1007
+
1008
+ if (value === undefined) {
1009
+ return false;
1010
+ }
1011
+
1012
+ if (!segment.startsWith(":") && segment !== value) {
1013
+ return false;
1014
+ }
1015
+ }
1016
+
1017
+ return routeSegments.length === pathSegments.length;
1018
+ }
1019
+
1020
+ function __mreactNormalizeRoutePath(path) {
1021
+ const normalized = path.length > 1 ? path.replace(/\\/+$/, "") : path;
1022
+ return normalized === "/" || normalized === "" ? [] : normalized.replace(/^\\/+/, "").split("/");
1023
+ }
1024
+
1025
+ function __mreactCanPrefetch() {
1026
+ if (typeof navigator === "undefined") {
1027
+ return true;
1028
+ }
1029
+
1030
+ const connection = navigator.connection ?? navigator.mozConnection ?? navigator.webkitConnection;
1031
+ const effectiveType = typeof connection?.effectiveType === "string"
1032
+ ? connection.effectiveType.toLowerCase()
1033
+ : "";
1034
+
1035
+ return connection?.saveData !== true && effectiveType !== "slow-2g" && effectiveType !== "2g";
1036
+ }
1037
+
676
1038
  function __mreactScrollTo(x, y) {
677
1039
  if (typeof scrollTo === "function") {
678
1040
  scrollTo(x, y);
679
1041
  }
680
1042
  }
681
1043
 
1044
+ function __mreactIsHashOnlyNavigation(nextUrl) {
1045
+ if (typeof location === "undefined") {
1046
+ return false;
1047
+ }
1048
+
1049
+ return nextUrl.origin === location.origin &&
1050
+ nextUrl.pathname === location.pathname &&
1051
+ nextUrl.search === location.search &&
1052
+ nextUrl.hash !== "" &&
1053
+ nextUrl.hash !== location.hash;
1054
+ }
1055
+
682
1056
  function __mreactInstallNavigation() {
683
1057
  if (__mreactNavigationState.installed || typeof document === "undefined") {
684
1058
  return;
685
1059
  }
686
1060
 
687
1061
  __mreactNavigationState.installed = true;
1062
+ __mreactEnableManualScrollRestoration();
688
1063
  __mreactSaveCurrentHistoryState();
689
1064
  addEventListener("popstate", (event) => {
1065
+ __mreactSaveCurrentHistoryState();
690
1066
  if (!__mreactRestoreHistoryState(event.state)) {
691
1067
  location.reload();
692
1068
  }
@@ -694,17 +1070,25 @@ function __mreactInstallNavigation() {
694
1070
  document.addEventListener("pointerenter", (event) => {
695
1071
  const anchor = __mreactAnchorFromEvent(event);
696
1072
 
697
- if (anchor !== null && anchor.dataset.mreactPrefetch !== "false") {
1073
+ if (anchor !== null && __mreactAnchorPrefetchMode(anchor) === "intent") {
1074
+ void __mreactPrefetch(anchor.href);
1075
+ }
1076
+ }, true);
1077
+ document.addEventListener("pointerdown", (event) => {
1078
+ const anchor = __mreactAnchorFromEvent(event);
1079
+
1080
+ if (anchor !== null && __mreactAnchorPrefetchMode(anchor) === "intent") {
698
1081
  void __mreactPrefetch(anchor.href);
699
1082
  }
700
1083
  }, true);
701
1084
  document.addEventListener("focusin", (event) => {
702
1085
  const anchor = __mreactAnchorFromEvent(event);
703
1086
 
704
- if (anchor !== null && anchor.dataset.mreactPrefetch !== "false") {
1087
+ if (anchor !== null && __mreactAnchorPrefetchMode(anchor) === "intent") {
705
1088
  void __mreactPrefetch(anchor.href);
706
1089
  }
707
1090
  });
1091
+ __mreactObserveViewportPrefetchAnchors(document);
708
1092
  document.addEventListener("click", (event) => {
709
1093
  if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
710
1094
  return;
@@ -716,14 +1100,25 @@ function __mreactInstallNavigation() {
716
1100
  return;
717
1101
  }
718
1102
 
1103
+ if (anchor.dataset.mreactReload === "true") {
1104
+ return;
1105
+ }
1106
+
719
1107
  const nextUrl = new URL(anchor.href, location.href);
720
1108
 
721
1109
  if (nextUrl.origin !== location.origin) {
722
1110
  return;
723
1111
  }
724
1112
 
1113
+ if (__mreactIsHashOnlyNavigation(nextUrl)) {
1114
+ return;
1115
+ }
1116
+
725
1117
  event.preventDefault();
726
- void __mreactNavigate(nextUrl.href)
1118
+ void __mreactNavigate(nextUrl.href, {
1119
+ scroll: __mreactAnchorScrollMode(anchor),
1120
+ transition: __mreactAnchorTransitionMode(anchor),
1121
+ })
727
1122
  .then((navigated) => {
728
1123
  if (!navigated) {
729
1124
  location.href = nextUrl.href;
@@ -744,6 +1139,52 @@ function __mreactAnchorFromEvent(event) {
744
1139
 
745
1140
  return anchor;
746
1141
  }
1142
+
1143
+ function __mreactAnchorPrefetchMode(anchor) {
1144
+ const value = anchor.dataset.mreactPrefetch;
1145
+
1146
+ if (value === "false" || value === "none") {
1147
+ return "none";
1148
+ }
1149
+
1150
+ return value === "viewport" ? "viewport" : "intent";
1151
+ }
1152
+
1153
+ function __mreactAnchorScrollMode(anchor) {
1154
+ return anchor.dataset.mreactScroll === "preserve" ? "preserve" : "top";
1155
+ }
1156
+
1157
+ function __mreactAnchorTransitionMode(anchor) {
1158
+ return anchor.dataset.mreactTransition === "auto" ? "auto" : "none";
1159
+ }
1160
+
1161
+ function __mreactObserveViewportPrefetchAnchors(root) {
1162
+ if (typeof IntersectionObserver === "undefined") {
1163
+ return;
1164
+ }
1165
+
1166
+ if (__mreactNavigationState.viewportObserver === undefined) {
1167
+ __mreactNavigationState.viewportObserver = new IntersectionObserver((entries) => {
1168
+ for (const entry of entries) {
1169
+ if (!entry.isIntersecting || !(entry.target instanceof HTMLAnchorElement)) {
1170
+ continue;
1171
+ }
1172
+
1173
+ __mreactNavigationState.viewportObserver?.unobserve(entry.target);
1174
+ void __mreactPrefetch(entry.target.href);
1175
+ }
1176
+ });
1177
+ }
1178
+
1179
+ for (const anchor of Array.from(root.querySelectorAll('a[href][data-mreact-prefetch="viewport"]'))) {
1180
+ if (!(anchor instanceof HTMLAnchorElement) || __mreactNavigationState.viewportAnchors.has(anchor)) {
1181
+ continue;
1182
+ }
1183
+
1184
+ __mreactNavigationState.viewportAnchors.add(anchor);
1185
+ __mreactNavigationState.viewportObserver.observe(anchor);
1186
+ }
1187
+ }
747
1188
  `
748
1189
  : ""}
749
1190
 
@@ -1051,24 +1492,47 @@ function __mreactResumeChildren(current, next) {
1051
1492
  };
1052
1493
  }
1053
1494
  function workspaceRuntimePlugin(options) {
1054
- const rootDir = join(dirname(fileURLToPath(import.meta.url)), "../../..");
1055
1495
  const routeDir = dirname(options.routeFile);
1056
- const reactiveCorePath = join(rootDir, "packages/reactive-core/src/index.ts");
1057
- const packageFile = (packageName, basename) => join(rootDir, "packages", packageName, "src", `${basename}.ts`);
1496
+ const packageFile = (monorepoDir, packageName, entry) => workspacePackageFile({
1497
+ currentFileUrl: import.meta.url,
1498
+ entry,
1499
+ monorepoDir,
1500
+ packageName,
1501
+ });
1502
+ const reactiveCorePath = packageFile("reactive-core", "@reckona/mreact-reactive-core", "index");
1503
+ const reactiveCoreDir = dirname(reactiveCorePath);
1058
1504
  const runtimePaths = new Map([
1059
- ["@reckona/mreact-compat", packageFile("react-compat", "index")],
1060
- ["@reckona/mreact-compat/event-priority", packageFile("react-compat", "event-priority")],
1061
- ["@reckona/mreact-compat/flight", packageFile("react-compat", "flight")],
1062
- ["@reckona/mreact-compat/internal", packageFile("react-compat", "internal")],
1063
- ["@reckona/mreact-compat/jsx-dev-runtime", packageFile("react-compat", "jsx-dev-runtime")],
1064
- ["@reckona/mreact-compat/jsx-runtime", packageFile("react-compat", "jsx-runtime")],
1065
- ["@reckona/mreact-compat/scheduler", packageFile("react-compat", "scheduler")],
1066
- ["@reckona/mreact-reactive-dom", join(rootDir, "packages/reactive-dom/src/index.ts")],
1505
+ ["@reckona/mreact-compat", packageFile("react-compat", "@reckona/mreact-compat", "index")],
1506
+ [
1507
+ "@reckona/mreact-compat/event-priority",
1508
+ packageFile("react-compat", "@reckona/mreact-compat", "event-priority"),
1509
+ ],
1510
+ ["@reckona/mreact-compat/flight", packageFile("react-compat", "@reckona/mreact-compat", "flight")],
1511
+ [
1512
+ "@reckona/mreact-compat/internal",
1513
+ packageFile("react-compat", "@reckona/mreact-compat", "internal"),
1514
+ ],
1515
+ [
1516
+ "@reckona/mreact-compat/jsx-dev-runtime",
1517
+ packageFile("react-compat", "@reckona/mreact-compat", "jsx-dev-runtime"),
1518
+ ],
1519
+ [
1520
+ "@reckona/mreact-compat/jsx-runtime",
1521
+ packageFile("react-compat", "@reckona/mreact-compat", "jsx-runtime"),
1522
+ ],
1523
+ [
1524
+ "@reckona/mreact-compat/scheduler",
1525
+ packageFile("react-compat", "@reckona/mreact-compat", "scheduler"),
1526
+ ],
1527
+ [
1528
+ "@reckona/mreact-reactive-dom",
1529
+ packageFile("reactive-dom", "@reckona/mreact-reactive-dom", "index"),
1530
+ ],
1067
1531
  ]);
1068
1532
  return {
1069
1533
  name: "mreact-workspace-runtime",
1070
1534
  setup(buildApi) {
1071
- buildApi.onResolve({ filter: /^\.\/devtools\.js$/ }, (args) => args.importer?.startsWith(join(rootDir, "packages/reactive-core/src/")) === true
1535
+ buildApi.onResolve({ filter: /^\.\/devtools\.js$/ }, (args) => args.importer?.startsWith(reactiveCoreDir) === true
1072
1536
  ? { namespace: "mreact-devtools-stub", path: "devtools" }
1073
1537
  : undefined);
1074
1538
  buildApi.onResolve({ filter: /^@reckona\/mreact-reactive-core$/ }, () => ({
@@ -1089,7 +1553,7 @@ export function cell(initial) {
1089
1553
  return typeof routeCell === "function" ? routeCell(nativeCell, initial) : nativeCell(initial);
1090
1554
  }`,
1091
1555
  loader: "ts",
1092
- resolveDir: rootDir,
1556
+ resolveDir: reactiveCoreDir,
1093
1557
  }));
1094
1558
  buildApi.onLoad({ filter: /^devtools$/, namespace: "mreact-devtools-stub" }, () => ({
1095
1559
  contents: `export function emitReactiveDevtoolsEvent() {}
@@ -1180,6 +1644,22 @@ function emitClientReferenceRegistry(manifest) {
1180
1644
  function clientReferenceExpression(name) {
1181
1645
  return /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*$/.test(name) ? name : undefined;
1182
1646
  }
1647
+ function routeComponentExpressionForComponents(components) {
1648
+ const candidates = uniqueStrings([
1649
+ ...components
1650
+ .filter((component) => component.exportName === "default")
1651
+ .map((component) => component.name),
1652
+ "Page",
1653
+ "DefaultExport",
1654
+ ]).filter(isIdentifierName);
1655
+ return candidates.reduceRight((next, name) => `typeof ${name} === "function" ? ${name} : ${next}`, "undefined");
1656
+ }
1657
+ function uniqueStrings(values) {
1658
+ return Array.from(new Set(values));
1659
+ }
1660
+ function isIdentifierName(value) {
1661
+ return /^[A-Za-z_$][\w$]*$/.test(value);
1662
+ }
1183
1663
  function routeStateSignatureForSource(code) {
1184
1664
  const callExpression = routeCellCallExpressionSource(code);
1185
1665
  const callsitePattern = callExpression === undefined