@reckona/mreact-router 0.0.210 → 0.0.211

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.
package/dist/client.js CHANGED
@@ -1924,6 +1924,7 @@ export async function buildClientRouteEntrySource(options) {
1924
1924
  const navigationStateDeclaration = inlineClientNavigation
1925
1925
  ? `const __mreactNavigationState = __mreactGlobal.__mreactNavigationState ??= {
1926
1926
  cache: new Map(),
1927
+ cacheTokens: new Map(),
1927
1928
  current: {
1928
1929
  from: null,
1929
1930
  pending: false,
@@ -1932,6 +1933,7 @@ export async function buildClientRouteEntrySource(options) {
1932
1933
  },
1933
1934
  fetchRevalidationInstalled: false,
1934
1935
  installed: false,
1936
+ navigationFetchInits: new WeakSet(),
1935
1937
  pendingHtmlFetches: new Map(),
1936
1938
  prefetchedUrls: new Set(),
1937
1939
  prefetchedScripts: new Set(),
@@ -1940,7 +1942,9 @@ export async function buildClientRouteEntrySource(options) {
1940
1942
  routePrefetchManifestText: undefined,
1941
1943
  viewportAnchors: new WeakSet(),
1942
1944
  viewportObserver: undefined,
1943
- };`
1945
+ };
1946
+ __mreactNavigationState.cacheTokens ??= new Map();
1947
+ __mreactNavigationState.navigationFetchInits ??= new WeakSet();`
1944
1948
  : "";
1945
1949
  const deferredNavigationRuntime = deferredClientNavigation
1946
1950
  ? `
@@ -2103,10 +2107,10 @@ export async function __mreactNavigate(url, options = {}) {
2103
2107
  : false;
2104
2108
  }
2105
2109
 
