stx-router 0.2.709 → 0.2.710

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/README.md CHANGED
@@ -161,6 +161,11 @@ const script = getRouterScript()
161
161
  // - Loading indicator
162
162
  ```
163
163
 
164
+ Background page prefetches and revalidations run at most two at a time. Hover or
165
+ touch intent moves a queued page ahead of links merely visible on screen. A
166
+ navigation starts its queued destination immediately and shares that request,
167
+ so it never waits for the speculative queue or fetches the page twice.
168
+
164
169
  ### Route Type Generation
165
170
 
166
171
  ```typescript
package/dist/client.js CHANGED
@@ -51,7 +51,7 @@ function routerSource() {
51
51
  return `
52
52
  ;(function(){
53
53
  'use strict';
54
- var ROUTER_REV=7;
54
+ var ROUTER_REV=8;
55
55
  if(window.__stxRouter&&window.__stxRouter.__rev===ROUTER_REV)return;
56
56
 
57
57
  // \u2500\u2500 Configuration \u2500\u2500
@@ -516,6 +516,37 @@ function routerSource() {
516
516
  // checking behind the swap (revalidate).
517
517
  var cacheAt={};
518
518
  var prefetching={};
519
+ // Speculative HTML still renders on the server. A viewport with twelve
520
+ // links must not start twelve renders alongside the page being read.
521
+ // Intent moves a queued page to the front; navigation starts it at once,
522
+ // sharing the same promise instead of waiting behind background work.
523
+ var prefetchQueue=[],prefetchActive=0;
524
+ function runPrefetch(job){
525
+ prefetchActive++;
526
+ var started;
527
+ try{started=job.start()}catch(error){started=Promise.reject(error)}
528
+ Promise.resolve(started).then(job.resolve,job.reject).finally(function(){
529
+ prefetchActive--;
530
+ while(prefetchActive<2&&prefetchQueue.length)runPrefetch(prefetchQueue.shift());
531
+ });
532
+ }
533
+ function queuePrefetch(key,start,intent){
534
+ return new Promise(function(resolve,reject){
535
+ var job={key:key,start:start,resolve:resolve,reject:reject};
536
+ if(prefetchActive<2)runPrefetch(job);
537
+ else if(intent)prefetchQueue.unshift(job);
538
+ else prefetchQueue.push(job);
539
+ });
540
+ }
541
+ function promotePrefetch(key,navigation){
542
+ for(var i=0;i<prefetchQueue.length;i++){
543
+ if(prefetchQueue[i].key!==key)continue;
544
+ var job=prefetchQueue.splice(i,1)[0];
545
+ if(navigation)runPrefetch(job);
546
+ else prefetchQueue.unshift(job);
547
+ break;
548
+ }
549
+ }
519
550
  var awaitingKey='';
520
551
  var isNavigating=false;
521
552
 
@@ -1303,6 +1334,7 @@ function routerSource() {
1303
1334
  var awaitedKey=cacheKey(url);
1304
1335
  if(o.cache&&!force&&!cache[awaitedKey]&&prefetching[awaitedKey]&&prefetching[awaitedKey].then){
1305
1336
  awaitingKey=awaitedKey;
1337
+ promotePrefetch(awaitedKey,true);
1306
1338
  // Answer the tap now, not when the prefetch lands. The page it asked
1307
1339
  // for is on the way, but until then nothing on screen said so: no
1308
1340
  // loading class, no progress bar. On a server a second or two away
@@ -3186,7 +3218,7 @@ else {
3186
3218
  if(Date.now()-(cacheAt[key]||0)<o.revalidateAfter)return;
3187
3219
  // Compare like with like, including documents prefetched across layouts.
3188
3220
  var before=cache[key],wantsFragment=before?isFragment(before):shouldUseFragmentResponse();
3189
- prefetching[key]=fetch(url,{headers:wantsFragment?{'X-STX-Router':'true','Accept':'text/html'}:{'Accept':'text/html'}}).then(function(r){
3221
+ prefetching[key]=queuePrefetch(key,function(){return fetch(url,{priority:'low',headers:wantsFragment?{'X-STX-Router':'true','Accept':'text/html'}:{'Accept':'text/html'}}).then(function(r){
3190
3222
  var incoming=r.headers.get('X-STX-Build')||'';
3191
3223
  if(isBuildSkew(incoming)){reloadForSkew(url,incoming,true);return null}
3192
3224
  return readPrefetchResponse(r,wantsFragment);
@@ -3194,7 +3226,7 @@ else {
3194
3226
  if(!result)return;
3195
3227
  if(o.cache)setCache(key,result.html,result.layout,result.layoutGroup,result.title,result.containerAttrs);
3196
3228
  if(before!==undefined&&before!==result.html&&cacheKey(location.href)===key)emitUpdated(url,'page');
3197
- }).catch(function(){}).finally(function(){delete prefetching[key]});
3229
+ }).catch(function(){}).finally(function(){delete prefetching[key]})});
3198
3230
  }
3199
3231
  // The offline worker answered from its cache and found something newer
3200
3232
  // behind it. It posts { type: 'stx:updated', url, kind }, which its own
@@ -3571,12 +3603,12 @@ else {
3571
3603
  var key=cacheKey(href);
3572
3604
  if(cache[key]&&!fresh){if(!glance)loadModuleRegistries(cache[key],href);return}
3573
3605
  if(prefetching[key]){
3574
- if(!glance)prefetching[key].then(function(){if(cache[key])loadModuleRegistries(cache[key],href)});
3606
+ if(!glance){promotePrefetch(key,false);prefetching[key].then(function(){if(cache[key])loadModuleRegistries(cache[key],href)})}
3575
3607
  return;
3576
3608
  }
3577
3609
  var eager=link.getAttribute('data-stx-prefetch')==='eager';
3578
3610
  var wantsFragment=shouldUseFragmentResponse();
3579
- prefetching[key]=fetch(href,{headers:wantsFragment?{'X-STX-Router':'true','Accept':'text/html'}:{'Accept':'text/html'}}).then(function(r){
3611
+ prefetching[key]=queuePrefetch(key,function(){return fetch(href,{priority:'low',headers:wantsFragment?{'X-STX-Router':'true','Accept':'text/html'}:{'Accept':'text/html'}}).then(function(r){
3580
3612
  return readPrefetchResponse(r,wantsFragment);
3581
3613
  }).then(function(result){
3582
3614
  // A page in another layout cannot be swapped in as a fragment: the tap
@@ -3585,7 +3617,7 @@ else {
3585
3617
  // still reaching for the link, so the tap is a swap like any other.
3586
3618
  if(!result||!isFragment(result.html)||!checkLayoutChange(result.layout,href,result.layoutGroup))return result;
3587
3619
  // Same guards as any prefetch; the layout is the one the fragment declared.
3588
- return fetch(href,{headers:{'Accept':'text/html'}}).then(function(full){
3620
+ return fetch(href,{priority:'low',headers:{'Accept':'text/html'}}).then(function(full){
3589
3621
  return full.ok?readPrefetchResponse(full,false).then(function(doc){return doc&&(doc.layout=result.layout,doc.layoutGroup=result.layoutGroup,doc)}):null;
3590
3622
  });
3591
3623
  }).then(function(result){
@@ -3601,7 +3633,7 @@ else {
3601
3633
  // A whole document's generated sheets too, named the same way in its head.
3602
3634
  preloadGeneratedCss(fragmentCssHrefs(result.html),location.href);
3603
3635
  }
3604
- }).catch(function(){}).finally(function(){delete prefetching[key]});
3636
+ }).catch(function(){}).finally(function(){delete prefetching[key]})},!glance);
3605
3637
  }
3606
3638
 
3607
3639
  // \u2500\u2500 Pages kept between launches \u2500\u2500
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "stx-router",
3
3
  "type": "module",
4
- "version": "0.2.709",
4
+ "version": "0.2.710",
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",