@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/src/client.ts CHANGED
@@ -61,6 +61,7 @@ export interface ClientRouteManifestEntry {
61
61
  clientReferenceManifest?: readonly ClientReferenceMetadata[] | undefined;
62
62
  devScript?: string;
63
63
  imports?: readonly string[];
64
+ modulePreloads?: readonly string[];
64
65
  navigation?: boolean;
65
66
  navigationScript?: string;
66
67
  routeId?: string;
@@ -3013,6 +3014,7 @@ export async function buildClientRouteEntrySource(
3013
3014
  const navigationStateDeclaration = inlineClientNavigation
3014
3015
  ? `const __mreactNavigationState = __mreactGlobal.__mreactNavigationState ??= {
3015
3016
  cache: new Map(),
3017
+ cacheTokens: new Map(),
3016
3018
  current: {
3017
3019
  from: null,
3018
3020
  pending: false,
@@ -3021,6 +3023,7 @@ export async function buildClientRouteEntrySource(
3021
3023
  },
3022
3024
  fetchRevalidationInstalled: false,
3023
3025
  installed: false,
3026
+ navigationFetchInits: new WeakSet(),
3024
3027
  pendingHtmlFetches: new Map(),
3025
3028
  prefetchedUrls: new Set(),
3026
3029
  prefetchedScripts: new Set(),
@@ -3029,7 +3032,9 @@ export async function buildClientRouteEntrySource(
3029
3032
  routePrefetchManifestText: undefined,
3030
3033
  viewportAnchors: new WeakSet(),
3031
3034
  viewportObserver: undefined,
3032
- };`
3035
+ };
3036
+ __mreactNavigationState.cacheTokens ??= new Map();
3037
+ __mreactNavigationState.navigationFetchInits ??= new WeakSet();`
3033
3038
  : "";
3034
3039
  const deferredNavigationRuntime = deferredClientNavigation
3035
3040
  ? `
@@ -3192,10 +3197,10 @@ export async function __mreactNavigate(url, options = {}) {
3192
3197
  : false;
3193
3198
  }
3194
3199
 
3195
- export async function __mreactPrefetch(url) {
3200
+ export async function __mreactPrefetch(url, options = {}) {
3196
3201
  const runtime = await __mreactLoadNavigationRuntime();
3197
3202
  return typeof runtime?.__mreactPrefetch === "function"
3198
- ? runtime.__mreactPrefetch(url)
3203
+ ? runtime.__mreactPrefetch(url, options)
3199
3204
  : false;
3200
3205
  }
3201
3206
 
@@ -3241,13 +3246,16 @@ __mreactGlobal.__mreactRouteCell = (nativeCell, initial) => {
3241
3246
  const stateCell = nativeCell(record.value);
3242
3247
  const setStateCell = stateCell.set;
3243
3248
 
3244
- stateCell.set = (next) => {
3249
+ const setRouteStateCell = (next) => {
3245
3250
  setStateCell((previous) => {
3246
3251
  const resolved = typeof next === "function" ? next(previous) : next;
3247
3252
  record.value = resolved;
3248
3253
  return resolved;
3249
3254
  });
3250
3255
  };
3256
+ stateCell.set = setRouteStateCell;
3257
+ stateCell.setValue = (next) => stateCell.set(() => next);
3258
+ stateCell.update = (updater) => stateCell.set(updater);
3251
3259
 
3252
3260
  __mreactActiveCellRecords.set(cellKey, record);
3253
3261
  return stateCell;
@@ -3674,7 +3682,7 @@ function __mreactRouteAnnouncementElement() {
3674
3682
  return announcement;
3675
3683
  }
3676
3684
 
3677
- export async function __mreactPrefetch(url) {
3685
+ export async function __mreactPrefetch(url, options = {}) {
3678
3686
  if (!__mreactCanPrefetch()) {
3679
3687
  return false;
3680
3688
  }
@@ -3693,10 +3701,6 @@ export async function __mreactPrefetch(url) {
3693
3701
  return false;
3694
3702
  }
3695
3703
 
3696
- if (__mreactNavigationState.prefetchedUrls.has(href)) {
3697
- return true;
3698
- }
3699
-
3700
3704
  __mreactRememberPrefetchedUrl(href);
3701
3705
 
3702
3706
  const script = __mreactRouteScriptForNavigationUrl(href);
@@ -3705,21 +3709,30 @@ export async function __mreactPrefetch(url) {
3705
3709
  return __mreactPrefetchNavigationHtml(href);
3706
3710
  }
3707
3711
 
3708
- return __mreactPrefetchRouteScript(script);
3712
+ const scriptPrefetched = __mreactPrefetchRouteScript(script);
3713
+
3714
+ if (options?.trigger === "viewport") {
3715
+ return scriptPrefetched;
3716
+ }
3717
+
3718
+ return Promise.resolve(__mreactPrefetchNavigationHtml(href))
3719
+ .then(() => scriptPrefetched)
3720
+ .catch(() => scriptPrefetched);
3709
3721
  }
3710
3722
 
3711
3723
  function __mreactPrefetchNavigationHtml(href) {
3712
- if (__mreactNavigationState.cache.has(href)) {
3724
+ if (__mreactCachedNavigationHtml(href) !== undefined) {
3713
3725
  return true;
3714
3726
  }
3715
3727
 
3716
- return __mreactFetchNavigationHtmlOnce(href)
3717
- .then((html) => typeof html === "string")
3728
+ return __mreactFetchNavigationHtmlOnce(href, { speculative: true })
3729
+ .then((result) => result.cacheable && typeof result.html === "string")
3718
3730
  .catch(() => false);
3719
3731
  }
3720
3732
 
3721
3733
  const __mreactNavigationHtmlCacheMaxEntries = 64;
3722
3734
  const __mreactNavigationPrefetchHistoryMaxEntries = 64;
3735
+ const __mreactNavigationHtmlCacheTtlMs = 30_000;
3723
3736
 
3724
3737
  function __mreactRememberPrefetchedUrl(href) {
3725
3738
  __mreactRememberPrefetchHistory(__mreactNavigationState.prefetchedUrls, href);
@@ -3748,23 +3761,49 @@ function __mreactRememberPrefetchHistory(history, href) {
3748
3761
  }
3749
3762
 
3750
3763
  function __mreactCachedNavigationHtml(href) {
3751
- const html = __mreactNavigationState.cache.get(href);
3764
+ const entry = __mreactNavigationState.cache.get(href);
3752
3765
 
3753
- if (html === undefined) {
3766
+ if (entry === undefined) {
3767
+ return undefined;
3768
+ }
3769
+
3770
+ if (
3771
+ entry.token !== __mreactNavigationState.cacheTokens.get(href) ||
3772
+ entry.token.valid !== true ||
3773
+ entry.cacheContext !== __mreactNavigationCacheContext() ||
3774
+ Date.now() - entry.fetchedAt >= __mreactNavigationHtmlCacheTtlMs
3775
+ ) {
3776
+ __mreactNavigationState.cache.delete(href);
3777
+ __mreactNavigationState.prefetchedUrls.delete(href);
3778
+ __mreactReleaseNavigationCacheToken(href, entry.token);
3754
3779
  return undefined;
3755
3780
  }
3756
3781
 
3757
3782
  __mreactNavigationState.cache.delete(href);
3758
- __mreactNavigationState.cache.set(href, html);
3759
- return html;
3783
+ __mreactNavigationState.cache.set(href, entry);
3784
+ return entry.html;
3760
3785
  }
3761
3786
 
3762
- function __mreactRememberNavigationHtml(href, html) {
3787
+ function __mreactRememberNavigationHtml(href, html, token, cacheContext) {
3788
+ if (
3789
+ token === undefined ||
3790
+ token.valid !== true ||
3791
+ __mreactNavigationState.cacheTokens.get(href) !== token ||
3792
+ cacheContext !== __mreactNavigationCacheContext()
3793
+ ) {
3794
+ return;
3795
+ }
3796
+
3763
3797
  if (__mreactNavigationState.cache.has(href)) {
3764
3798
  __mreactNavigationState.cache.delete(href);
3765
3799
  }
3766
3800
 
3767
- __mreactNavigationState.cache.set(href, html);
3801
+ __mreactNavigationState.cache.set(href, {
3802
+ cacheContext,
3803
+ fetchedAt: Date.now(),
3804
+ html,
3805
+ token,
3806
+ });
3768
3807
 
3769
3808
  while (__mreactNavigationState.cache.size > __mreactNavigationHtmlCacheMaxEntries) {
3770
3809
  const oldestHref = __mreactNavigationState.cache.keys().next().value;
@@ -3775,37 +3814,100 @@ function __mreactRememberNavigationHtml(href, html) {
3775
3814
 
3776
3815
  __mreactNavigationState.cache.delete(oldestHref);
3777
3816
  __mreactNavigationState.prefetchedUrls.delete(oldestHref);
3817
+ __mreactReleaseNavigationCacheToken(oldestHref);
3778
3818
  }
3779
3819
  }
3780
3820
 
3781
- function __mreactPrefetchRouteScript(script) {
3782
- if (typeof document === "undefined") {
3783
- return false;
3821
+ function __mreactNavigationCacheContext() {
3822
+ const authSession = typeof document === "undefined"
3823
+ ? undefined
3824
+ : document.getElementById("__mreact_auth_session")?.textContent ?? undefined;
3825
+ const cookies = __mreactNavigationCookies();
3826
+
3827
+ return JSON.stringify([authSession, cookies]);
3828
+ }
3829
+
3830
+ function __mreactNavigationCookies() {
3831
+ return typeof document === "undefined" ? "" : document.cookie;
3832
+ }
3833
+
3834
+ function __mreactNavigationResponseCacheContext(html, requestCookies) {
3835
+ let authSession;
3836
+
3837
+ if (typeof document !== "undefined") {
3838
+ const template = document.createElement("template");
3839
+ template.innerHTML = html;
3840
+ authSession = template.content.querySelector("#__mreact_auth_session")?.textContent ?? undefined;
3784
3841
  }
3785
3842
 
3786
- const href = __mreactNormalizeAssetUrl(script);
3843
+ return JSON.stringify([authSession, requestCookies]);
3844
+ }
3787
3845
 
3788
- if (href === undefined) {
3789
- return false;
3846
+ function __mreactReleaseNavigationCacheToken(href, token) {
3847
+ if (
3848
+ __mreactNavigationState.cache.get(href) === undefined &&
3849
+ __mreactNavigationState.pendingHtmlFetches.get(href) === undefined &&
3850
+ (token === undefined || __mreactNavigationState.cacheTokens.get(href) === token)
3851
+ ) {
3852
+ __mreactNavigationState.cacheTokens.delete(href);
3790
3853
  }
3854
+ }
3791
3855
 
3792
- if (__mreactNavigationState.prefetchedScripts.has(href)) {
3793
- return true;
3856
+ function __mreactNavigationCacheToken(href) {
3857
+ const existing = __mreactNavigationState.cacheTokens.get(href);
3858
+
3859
+ if (existing !== undefined) {
3860
+ return existing;
3794
3861
  }
3795
3862
 
3796
- for (const link of Array.from(document.querySelectorAll('link[rel="modulepreload"][href]'))) {
3797
- if (link.href === href) {
3798
- __mreactRememberPrefetchedScript(href);
3799
- return true;
3863
+ const token = { valid: true };
3864
+ __mreactNavigationState.cacheTokens.set(href, token);
3865
+ return token;
3866
+ }
3867
+
3868
+ function __mreactPrefetchRouteScript(route) {
3869
+ if (typeof document === "undefined") {
3870
+ return false;
3871
+ }
3872
+
3873
+ const files = [route.script, ...(route.modulePreloads ?? [])];
3874
+ let valid = true;
3875
+
3876
+ for (const file of files) {
3877
+ const href = __mreactNormalizeAssetUrl(file);
3878
+
3879
+ if (
3880
+ href === undefined ||
3881
+ !__mreactIsSupportedModulePreloadUrl(file) ||
3882
+ !__mreactModulePreloadMatchesRouteScript(file, route.script)
3883
+ ) {
3884
+ valid = false;
3885
+ continue;
3886
+ }
3887
+
3888
+ if (__mreactNavigationState.prefetchedScripts.has(href)) {
3889
+ continue;
3890
+ }
3891
+
3892
+ let found = false;
3893
+ for (const link of Array.from(document.querySelectorAll('link[rel="modulepreload"][href]'))) {
3894
+ if (link.href === href) {
3895
+ found = true;
3896
+ break;
3897
+ }
3898
+ }
3899
+
3900
+ if (!found) {
3901
+ const link = document.createElement("link");
3902
+ link.rel = "modulepreload";
3903
+ link.href = href;
3904
+ document.head.appendChild(link);
3800
3905
  }
3906
+
3907
+ __mreactRememberPrefetchedScript(href);
3801
3908
  }
3802
3909
 
3803
- const link = document.createElement("link");
3804
- link.rel = "modulepreload";
3805
- link.href = href;
3806
- document.head.appendChild(link);
3807
- __mreactRememberPrefetchedScript(href);
3808
- return true;
3910
+ return valid;
3809
3911
  }
3810
3912
 
3811
3913
  export async function __mreactNavigate(url, options = {}) {
@@ -3818,19 +3920,46 @@ export async function __mreactNavigate(url, options = {}) {
3818
3920
  __mreactSetNavigationState(__mreactPendingNavigationState(href, options.type ?? "push"));
3819
3921
 
3820
3922
  try {
3821
- const cachedHtml = __mreactCachedNavigationHtml(href);
3822
- const script = __mreactRouteScriptForNavigationUrl(href);
3823
- if (script !== undefined) {
3824
- void __mreactPrefetchRouteScript(script);
3825
- }
3826
- const html = cachedHtml ?? await __mreactFetchNavigationHtmlOnce(href);
3923
+ let forceReload = __mreactNavigationState.reloadNextNavigationFetch === true;
3827
3924
 
3828
- if (typeof html !== "string") {
3829
- return false;
3830
- }
3925
+ for (;;) {
3926
+ const cachedHtml = forceReload ? undefined : __mreactCachedNavigationHtml(href);
3927
+ const script = __mreactRouteScriptForNavigationUrl(href);
3928
+ if (script !== undefined) {
3929
+ void __mreactPrefetchRouteScript(script);
3930
+ }
3931
+ const result = cachedHtml === undefined
3932
+ ? await __mreactFetchNavigationHtmlOnce(href, { force: forceReload })
3933
+ : {
3934
+ cacheable: true,
3935
+ cacheContext: __mreactNavigationCacheContext(),
3936
+ cacheContextMatches: true,
3937
+ html: cachedHtml,
3938
+ reusable: true,
3939
+ speculative: false,
3940
+ token: undefined,
3941
+ };
3942
+
3943
+ if (result.token !== undefined && result.token.valid !== true) {
3944
+ forceReload = __mreactNavigationState.reloadNextNavigationFetch === true;
3945
+ continue;
3946
+ }
3831
3947
 
3832
- __mreactRememberNavigationHtml(href, html);
3833
- return await __mreactApplyNavigationHtmlWithOptionalTransition(html, href, options);
3948
+ if (result.speculative === true && result.cacheable !== true) {
3949
+ forceReload = true;
3950
+ continue;
3951
+ }
3952
+
3953
+ if (result.reusable === true && result.cacheContextMatches !== true) {
3954
+ return false;
3955
+ }
3956
+
3957
+ if (typeof result.html !== "string") {
3958
+ return false;
3959
+ }
3960
+
3961
+ return await __mreactApplyNavigationHtmlWithOptionalTransition(result.html, href, options);
3962
+ }
3834
3963
  } finally {
3835
3964
  __mreactSetNavigationState(__mreactIdleNavigationState());
3836
3965
  }
@@ -3965,7 +4094,7 @@ function __mreactViewTransitionsAllowed(transition) {
3965
4094
  }
3966
4095
  }
3967
4096
 
3968
- export function __mreactInvalidateNavigationCache(path) {
4097
+ export function __mreactInvalidateNavigationCache(path, excludedRequest) {
3969
4098
  const normalizedPath = __mreactNormalizeNavigationPath(path);
3970
4099
 
3971
4100
  if (normalizedPath === undefined) {
@@ -3979,66 +4108,160 @@ export function __mreactInvalidateNavigationCache(path) {
3979
4108
 
3980
4109
  for (const href of hrefs) {
3981
4110
  if (__mreactNormalizeNavigationPath(href) === normalizedPath) {
4111
+ const token = __mreactNavigationState.cacheTokens.get(href);
4112
+ if (
4113
+ excludedRequest?.href === href &&
4114
+ excludedRequest.token === token &&
4115
+ token?.valid === true
4116
+ ) {
4117
+ continue;
4118
+ }
4119
+ if (token !== undefined) {
4120
+ token.valid = false;
4121
+ }
3982
4122
  __mreactNavigationState.cache.delete(href);
3983
4123
  __mreactNavigationState.prefetchedUrls.delete(href);
3984
4124
  __mreactNavigationState.pendingHtmlFetches.delete(href);
4125
+ __mreactNavigationState.cacheTokens.delete(href);
3985
4126
  }
3986
4127
  }
3987
4128
  }
3988
4129
 
3989
4130
  function __mreactInvalidateAllNavigationCache() {
4131
+ for (const entry of __mreactNavigationState.cache.values()) {
4132
+ entry.token.valid = false;
4133
+ }
4134
+ for (const request of __mreactNavigationState.pendingHtmlFetches.values()) {
4135
+ request.token.valid = false;
4136
+ }
3990
4137
  __mreactNavigationState.cache.clear();
4138
+ __mreactNavigationState.cacheTokens.clear();
3991
4139
  __mreactNavigationState.prefetchedUrls.clear();
3992
4140
  __mreactNavigationState.pendingHtmlFetches.clear();
3993
4141
  }
3994
4142
 
3995
- function __mreactFetchNavigationHtmlOnce(href) {
4143
+ function __mreactFetchNavigationHtmlOnce(href, options = {}) {
3996
4144
  const pending = __mreactNavigationState.pendingHtmlFetches.get(href);
3997
4145
 
4146
+ if (pending !== undefined && options.force !== true) {
4147
+ return pending.promise;
4148
+ }
4149
+
3998
4150
  if (pending !== undefined) {
3999
- return pending;
4151
+ pending.token.valid = false;
4152
+ __mreactNavigationState.pendingHtmlFetches.delete(href);
4153
+ __mreactNavigationState.cacheTokens.delete(href);
4000
4154
  }
4001
4155
 
4002
- const request = __mreactFetchNavigationHtml(href)
4003
- .then((html) => {
4156
+ const token = __mreactNavigationCacheToken(href);
4157
+ const pendingRequest = {
4158
+ promise: undefined,
4159
+ token,
4160
+ };
4161
+ const request = __mreactFetchNavigationHtml(href, options, pendingRequest)
4162
+ .then((result) => {
4163
+ const enriched = {
4164
+ ...result,
4165
+ speculative: options.speculative === true,
4166
+ token,
4167
+ };
4004
4168
  if (
4005
- typeof html === "string" &&
4006
- __mreactNavigationState.pendingHtmlFetches.get(href) === request
4169
+ enriched.cacheable === true &&
4170
+ typeof enriched.html === "string" &&
4171
+ __mreactNavigationState.pendingHtmlFetches.get(href) === pendingRequest &&
4172
+ token.valid === true &&
4173
+ __mreactNavigationState.cacheTokens.get(href) === token
4007
4174
  ) {
4008
- __mreactRememberNavigationHtml(href, html);
4175
+ __mreactRememberNavigationHtml(href, enriched.html, token, enriched.cacheContext);
4009
4176
  }
4010
4177
 
4011
- return html;
4178
+ return enriched;
4012
4179
  })
4013
4180
  .finally(() => {
4014
- if (__mreactNavigationState.pendingHtmlFetches.get(href) === request) {
4181
+ if (__mreactNavigationState.pendingHtmlFetches.get(href) === pendingRequest) {
4015
4182
  __mreactNavigationState.pendingHtmlFetches.delete(href);
4183
+ __mreactReleaseNavigationCacheToken(href, token);
4016
4184
  }
4017
4185
  });
4018
4186
 
4019
- __mreactNavigationState.pendingHtmlFetches.set(href, request);
4187
+ pendingRequest.promise = request;
4188
+ __mreactNavigationState.pendingHtmlFetches.set(href, pendingRequest);
4020
4189
  return request;
4021
4190
  }
4022
4191
 
4023
- function __mreactFetchNavigationHtml(href) {
4024
- const reloadRouteCache = __mreactNavigationState.reloadNextNavigationFetch === true;
4025
- __mreactNavigationState.reloadNextNavigationFetch = false;
4192
+ function __mreactFetchNavigationHtml(href, options = {}, pendingRequest) {
4193
+ const speculative = options.speculative === true;
4194
+ const reloadRouteCache = !speculative && __mreactNavigationState.reloadNextNavigationFetch === true;
4195
+ if (!speculative) {
4196
+ __mreactNavigationState.reloadNextNavigationFetch = false;
4197
+ }
4026
4198
  const headers = reloadRouteCache
4027
4199
  ? { "x-mreact-navigation": "1", "x-mreact-navigation-cache": "reload" }
4028
4200
  : { "x-mreact-navigation": "1" };
4029
-
4030
- return fetch(__mreactStaticNavigationRequestHref(href), {
4031
- headers,
4032
- }).then((response) => {
4033
- __mreactApplyRevalidationHeader(response);
4034
- if (__mreactNavigationResponseRequiresDocumentReload(response)) {
4035
- return undefined;
4201
+ const requestCacheContext = __mreactNavigationCacheContext();
4202
+ const requestCookies = __mreactNavigationCookies();
4203
+ const navigationRequestInit = { headers };
4204
+ __mreactNavigationState.navigationFetchInits.add(navigationRequestInit);
4205
+
4206
+ return fetch(__mreactStaticNavigationRequestHref(href), navigationRequestInit).then((response) => {
4207
+ const currentRequest =
4208
+ pendingRequest !== undefined &&
4209
+ __mreactNavigationState.pendingHtmlFetches.get(href) === pendingRequest &&
4210
+ __mreactNavigationState.cacheTokens.get(href) === pendingRequest.token &&
4211
+ pendingRequest.token.valid === true
4212
+ ? { href, token: pendingRequest.token }
4213
+ : undefined;
4214
+ if (pendingRequest === undefined || currentRequest !== undefined) {
4215
+ __mreactApplyRevalidationHeader(response, currentRequest);
4216
+ }
4217
+ const requiresDocumentReload = __mreactNavigationResponseRequiresDocumentReload(response);
4218
+ const responseIsSuccessful =
4219
+ response.ok === true ||
4220
+ (response.ok === undefined && response.status >= 200 && response.status < 300);
4221
+ const responseIsRedirected = response.redirected === true;
4222
+ const cacheControl = (response.headers.get("cache-control") ?? "").toLowerCase();
4223
+ const responseDisallowsCache = /(?:^|,)\\s*(?:no-store|no-cache)(?:\\s*=|\\s*(?:,|$))/.test(
4224
+ cacheControl,
4225
+ );
4226
+ const reusable =
4227
+ !requiresDocumentReload &&
4228
+ responseIsSuccessful &&
4229
+ !responseIsRedirected &&
4230
+ !responseDisallowsCache;
4231
+
4232
+ if (requiresDocumentReload || responseIsRedirected || speculative && !reusable) {
4233
+ __mreactDiscardNavigationResponse(response);
4234
+ return { cacheable: false, html: undefined };
4036
4235
  }
4037
4236
 
4038
- return response.text();
4237
+ return response.text().then((html) => {
4238
+ const cacheContext = __mreactNavigationResponseCacheContext(html, requestCookies);
4239
+ const currentCacheContext = __mreactNavigationCacheContext();
4240
+ const cacheContextMatches =
4241
+ cacheContext === currentCacheContext && requestCacheContext === currentCacheContext;
4242
+
4243
+ return {
4244
+ cacheContext,
4245
+ cacheContextMatches,
4246
+ cacheable: reusable && cacheContextMatches,
4247
+ html,
4248
+ reusable,
4249
+ };
4250
+ });
4039
4251
  });
4040
4252
  }
4041
4253
 
4254
+ function __mreactDiscardNavigationResponse(response) {
4255
+ try {
4256
+ const result = response?.body?.cancel?.();
4257
+ if (result !== undefined && typeof result.catch === "function") {
4258
+ void result.catch(() => {});
4259
+ }
4260
+ } catch {
4261
+ // Ignore body cancellation failures while falling back to a document navigation.
4262
+ }
4263
+ }
4264
+
4042
4265
  function __mreactStaticNavigationRequestHref(href) {
4043
4266
  if (typeof document === "undefined" || typeof location === "undefined") {
4044
4267
  return href;
@@ -4074,7 +4297,7 @@ function __mreactNavigationResponseRequiresDocumentReload(response) {
4074
4297
  return response.status === 204 || response.headers.get("x-mreact-navigation") === "reload";
4075
4298
  }
4076
4299
 
4077
- function __mreactApplyRevalidationHeader(response) {
4300
+ function __mreactApplyRevalidationHeader(response, currentRequest) {
4078
4301
  const header = typeof response?.headers?.get === "function"
4079
4302
  ? response.headers.get("x-mreact-revalidate")
4080
4303
  : null;
@@ -4083,18 +4306,34 @@ function __mreactApplyRevalidationHeader(response) {
4083
4306
  return;
4084
4307
  }
4085
4308
 
4309
+ const currentPath = __mreactNormalizeNavigationPath(currentRequest?.href);
4310
+
4086
4311
  for (const path of header.split(",")) {
4087
- __mreactInvalidateNavigationCache(path.trim());
4312
+ const trimmedPath = path.trim();
4313
+ const normalizedPath = __mreactNormalizeNavigationPath(trimmedPath);
4314
+ const excludedRequest =
4315
+ currentRequest !== undefined && normalizedPath === currentPath ? currentRequest : undefined;
4316
+ __mreactInvalidateNavigationCache(trimmedPath, excludedRequest);
4088
4317
  }
4089
4318
  }
4090
4319
 
4320
+ function __mreactIsNavigationFetchRequest(_input, init) {
4321
+ return init !== null && typeof init === "object" && __mreactNavigationState.navigationFetchInits.has(init);
4322
+ }
4323
+
4091
4324
  function __mreactNormalizeNavigationPath(path) {
4325
+ if (typeof path !== "string" || path.trim() === "") {
4326
+ return undefined;
4327
+ }
4328
+
4329
+ const normalizedInput = path.trim();
4330
+
4092
4331
  if (typeof location === "undefined") {
4093
- return typeof path === "string" && path.length > 0 ? path : undefined;
4332
+ return normalizedInput;
4094
4333
  }
4095
4334
 
4096
4335
  try {
4097
- const url = new URL(path, location.href);
4336
+ const url = new URL(normalizedInput, location.href);
4098
4337
  const pathname = url.pathname.replace(/\\/+$/, "");
4099
4338
 
4100
4339
  return pathname === "" ? "/" : pathname;
@@ -4110,7 +4349,7 @@ export function __mreactRestoreHistoryState(state) {
4110
4349
 
4111
4350
  const html = typeof state.html === "string"
4112
4351
  ? state.html
4113
- : typeof state.url === "string" ? __mreactNavigationState.cache.get(state.url) : undefined;
4352
+ : typeof state.url === "string" ? __mreactCachedNavigationHtml(state.url) : undefined;
4114
4353
 
4115
4354
  if (typeof html !== "string") {
4116
4355
  return false;
@@ -4486,7 +4725,7 @@ function __mreactRouteScriptForNavigationUrl(url) {
4486
4725
 
4487
4726
  for (const route of __mreactClientRoutePrefetchManifest()) {
4488
4727
  if (__mreactRoutePathMatches(route.path, nextUrl.pathname)) {
4489
- return route.script;
4728
+ return route;
4490
4729
  }
4491
4730
  }
4492
4731
 
@@ -4516,12 +4755,28 @@ function __mreactClientRoutePrefetchManifest() {
4516
4755
  try {
4517
4756
  const parsed = JSON.parse(text);
4518
4757
  __mreactNavigationState.routePrefetchManifest = Array.isArray(parsed)
4519
- ? parsed.filter((route) =>
4520
- route !== null &&
4521
- typeof route === "object" &&
4522
- typeof route.path === "string" &&
4523
- typeof route.script === "string"
4524
- )
4758
+ ? parsed.flatMap((route) => {
4759
+ if (
4760
+ route === null ||
4761
+ typeof route !== "object" ||
4762
+ typeof route.path !== "string" ||
4763
+ typeof route.script !== "string" ||
4764
+ !__mreactIsSupportedModulePreloadUrl(route.script)
4765
+ ) {
4766
+ return [];
4767
+ }
4768
+
4769
+ const modulePreloads = Array.isArray(route.modulePreloads)
4770
+ ? route.modulePreloads.filter(
4771
+ (file) =>
4772
+ typeof file === "string" &&
4773
+ __mreactIsSupportedModulePreloadUrl(file) &&
4774
+ __mreactModulePreloadMatchesRouteScript(file, route.script),
4775
+ )
4776
+ : [];
4777
+
4778
+ return [{ ...route, modulePreloads }];
4779
+ })
4525
4780
  : [];
4526
4781
  } catch {
4527
4782
  __mreactNavigationState.routePrefetchManifest = [];
@@ -4530,6 +4785,29 @@ function __mreactClientRoutePrefetchManifest() {
4530
4785
  return __mreactNavigationState.routePrefetchManifest;
4531
4786
  }
4532
4787
 
4788
+ function __mreactIsSupportedModulePreloadUrl(value) {
4789
+ if (typeof value !== "string" || value === "" || value.startsWith("javascript:")) {
4790
+ return false;
4791
+ }
4792
+
4793
+ try {
4794
+ const resolved = new URL(value, typeof location === "undefined" ? undefined : location.href);
4795
+ return resolved.protocol === "http:" || resolved.protocol === "https:";
4796
+ } catch {
4797
+ return false;
4798
+ }
4799
+ }
4800
+
4801
+ function __mreactModulePreloadMatchesRouteScript(value, script) {
4802
+ try {
4803
+ const scriptUrl = new URL(__mreactNormalizeAssetUrl(script), location.href);
4804
+ const dependencyUrl = new URL(__mreactNormalizeAssetUrl(value), location.href);
4805
+ return scriptUrl.origin === dependencyUrl.origin;
4806
+ } catch {
4807
+ return false;
4808
+ }
4809
+ }
4810
+
4533
4811
  function __mreactRoutePathMatches(routePath, pathname) {
4534
4812
  const routeSegments = __mreactNormalizeRoutePath(routePath);
4535
4813
  const pathSegments = __mreactNormalizeRoutePath(pathname);
@@ -4827,9 +5105,12 @@ function __mreactInstallNavigationFetchRevalidation() {
4827
5105
  const fetchImpl = globalThis.fetch;
4828
5106
  globalThis.fetch = function(input, init) {
4829
5107
  const mutating = __mreactIsMutatingFetchRequest(input, init);
5108
+ const navigation = __mreactIsNavigationFetchRequest(input, init);
4830
5109
 
4831
5110
  return Promise.resolve(fetchImpl.call(this, input, init)).then((response) => {
4832
- __mreactApplyRevalidationHeader(response);
5111
+ if (!navigation) {
5112
+ __mreactApplyRevalidationHeader(response);
5113
+ }
4833
5114
 
4834
5115
  if (mutating) {
4835
5116
  __mreactInvalidateAllNavigationCache();
@@ -4901,7 +5182,7 @@ function __mreactObserveViewportPrefetchAnchors(root) {
4901
5182
  }
4902
5183
 
4903
5184
  __mreactNavigationState.viewportObserver?.unobserve(entry.target);
4904
- void __mreactPrefetch(entry.target.href);
5185
+ void __mreactPrefetch(entry.target.href, { trigger: "viewport" });
4905
5186
  }
4906
5187
  });
4907
5188
  }