2106
- export async function __mreactPrefetch(url) {
2110
+ export async function __mreactPrefetch(url, options = {}) {
2107
2111
  const runtime = await __mreactLoadNavigationRuntime();
2108
2112
  return typeof runtime?.__mreactPrefetch === "function"
2109
- ? runtime.__mreactPrefetch(url)
2113
+ ? runtime.__mreactPrefetch(url, options)
2110
2114
  : false;
2111
2115
  }
2112
2116
 
@@ -2152,13 +2156,16 @@ __mreactGlobal.__mreactRouteCell = (nativeCell, initial) => {
2152
2156
  const stateCell = nativeCell(record.value);
2153
2157
  const setStateCell = stateCell.set;
2154
2158
 
2155
- stateCell.set = (next) => {
2159
+ const setRouteStateCell = (next) => {
2156
2160
  setStateCell((previous) => {
2157
2161
  const resolved = typeof next === "function" ? next(previous) : next;
2158
2162
  record.value = resolved;
2159
2163
  return resolved;
2160
2164
  });
2161
2165
  };
2166
+ stateCell.set = setRouteStateCell;
2167
+ stateCell.setValue = (next) => stateCell.set(() => next);
2168
+ stateCell.update = (updater) => stateCell.set(updater);
2162
2169
 
2163
2170
  __mreactActiveCellRecords.set(cellKey, record);
2164
2171
  return stateCell;
@@ -2579,7 +2586,7 @@ function __mreactRouteAnnouncementElement() {
2579
2586
  return announcement;
2580
2587
  }
2581
2588
 
2582
- export async function __mreactPrefetch(url) {
2589
+ export async function __mreactPrefetch(url, options = {}) {
2583
2590
  if (!__mreactCanPrefetch()) {
2584
2591
  return false;
2585
2592
  }
@@ -2598,10 +2605,6 @@ export async function __mreactPrefetch(url) {
2598
2605
  return false;
2599
2606
  }
2600
2607
 
2601
- if (__mreactNavigationState.prefetchedUrls.has(href)) {
2602
- return true;
2603
- }
2604
-
2605
2608
  __mreactRememberPrefetchedUrl(href);
2606
2609
 
2607
2610
  const script = __mreactRouteScriptForNavigationUrl(href);
@@ -2610,21 +2613,30 @@ export async function __mreactPrefetch(url) {
2610
2613
  return __mreactPrefetchNavigationHtml(href);
2611
2614
  }
2612
2615
 
2613
- return __mreactPrefetchRouteScript(script);
2616
+ const scriptPrefetched = __mreactPrefetchRouteScript(script);
2617
+
2618
+ if (options?.trigger === "viewport") {
2619
+ return scriptPrefetched;
2620
+ }
2621
+
2622
+ return Promise.resolve(__mreactPrefetchNavigationHtml(href))
2623
+ .then(() => scriptPrefetched)
2624
+ .catch(() => scriptPrefetched);
2614
2625
  }
2615
2626
 
2616
2627
  function __mreactPrefetchNavigationHtml(href) {
2617
- if (__mreactNavigationState.cache.has(href)) {
2628
+ if (__mreactCachedNavigationHtml(href) !== undefined) {
2618
2629
  return true;
2619
2630
  }
2620
2631
 
2621
- return __mreactFetchNavigationHtmlOnce(href)
2622
- .then((html) => typeof html === "string")
2632
+ return __mreactFetchNavigationHtmlOnce(href, { speculative: true })
2633
+ .then((result) => result.cacheable && typeof result.html === "string")
2623
2634
  .catch(() => false);
2624
2635
  }
2625
2636
 
2626
2637
  const __mreactNavigationHtmlCacheMaxEntries = 64;
2627
2638
  const __mreactNavigationPrefetchHistoryMaxEntries = 64;
2639
+ const __mreactNavigationHtmlCacheTtlMs = 30_000;
2628
2640
 
2629
2641
  function __mreactRememberPrefetchedUrl(href) {
2630
2642
  __mreactRememberPrefetchHistory(__mreactNavigationState.prefetchedUrls, href);
@@ -2653,23 +2665,49 @@ function __mreactRememberPrefetchHistory(history, href) {
2653
2665
  }
2654
2666
 
2655
2667
  function __mreactCachedNavigationHtml(href) {
2656
- const html = __mreactNavigationState.cache.get(href);
2668
+ const entry = __mreactNavigationState.cache.get(href);
2657
2669
 
2658
- if (html === undefined) {
2670
+ if (entry === undefined) {
2671
+ return undefined;
2672
+ }
2673
+
2674
+ if (
2675
+ entry.token !== __mreactNavigationState.cacheTokens.get(href) ||
2676
+ entry.token.valid !== true ||
2677
+ entry.cacheContext !== __mreactNavigationCacheContext() ||
2678
+ Date.now() - entry.fetchedAt >= __mreactNavigationHtmlCacheTtlMs
2679
+ ) {
2680
+ __mreactNavigationState.cache.delete(href);
2681
+ __mreactNavigationState.prefetchedUrls.delete(href);
2682
+ __mreactReleaseNavigationCacheToken(href, entry.token);
2659
2683
  return undefined;
2660
2684
  }
2661
2685
 
2662
2686
  __mreactNavigationState.cache.delete(href);
2663
- __mreactNavigationState.cache.set(href, html);
2664
- return html;
2687
+ __mreactNavigationState.cache.set(href, entry);
2688
+ return entry.html;
2665
2689
  }
2666
2690
 
2667
- function __mreactRememberNavigationHtml(href, html) {
2691
+ function __mreactRememberNavigationHtml(href, html, token, cacheContext) {
2692
+ if (
2693
+ token === undefined ||
2694
+ token.valid !== true ||
2695
+ __mreactNavigationState.cacheTokens.get(href) !== token ||
2696
+ cacheContext !== __mreactNavigationCacheContext()
2697
+ ) {
2698
+ return;
2699
+ }
2700
+
2668
2701
  if (__mreactNavigationState.cache.has(href)) {
2669
2702
  __mreactNavigationState.cache.delete(href);
2670
2703
  }
2671
2704
 
2672
- __mreactNavigationState.cache.set(href, html);
2705
+ __mreactNavigationState.cache.set(href, {
2706
+ cacheContext,
2707
+ fetchedAt: Date.now(),
2708
+ html,
2709
+ token,
2710
+ });
2673
2711
 
2674
2712
  while (__mreactNavigationState.cache.size > __mreactNavigationHtmlCacheMaxEntries) {
2675
2713
  const oldestHref = __mreactNavigationState.cache.keys().next().value;
@@ -2680,37 +2718,100 @@ function __mreactRememberNavigationHtml(href, html) {
2680
2718
 
2681
2719
  __mreactNavigationState.cache.delete(oldestHref);
2682
2720
  __mreactNavigationState.prefetchedUrls.delete(oldestHref);
2721
+ __mreactReleaseNavigationCacheToken(oldestHref);
2683
2722
  }
2684
2723
  }
2685
2724
 
2686
- function __mreactPrefetchRouteScript(script) {
2687
- if (typeof document === "undefined") {
2688
- return false;
2725
+ function __mreactNavigationCacheContext() {
2726
+ const authSession = typeof document === "undefined"
2727
+ ? undefined
2728
+ : document.getElementById("__mreact_auth_session")?.textContent ?? undefined;
2729
+ const cookies = __mreactNavigationCookies();
2730
+
2731
+ return JSON.stringify([authSession, cookies]);
2732
+ }
2733
+
2734
+ function __mreactNavigationCookies() {
2735
+ return typeof document === "undefined" ? "" : document.cookie;
2736
+ }
2737
+
2738
+ function __mreactNavigationResponseCacheContext(html, requestCookies) {
2739
+ let authSession;
2740
+
2741
+ if (typeof document !== "undefined") {
2742
+ const template = document.createElement("template");
2743
+ template.innerHTML = html;
2744
+ authSession = template.content.querySelector("#__mreact_auth_session")?.textContent ?? undefined;
2745
+ }
2746
+
2747
+ return JSON.stringify([authSession, requestCookies]);
2748
+ }
2749
+
2750
+ function __mreactReleaseNavigationCacheToken(href, token) {
2751
+ if (
2752
+ __mreactNavigationState.cache.get(href) === undefined &&
2753
+ __mreactNavigationState.pendingHtmlFetches.get(href) === undefined &&
2754
+ (token === undefined || __mreactNavigationState.cacheTokens.get(href) === token)
2755
+ ) {
2756
+ __mreactNavigationState.cacheTokens.delete(href);
2689
2757
  }
2758
+ }
2690
2759
 
2691
- const href = __mreactNormalizeAssetUrl(script);
2760
+ function __mreactNavigationCacheToken(href) {
2761
+ const existing = __mreactNavigationState.cacheTokens.get(href);
2692
2762
 
2693
- if (href === undefined) {
2694
- return false;
2763
+ if (existing !== undefined) {
2764
+ return existing;
2695
2765
  }
2696
2766
 
2697
- if (__mreactNavigationState.prefetchedScripts.has(href)) {
2698
- return true;
2767
+ const token = { valid: true };
2768
+ __mreactNavigationState.cacheTokens.set(href, token);
2769
+ return token;
2770
+ }
2771
+
2772
+ function __mreactPrefetchRouteScript(route) {
2773
+ if (typeof document === "undefined") {
2774
+ return false;
2699
2775
  }
2700
2776
 
2701
- for (const link of Array.from(document.querySelectorAll('link[rel="modulepreload"][href]'))) {
2702
- if (link.href === href) {
2703
- __mreactRememberPrefetchedScript(href);
2704
- return true;
2777
+ const files = [route.script, ...(route.modulePreloads ?? [])];
2778
+ let valid = true;
2779
+
2780
+ for (const file of files) {
2781
+ const href = __mreactNormalizeAssetUrl(file);
2782
+
2783
+ if (
2784
+ href === undefined ||
2785
+ !__mreactIsSupportedModulePreloadUrl(file) ||
2786
+ !__mreactModulePreloadMatchesRouteScript(file, route.script)
2787
+ ) {
2788
+ valid = false;
2789
+ continue;
2705
2790
  }
2791
+
2792
+ if (__mreactNavigationState.prefetchedScripts.has(href)) {
2793
+ continue;
2794
+ }
2795
+
2796
+ let found = false;
2797
+ for (const link of Array.from(document.querySelectorAll('link[rel="modulepreload"][href]'))) {
2798
+ if (link.href === href) {
2799
+ found = true;
2800
+ break;
2801
+ }
2802
+ }
2803
+
2804
+ if (!found) {
2805
+ const link = document.createElement("link");
2806
+ link.rel = "modulepreload";
2807
+ link.href = href;
2808
+ document.head.appendChild(link);
2809
+ }
2810
+
2811
+ __mreactRememberPrefetchedScript(href);
2706
2812
  }
2707
2813
 
2708
- const link = document.createElement("link");
2709
- link.rel = "modulepreload";
2710
- link.href = href;
2711
- document.head.appendChild(link);
2712
- __mreactRememberPrefetchedScript(href);
2713
- return true;
2814
+ return valid;
2714
2815
  }
2715
2816
 
2716
2817
  export async function __mreactNavigate(url, options = {}) {
@@ -2723,19 +2824,46 @@ export async function __mreactNavigate(url, options = {}) {
2723
2824
  __mreactSetNavigationState(__mreactPendingNavigationState(href, options.type ?? "push"));
2724
2825
 
2725
2826
  try {
2726
- const cachedHtml = __mreactCachedNavigationHtml(href);
2727
- const script = __mreactRouteScriptForNavigationUrl(href);
2728
- if (script !== undefined) {
2729
- void __mreactPrefetchRouteScript(script);
2730
- }
2731
- const html = cachedHtml ?? await __mreactFetchNavigationHtmlOnce(href);
2827
+ let forceReload = __mreactNavigationState.reloadNextNavigationFetch === true;
2732
2828
 
2733
- if (typeof html !== "string") {
2734
- return false;
2735
- }
2829
+ for (;;) {
2830
+ const cachedHtml = forceReload ? undefined : __mreactCachedNavigationHtml(href);
2831
+ const script = __mreactRouteScriptForNavigationUrl(href);
2832
+ if (script !== undefined) {
2833
+ void __mreactPrefetchRouteScript(script);
2834
+ }
2835
+ const result = cachedHtml === undefined
2836
+ ? await __mreactFetchNavigationHtmlOnce(href, { force: forceReload })
2837
+ : {
2838
+ cacheable: true,
2839
+ cacheContext: __mreactNavigationCacheContext(),
2840
+ cacheContextMatches: true,
2841
+ html: cachedHtml,
2842
+ reusable: true,
2843
+ speculative: false,
2844
+ token: undefined,
2845
+ };
2846
+
2847
+ if (result.token !== undefined && result.token.valid !== true) {
2848
+ forceReload = __mreactNavigationState.reloadNextNavigationFetch === true;
2849
+ continue;
2850
+ }
2736
2851
 
2737
- __mreactRememberNavigationHtml(href, html);
2738
- return await __mreactApplyNavigationHtmlWithOptionalTransition(html, href, options);
2852
+ if (result.speculative === true && result.cacheable !== true) {
2853
+ forceReload = true;
2854
+ continue;
2855
+ }
2856
+
2857
+ if (result.reusable === true && result.cacheContextMatches !== true) {
2858
+ return false;
2859
+ }
2860
+
2861
+ if (typeof result.html !== "string") {
2862
+ return false;
2863
+ }
2864
+
2865
+ return await __mreactApplyNavigationHtmlWithOptionalTransition(result.html, href, options);
2866
+ }
2739
2867
  } finally {
2740
2868
  __mreactSetNavigationState(__mreactIdleNavigationState());
2741
2869
  }
@@ -2870,7 +2998,7 @@ function __mreactViewTransitionsAllowed(transition) {
2870
2998
  }
2871
2999
  }
2872
3000
 
2873
- export function __mreactInvalidateNavigationCache(path) {
3001
+ export function __mreactInvalidateNavigationCache(path, excludedRequest) {
2874
3002
  const normalizedPath = __mreactNormalizeNavigationPath(path);
2875
3003
 
2876
3004
  if (normalizedPath === undefined) {
@@ -2884,64 +3012,158 @@ export function __mreactInvalidateNavigationCache(path) {
2884
3012
 
2885
3013
  for (const href of hrefs) {
2886
3014
  if (__mreactNormalizeNavigationPath(href) === normalizedPath) {
3015
+ const token = __mreactNavigationState.cacheTokens.get(href);
3016
+ if (
3017
+ excludedRequest?.href === href &&
3018
+ excludedRequest.token === token &&
3019
+ token?.valid === true
3020
+ ) {
3021
+ continue;
3022
+ }
3023
+ if (token !== undefined) {
3024
+ token.valid = false;
3025
+ }
2887
3026
  __mreactNavigationState.cache.delete(href);
2888
3027
  __mreactNavigationState.prefetchedUrls.delete(href);
2889
3028
  __mreactNavigationState.pendingHtmlFetches.delete(href);
3029
+ __mreactNavigationState.cacheTokens.delete(href);
2890
3030
  }
2891
3031
  }
2892
3032
  }
2893
3033
 
2894
3034
  function __mreactInvalidateAllNavigationCache() {
3035
+ for (const entry of __mreactNavigationState.cache.values()) {
3036
+ entry.token.valid = false;
3037
+ }
3038
+ for (const request of __mreactNavigationState.pendingHtmlFetches.values()) {
3039
+ request.token.valid = false;
3040
+ }
2895
3041
  __mreactNavigationState.cache.clear();
3042
+ __mreactNavigationState.cacheTokens.clear();
2896
3043
  __mreactNavigationState.prefetchedUrls.clear();
2897
3044
  __mreactNavigationState.pendingHtmlFetches.clear();
2898
3045
  }
2899
3046
 
2900
- function __mreactFetchNavigationHtmlOnce(href) {
3047
+ function __mreactFetchNavigationHtmlOnce(href, options = {}) {
2901
3048
  const pending = __mreactNavigationState.pendingHtmlFetches.get(href);
2902
3049
 
3050
+ if (pending !== undefined && options.force !== true) {
3051
+ return pending.promise;
3052
+ }
3053
+
2903
3054
  if (pending !== undefined) {
2904
- return pending;
3055
+ pending.token.valid = false;
3056
+ __mreactNavigationState.pendingHtmlFetches.delete(href);
3057
+ __mreactNavigationState.cacheTokens.delete(href);
2905
3058
  }
2906
3059
 
2907
- const request = __mreactFetchNavigationHtml(href)
2908
- .then((html) => {
3060
+ const token = __mreactNavigationCacheToken(href);
3061
+ const pendingRequest = {
3062
+ promise: undefined,
3063
+ token,
3064
+ };
3065
+ const request = __mreactFetchNavigationHtml(href, options, pendingRequest)
3066
+ .then((result) => {
3067
+ const enriched = {
3068
+ ...result,
3069
+ speculative: options.speculative === true,
3070
+ token,
3071
+ };
2909
3072
  if (
2910
- typeof html === "string" &&
2911
- __mreactNavigationState.pendingHtmlFetches.get(href) === request
3073
+ enriched.cacheable === true &&
3074
+ typeof enriched.html === "string" &&
3075
+ __mreactNavigationState.pendingHtmlFetches.get(href) === pendingRequest &&
3076
+ token.valid === true &&
3077
+ __mreactNavigationState.cacheTokens.get(href) === token
2912
3078
  ) {
2913
- __mreactRememberNavigationHtml(href, html);
3079
+ __mreactRememberNavigationHtml(href, enriched.html, token, enriched.cacheContext);
2914
3080
  }
2915
3081
 
2916
- return html;
3082
+ return enriched;
2917
3083
  })
2918
3084
  .finally(() => {
2919
- if (__mreactNavigationState.pendingHtmlFetches.get(href) === request) {
3085
+ if (__mreactNavigationState.pendingHtmlFetches.get(href) === pendingRequest) {
2920
3086
  __mreactNavigationState.pendingHtmlFetches.delete(href);
3087
+ __mreactReleaseNavigationCacheToken(href, token);
2921
3088
  }
2922
3089
  });
2923
3090
 
2924
- __mreactNavigationState.pendingHtmlFetches.set(href, request);
3091
+ pendingRequest.promise = request;
3092
+ __mreactNavigationState.pendingHtmlFetches.set(href, pendingRequest);
2925
3093
  return request;
2926
3094
  }
2927
3095
 
2928
- function __mreactFetchNavigationHtml(href) {
2929
- const reloadRouteCache = __mreactNavigationState.reloadNextNavigationFetch === true;
2930
- __mreactNavigationState.reloadNextNavigationFetch = false;
3096
+ function __mreactFetchNavigationHtml(href, options = {}, pendingRequest) {
3097
+ const speculative = options.speculative === true;
3098
+ const reloadRouteCache = !speculative && __mreactNavigationState.reloadNextNavigationFetch === true;
3099
+ if (!speculative) {
3100
+ __mreactNavigationState.reloadNextNavigationFetch = false;
3101
+ }
2931
3102
  const headers = reloadRouteCache
2932
3103
  ? { "x-mreact-navigation": "1", "x-mreact-navigation-cache": "reload" }
2933
3104
  : { "x-mreact-navigation": "1" };
3105
+ const requestCacheContext = __mreactNavigationCacheContext();
3106
+ const requestCookies = __mreactNavigationCookies();
3107
+ const navigationRequestInit = { headers };
3108
+ __mreactNavigationState.navigationFetchInits.add(navigationRequestInit);
3109
+
3110
+ return fetch(__mreactStaticNavigationRequestHref(href), navigationRequestInit).then((response) => {
3111
+ const currentRequest =
3112
+ pendingRequest !== undefined &&
3113
+ __mreactNavigationState.pendingHtmlFetches.get(href) === pendingRequest &&
3114
+ __mreactNavigationState.cacheTokens.get(href) === pendingRequest.token &&
3115
+ pendingRequest.token.valid === true
3116
+ ? { href, token: pendingRequest.token }
3117
+ : undefined;
3118
+ if (pendingRequest === undefined || currentRequest !== undefined) {
3119
+ __mreactApplyRevalidationHeader(response, currentRequest);
3120
+ }
3121
+ const requiresDocumentReload = __mreactNavigationResponseRequiresDocumentReload(response);
3122
+ const responseIsSuccessful =
3123
+ response.ok === true ||
3124
+ (response.ok === undefined && response.status >= 200 && response.status < 300);
3125
+ const responseIsRedirected = response.redirected === true;
3126
+ const cacheControl = (response.headers.get("cache-control") ?? "").toLowerCase();
3127
+ const responseDisallowsCache = /(?:^|,)\\s*(?:no-store|no-cache)(?:\\s*=|\\s*(?:,|$))/.test(
3128
+ cacheControl,
3129
+ );
3130
+ const reusable =
3131
+ !requiresDocumentReload &&
3132
+ responseIsSuccessful &&
3133
+ !responseIsRedirected &&
3134
+ !responseDisallowsCache;
3135
+
3136
+ if (requiresDocumentReload || responseIsRedirected || speculative && !reusable) {
3137
+ __mreactDiscardNavigationResponse(response);
3138
+ return { cacheable: false, html: undefined };
3139
+ }
3140
+
3141
+ return response.text().then((html) => {
3142
+ const cacheContext = __mreactNavigationResponseCacheContext(html, requestCookies);
3143
+ const currentCacheContext = __mreactNavigationCacheContext();
3144
+ const cacheContextMatches =
3145
+ cacheContext === currentCacheContext && requestCacheContext === currentCacheContext;
3146
+
3147
+ return {
3148
+ cacheContext,
3149
+ cacheContextMatches,
3150
+ cacheable: reusable && cacheContextMatches,
3151
+ html,
3152
+ reusable,
3153
+ };
3154
+ });
3155
+ });
3156
+ }
2934
3157
 
2935
- return fetch(__mreactStaticNavigationRequestHref(href), {
2936
- headers,
2937
- }).then((response) => {
2938
- __mreactApplyRevalidationHeader(response);
2939
- if (__mreactNavigationResponseRequiresDocumentReload(response)) {
2940
- return undefined;
3158
+ function __mreactDiscardNavigationResponse(response) {
3159
+ try {
3160
+ const result = response?.body?.cancel?.();
3161
+ if (result !== undefined && typeof result.catch === "function") {
3162
+ void result.catch(() => {});
2941
3163
  }
2942
-
2943
- return response.text();
2944
- });
3164
+ } catch {
3165
+ // Ignore body cancellation failures while falling back to a document navigation.
3166
+ }
2945
3167
  }
2946
3168
 
2947
3169
  function __mreactStaticNavigationRequestHref(href) {
@@ -2979,7 +3201,7 @@ function __mreactNavigationResponseRequiresDocumentReload(response) {
2979
3201
  return response.status === 204 || response.headers.get("x-mreact-navigation") === "reload";
2980
3202
  }
2981
3203
 
2982
- function __mreactApplyRevalidationHeader(response) {
3204
+ function __mreactApplyRevalidationHeader(response, currentRequest) {
2983
3205
  const header = typeof response?.headers?.get === "function"
2984
3206
  ? response.headers.get("x-mreact-revalidate")
2985
3207
  : null;
@@ -2988,18 +3210,34 @@ function __mreactApplyRevalidationHeader(response) {
2988
3210
  return;
2989
3211
  }
2990
3212
 
3213
+ const currentPath = __mreactNormalizeNavigationPath(currentRequest?.href);
3214
+
2991
3215
  for (const path of header.split(",")) {
2992
- __mreactInvalidateNavigationCache(path.trim());
3216
+ const trimmedPath = path.trim();
3217
+ const normalizedPath = __mreactNormalizeNavigationPath(trimmedPath);
3218
+ const excludedRequest =
3219
+ currentRequest !== undefined && normalizedPath === currentPath ? currentRequest : undefined;
3220
+ __mreactInvalidateNavigationCache(trimmedPath, excludedRequest);
2993
3221
  }
2994
3222
  }
2995
3223
 
3224
+ function __mreactIsNavigationFetchRequest(_input, init) {
3225
+ return init !== null && typeof init === "object" && __mreactNavigationState.navigationFetchInits.has(init);
3226
+ }
3227
+
2996
3228
  function __mreactNormalizeNavigationPath(path) {
3229
+ if (typeof path !== "string" || path.trim() === "") {
3230
+ return undefined;
3231
+ }
3232
+
3233
+ const normalizedInput = path.trim();
3234
+
2997
3235
  if (typeof location === "undefined") {
2998
- return typeof path === "string" && path.length > 0 ? path : undefined;
3236
+ return normalizedInput;
2999
3237
  }
3000
3238
 
3001
3239
  try {
3002
- const url = new URL(path, location.href);
3240
+ const url = new URL(normalizedInput, location.href);
3003
3241
  const pathname = url.pathname.replace(/\\/+$/, "");
3004
3242
 
3005
3243
  return pathname === "" ? "/" : pathname;
@@ -3015,7 +3253,7 @@ export function __mreactRestoreHistoryState(state) {
3015
3253
 
3016
3254
  const html = typeof state.html === "string"
3017
3255
  ? state.html
3018
- : typeof state.url === "string" ? __mreactNavigationState.cache.get(state.url) : undefined;
3256
+ : typeof state.url === "string" ? __mreactCachedNavigationHtml(state.url) : undefined;
3019
3257
 
3020
3258
  if (typeof html !== "string") {
3021
3259
  return false;
@@ -3391,7 +3629,7 @@ function __mreactRouteScriptForNavigationUrl(url) {
3391
3629
 
3392
3630
  for (const route of __mreactClientRoutePrefetchManifest()) {
3393
3631
  if (__mreactRoutePathMatches(route.path, nextUrl.pathname)) {
3394
- return route.script;
3632
+ return route;
3395
3633
  }
3396
3634
  }
3397
3635
 
@@ -3421,12 +3659,28 @@ function __mreactClientRoutePrefetchManifest() {
3421
3659
  try {
3422
3660
  const parsed = JSON.parse(text);
3423
3661
  __mreactNavigationState.routePrefetchManifest = Array.isArray(parsed)
3424
- ? parsed.filter((route) =>
3425
- route !== null &&
3426
- typeof route === "object" &&
3427
- typeof route.path === "string" &&
3428
- typeof route.script === "string"
3429
- )
3662
+ ? parsed.flatMap((route) => {
3663
+ if (
3664
+ route === null ||
3665
+ typeof route !== "object" ||
3666
+ typeof route.path !== "string" ||
3667
+ typeof route.script !== "string" ||
3668
+ !__mreactIsSupportedModulePreloadUrl(route.script)
3669
+ ) {
3670
+ return [];
3671
+ }
3672
+
3673
+ const modulePreloads = Array.isArray(route.modulePreloads)
3674
+ ? route.modulePreloads.filter(
3675
+ (file) =>
3676
+ typeof file === "string" &&
3677
+ __mreactIsSupportedModulePreloadUrl(file) &&
3678
+ __mreactModulePreloadMatchesRouteScript(file, route.script),
3679
+ )
3680
+ : [];
3681
+
3682
+ return [{ ...route, modulePreloads }];
3683
+ })
3430
3684
  : [];
3431
3685
  } catch {
3432
3686
  __mreactNavigationState.routePrefetchManifest = [];
@@ -3435,6 +3689,29 @@ function __mreactClientRoutePrefetchManifest() {
3435
3689
  return __mreactNavigationState.routePrefetchManifest;
3436
3690
  }
3437
3691
 
3692
+ function __mreactIsSupportedModulePreloadUrl(value) {
3693
+ if (typeof value !== "string" || value === "" || value.startsWith("javascript:")) {
3694
+ return false;
3695
+ }
3696
+
3697
+ try {
3698
+ const resolved = new URL(value, typeof location === "undefined" ? undefined : location.href);
3699
+ return resolved.protocol === "http:" || resolved.protocol === "https:";
3700
+ } catch {
3701
+ return false;
3702
+ }
3703
+ }
3704
+
3705
+ function __mreactModulePreloadMatchesRouteScript(value, script) {
3706
+ try {
3707
+ const scriptUrl = new URL(__mreactNormalizeAssetUrl(script), location.href);
3708
+ const dependencyUrl = new URL(__mreactNormalizeAssetUrl(value), location.href);
3709
+ return scriptUrl.origin === dependencyUrl.origin;
3710
+ } catch {
3711
+ return false;
3712
+ }
3713
+ }
3714
+
3438
3715
  function __mreactRoutePathMatches(routePath, pathname) {
3439
3716
  const routeSegments = __mreactNormalizeRoutePath(routePath);
3440
3717
  const pathSegments = __mreactNormalizeRoutePath(pathname);
@@ -3732,9 +4009,12 @@ function __mreactInstallNavigationFetchRevalidation() {
3732
4009
  const fetchImpl = globalThis.fetch;
3733
4010
  globalThis.fetch = function(input, init) {
3734
4011
  const mutating = __mreactIsMutatingFetchRequest(input, init);
4012
+ const navigation = __mreactIsNavigationFetchRequest(input, init);
3735
4013
 
3736
4014
  return Promise.resolve(fetchImpl.call(this, input, init)).then((response) => {
3737
- __mreactApplyRevalidationHeader(response);
4015
+ if (!navigation) {
4016
+ __mreactApplyRevalidationHeader(response);
4017
+ }
3738
4018
 
3739
4019
  if (mutating) {
3740
4020
  __mreactInvalidateAllNavigationCache();
@@ -3806,7 +4086,7 @@ function __mreactObserveViewportPrefetchAnchors(root) {
3806
4086
  }
3807
4087
 
3808
4088
  __mreactNavigationState.viewportObserver?.unobserve(entry.target);
3809
- void __mreactPrefetch(entry.target.href);
4089
+ void __mreactPrefetch(entry.target.href, { trigger: "viewport" });
3810
4090
  }
3811
4091
  });
3812
4092
  }