stx-router 0.2.264 → 0.2.266

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 +50 -16
  2. package/package.json +1 -1
package/dist/client.js CHANGED
@@ -125,6 +125,45 @@ export function getRouterScript() {
125
125
  delete attrsCache[oldest];
126
126
  }
127
127
  }
128
+ // Read a prefetch response into the shape setCache stores, or null when this
129
+ // answer must not be cached at all.
130
+ //
131
+ // Both prefetch paths used to assemble this by hand, and both left out
132
+ // X-STX-Container-Attrs (#1947): setCache takes six arguments and they
133
+ // passed five, which JavaScript accepts without a word. The entry was
134
+ // cached with no container attributes, so a link that had been hovered
135
+ // before it was clicked applied '' on the cache hit, and setContainerAttrs
136
+ // stripped the class the previous navigation had put on the routed
137
+ // container. A dashboard lost its max-width, but only on links that
138
+ // happened to be hovered first, and a hard reload always fixed it \u2014 which
139
+ // made it read as a CSS problem.
140
+ //
141
+ // Hand-assembling it twice is what let them drift, so the two guards below
142
+ // live here rather than at the call sites \u2014 the hover path had one of them
143
+ // and the public router.prefetch() had neither:
144
+ //
145
+ // - a redirected body belongs to the URL it came FROM. A guarded route
146
+ // prefetched while logged out answers with /login's markup, and storing
147
+ // that under the guarded key poisons the cache: the later real
148
+ // navigation is a hit, so it swaps the login page in without ever
149
+ // reaching the network, where navigate's own redirect check would have
150
+ // caught it (#1849).
151
+ // - a fragment from a DIFFERENT build must not be handed to this page's
152
+ // runtime (#1772). navigate answers skew by reloading; a prefetch must
153
+ // not, because the user only hovered a link. Declining to cache is the
154
+ // whole fix \u2014 the click then goes to the network, where navigate does
155
+ // the reload properly.
156
+ function readPrefetchResponse(r,wantsFragment){
157
+ if(r.redirected)return Promise.resolve(null);
158
+ if(isBuildSkew(r.headers.get('X-STX-Build')||''))return Promise.resolve(null);
159
+ var isFrag=wantsFragment&&r.headers.get('X-STX-Fragment')==='true';
160
+ var layout=r.headers.get('X-STX-Layout')||'';
161
+ var group=r.headers.get('X-STX-Layout-Group')||'';
162
+ var title=r.headers.get('X-STX-Title')||'';
163
+ var cattrs=r.headers.get('X-STX-Container-Attrs')||'';
164
+ var runtime=r.headers.get('X-STX-Runtime')||'';
165
+ return r.text().then(function(html){return{html:isFrag?fragmentMarker(runtime)+html:html,layout:layout,layoutGroup:group,title:title,containerAttrs:cattrs}});
166
+ }
128
167
  // Apply a page title captured from the fragment response's X-STX-Title
129
168
  // header (URI-encoded) so SPA swaps keep document.title in sync with the
130
169
  // page \u2014 full-document swaps set the title from the <title> tag directly.
@@ -145,7 +184,14 @@ export function getRouterScript() {
145
184
  var probe=document.createElement('div');
146
185
  probe.innerHTML='<i '+attrStr+'></i>';
147
186
  var src=probe.firstChild;
148
- if(src&&src.attributes){
187
+ // getAttributeNames first: it is the same standard DOM answer and it is
188
+ // what the test DOM implements. Reading .attributes alone made this
189
+ // silently return {} there \u2014 so the whole container-attrs path, header to
190
+ // element, had no test coverage at all while #1947 sat in it.
191
+ if(src&&src.getAttributeNames){
192
+ src.getAttributeNames().forEach(function(n){map[n]=src.getAttribute(n)});
193
+ }
194
+ else if(src&&src.attributes){
149
195
  Array.prototype.forEach.call(src.attributes,function(a){map[a.name]=a.value});
150
196
  }
151
197
  return map;
@@ -1609,21 +1655,9 @@ else {
1609
1655
  prefetching[key]=true;
1610
1656
  var wantsFragment=shouldUseFragmentResponse();
1611
1657
  fetch(href,{headers:wantsFragment?{'X-STX-Router':'true','Accept':'text/html'}:{'Accept':'text/html'}}).then(function(r){
1612
- // Never cache a body that came from somewhere else. A guarded route
1613
- // prefetched while logged out answers with /login's markup, and
1614
- // storing it under the guarded key poisons the cache: the later real
1615
- // navigation is a cache hit, so it swaps the login page in without
1616
- // ever hitting the network, where the redirect check above would
1617
- // have caught it (#1849).
1618
- if(r.redirected)return null;
1619
- var isFrag=wantsFragment&&r.headers.get('X-STX-Fragment')==='true';
1620
- var pLayout=r.headers.get('X-STX-Layout')||'';
1621
- var pGroup=r.headers.get('X-STX-Layout-Group')||'';
1622
- var pTitle=r.headers.get('X-STX-Title')||'';
1623
- var pRuntime=r.headers.get('X-STX-Runtime')||'';
1624
- return r.text().then(function(html){return{html:isFrag?fragmentMarker(pRuntime)+html:html,layout:pLayout,layoutGroup:pGroup,title:pTitle}});
1658
+ return readPrefetchResponse(r,wantsFragment);
1625
1659
  }).then(function(result){
1626
- if(result&&o.cache)setCache(key,result.html,result.layout,result.layoutGroup,result.title);
1660
+ if(result&&o.cache)setCache(key,result.html,result.layout,result.layoutGroup,result.title,result.containerAttrs);
1627
1661
  }).catch(function(){}).finally(function(){delete prefetching[key]});
1628
1662
  },true);
1629
1663
  }
@@ -1739,7 +1773,7 @@ else {
1739
1773
  var key=cacheKey(url);
1740
1774
  if(!cache[key]){
1741
1775
  var wantsFragment=shouldUseFragmentResponse();
1742
- fetch(url,{headers:wantsFragment?{'X-STX-Router':'true'}:{'Accept':'text/html'}}).then(function(r){var isFrag=wantsFragment&&r.headers.get('X-STX-Fragment')==='true';var pLayout=r.headers.get('X-STX-Layout')||'';var pGroup=r.headers.get('X-STX-Layout-Group')||'';var pTitle=r.headers.get('X-STX-Title')||'';var pRuntime=r.headers.get('X-STX-Runtime')||'';return r.text().then(function(html){return{html:isFrag?fragmentMarker(pRuntime)+html:html,layout:pLayout,layoutGroup:pGroup,title:pTitle}})}).then(function(result){setCache(key,result.html,result.layout,result.layoutGroup,result.title)}).catch(function(){});
1776
+ fetch(url,{headers:wantsFragment?{'X-STX-Router':'true'}:{'Accept':'text/html'}}).then(function(r){return readPrefetchResponse(r,wantsFragment)}).then(function(result){if(result)setCache(key,result.html,result.layout,result.layoutGroup,result.title,result.containerAttrs)}).catch(function(){});
1743
1777
  }
1744
1778
  },
1745
1779
  // Re-run the CURRENT route against the server and swap the result.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "stx-router",
3
3
  "type": "module",
4
- "version": "0.2.264",
4
+ "version": "0.2.266",
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",