stx-router 0.2.395 → 0.2.397

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 (2) hide show
  1. package/dist/client.js +76 -9
  2. package/package.json +1 -1
package/dist/client.js CHANGED
@@ -332,6 +332,7 @@ function routerSource() {
332
332
 
333
333
  var cache={};
334
334
  var prefetching={};
335
+ var awaitingKey='';
335
336
  var isNavigating=false;
336
337
 
337
338
  // LRU bookkeeping for cache/layoutCache/layoutGroupCache. Pre-fix
@@ -923,6 +924,19 @@ function routerSource() {
923
924
  // forward-navigation page.
924
925
  if(pushState!==false&&t.href===location.href&&!t.hash&&!force)return Promise.resolve(false);
925
926
 
927
+ // Its prefetch is on the way: wait for that answer rather than asking the
928
+ // server a second time. A later tap elsewhere wins.
929
+ var awaitedKey=cacheKey(url);
930
+ if(o.cache&&!force&&!cache[awaitedKey]&&prefetching[awaitedKey]&&prefetching[awaitedKey].then){
931
+ awaitingKey=awaitedKey;
932
+ return prefetching[awaitedKey].then(function(){
933
+ if(awaitingKey!==awaitedKey)return false;
934
+ awaitingKey='';
935
+ return navigate(url,pushState,force);
936
+ });
937
+ }
938
+ awaitingKey='';
939
+
926
940
  isNavigating=true;
927
941
  instantNav=instantNext;
928
942
  instantNext=false;
@@ -2211,24 +2225,70 @@ else {
2211
2225
  // \u2500\u2500 Prefetch \u2500\u2500
2212
2226
  // A link's page, fetched before it is followed into the cache navigate
2213
2227
  // reads, so following it is a swap rather than a round trip.
2214
- function prefetchLink(link){
2228
+ // 'prefetching' holds each fetch in flight, so a tap that lands before its
2229
+ // prefetch has finished waits for that answer instead of asking again.
2230
+ // 'fresh' refetches a page that is cached, for an entry restored from an
2231
+ // earlier launch that is shown at once and replaced behind it.
2232
+ function prefetchLink(link,fresh){
2215
2233
  // Same opt-out set as the click path. Without it, hovering a link the
2216
2234
  // router is not allowed to claim still fired a real GET at it \u2014 a
2217
2235
  // logout or OAuth URL was requested on hover alone.
2218
2236
  if(isRouterExcluded(link))return;
2219
2237
  var href=withCurrentLocale(link.getAttribute('href'));
2220
2238
  var key=cacheKey(href);
2221
- if(cache[key]||prefetching[key])return;
2222
- prefetching[key]=true;
2239
+ if((cache[key]&&!fresh)||prefetching[key])return;
2240
+ var eager=link.getAttribute('data-stx-prefetch')==='eager';
2223
2241
  var wantsFragment=shouldUseFragmentResponse();
2224
- fetch(href,{headers:wantsFragment?{'X-STX-Router':'true','Accept':'text/html'}:{'Accept':'text/html'}}).then(function(r){
2242
+ prefetching[key]=fetch(href,{headers:wantsFragment?{'X-STX-Router':'true','Accept':'text/html'}:{'Accept':'text/html'}}).then(function(r){
2225
2243
  return readPrefetchResponse(r,wantsFragment);
2226
2244
  }).then(function(result){
2227
- if(result&&o.cache)setCache(key,result.html,result.layout,result.layoutGroup,result.title,result.containerAttrs);
2245
+ if(result&&o.cache){
2246
+ setCache(key,result.html,result.layout,result.layoutGroup,result.title,result.containerAttrs);
2247
+ if(eager)keepPage(key,result);
2248
+ }
2228
2249
  if(result)loadModuleRegistries(result.html,href);
2229
2250
  }).catch(function(){}).finally(function(){delete prefetching[key]});
2230
2251
  }
2231
2252
 
2253
+ // \u2500\u2500 Pages kept between launches \u2500\u2500
2254
+ // An eager link's page (a tab bar's) is kept in localStorage under the build
2255
+ // that rendered it, and put back into the cache when the next launch of the
2256
+ // same build starts. The first tap after opening the app is then a swap,
2257
+ // not a round trip, however far the server is; the eager prefetch replaces
2258
+ // the kept copy behind it. Pages of any other build are dropped, since a
2259
+ // runtime must never be handed another build's fragment (#1772).
2260
+ var KEPT_PREFIX='stx:pages:';
2261
+ var KEPT_MAX_BYTES=400000;
2262
+ function keptStore(){try{return window.localStorage||null}catch(e){return null}}
2263
+ function keepPage(key,result){
2264
+ var store=keptStore();
2265
+ if(!store||!loadedBuild||!result||!result.html||result.html.length>KEPT_MAX_BYTES)return;
2266
+ try{
2267
+ var all=JSON.parse(store.getItem(KEPT_PREFIX+loadedBuild)||'{}');
2268
+ all[key]={h:result.html,l:result.layout,g:result.layoutGroup,t:result.title,a:result.containerAttrs};
2269
+ store.setItem(KEPT_PREFIX+loadedBuild,JSON.stringify(all));
2270
+ }catch(e){}
2271
+ }
2272
+ function restoreKeptPages(){
2273
+ var store=keptStore();
2274
+ if(!store||!o.cache||!loadedBuild)return;
2275
+ try{
2276
+ for(var i=store.length-1;i>=0;i--){
2277
+ var name=store.key(i);
2278
+ if(name&&name.indexOf(KEPT_PREFIX)===0&&name!==KEPT_PREFIX+loadedBuild)store.removeItem(name);
2279
+ }
2280
+ var all=JSON.parse(store.getItem(KEPT_PREFIX+loadedBuild)||'{}');
2281
+ for(var key in all){
2282
+ var page=all[key];
2283
+ if(!Object.prototype.hasOwnProperty.call(all,key)||!page||!page.h||cache[key])continue;
2284
+ setCache(key,page.h,page.l||'',page.g||'',page.t||'',page.a||'');
2285
+ restored[key]=true;
2286
+ loadModuleRegistries(page.h,key);
2287
+ }
2288
+ }catch(e){}
2289
+ }
2290
+ var restored={};
2291
+
2232
2292
  // A prefetched page's module registry (the file its imports are bundled
2233
2293
  // into, one per page) is loaded with the prefetch, not on the tap. It only
2234
2294
  // registers modules, once, so loading it early changes nothing but timing,
@@ -2255,15 +2315,21 @@ else {
2255
2315
  eagerQueued=false;
2256
2316
  var connection=navigator.connection;
2257
2317
  if(connection&&(connection.saveData||/2g/.test(connection.effectiveType||'')))return;
2258
- var here=cacheKey(location.pathname+location.search);
2318
+ // The page it is on too: it was loaded as a whole document, so without
2319
+ // its fragment the way back to it went to the network.
2259
2320
  var links=document.querySelectorAll('[data-stx-link][data-stx-prefetch="eager"]');
2260
2321
  for(var i=0;i<links.length;i++){
2261
2322
  var href=links[i].getAttribute('href');
2262
- if(href&&cacheKey(withCurrentLocale(href))!==here)prefetchLink(links[i]);
2323
+ if(!href)continue;
2324
+ var key=cacheKey(withCurrentLocale(href));
2325
+ prefetchLink(links[i],restored[key]);
2326
+ delete restored[key];
2263
2327
  }
2264
2328
  };
2265
- if(window.requestIdleCallback)window.requestIdleCallback(run,{timeout:2000});
2266
- else setTimeout(run,300);
2329
+ // Soon after the first paint rather than whenever the page next idles: on
2330
+ // a distant server each page is a second away, and the first tap comes fast.
2331
+ if(window.requestIdleCallback)window.requestIdleCallback(run,{timeout:500});
2332
+ else setTimeout(run,100);
2267
2333
  }
2268
2334
 
2269
2335
  if(o.prefetch){
@@ -2522,6 +2588,7 @@ else {
2522
2588
  injectStyles();
2523
2589
  injectViewTransitionCSS();
2524
2590
  refreshCurrentLinks();
2591
+ restoreKeptPages();
2525
2592
  prefetchEager();
2526
2593
  }
2527
2594
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "stx-router",
3
3
  "type": "module",
4
- "version": "0.2.395",
4
+ "version": "0.2.397",
5
5
  "description": "File-based router for STX — .stx template discovery, nested layouts, typed route params, middleware, and client-side SPA navigation.",
6
6
  "author": "Chris Breuer <chris@stacksjs.org>",
7
7
  "license": "MIT",