bun-plugin-stx 0.2.47 → 0.2.51

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.
@@ -1,4 +1,8 @@
1
1
  // @bun
2
+ import {
3
+ deriveLayoutGroup,
4
+ extractLayoutMetadata
5
+ } from "./chunk-93ete56h.js";
2
6
  import"./chunk-2mx7fq49.js";
3
7
 
4
8
  // ../router/src/file-router.ts
@@ -585,6 +589,8 @@ function getRouterScript() {
585
589
  var defaults={container:'main',loadingClass:'stx-navigating',viewTransitions:true,cache:true,scrollToTop:true,prefetch:true,progress:true,progressColor:'#78dce8',progressHeight:'2px',interceptAllLinks:false};
586
590
  var o=Object.assign({},defaults,window.__stxRouterConfig||{},window.STX_ROUTER_OPTIONS||{});
587
591
  var containerSel=o.container;
592
+ var debug=!!o.debug;
593
+ function log(){if(debug&&typeof console!=='undefined'&&console.log)console.log.apply(console,arguments)}
588
594
 
589
595
  // \u2500\u2500 Progress bar (0\u2192100% at top of viewport) \u2500\u2500
590
596
  // Native loading indicator baked into the router. Starts when navigation
@@ -676,6 +682,7 @@ function getRouterScript() {
676
682
  // Layout group detection \u2014 layouts in the same group share a <main> container.
677
683
  // Only truly different layout groups trigger a full page reload.
678
684
  var layoutCache={};
685
+ var layoutGroupCache={};
679
686
  // Track executed script hashes to prevent redeclaration errors on navigation.
680
687
  // Layout-level scripts (theme, nav setup) execute on initial page load and
681
688
  // should NOT re-execute when navigating to another page with the same layout.
@@ -689,33 +696,50 @@ function getRouterScript() {
689
696
  var text=s.textContent||'';
690
697
  if(text.trim()&&!s.hasAttribute('src'))executedScriptHashes[hashScript(text)]=1;
691
698
  });
692
- function getLayoutGroup(layout){
699
+ function cacheKey(url){
700
+ var u=new URL(url,location.origin);
701
+ return u.pathname+u.search;
702
+ }
703
+ function defaultLayoutGroup(layout){
693
704
  if(!layout)return 'app';
694
- if(layout.indexOf('auth')!==-1||layout.indexOf('guest')!==-1)return 'auth';
695
- return 'app';
705
+ var clean=String(layout).replace(/\\\\/g,'/');
706
+ var parts=clean.split('/').filter(Boolean);
707
+ var idx=parts.lastIndexOf('layouts');
708
+ var part=idx>=0?parts[idx+1]:parts[parts.length-1];
709
+ return part?part.replace(/\\.stx$/i,''):'app';
710
+ }
711
+ function getCurrentLayoutGroup(){
712
+ var meta=document.querySelector('meta[name="stx-layout-group"]');
713
+ if(meta&&meta.getAttribute('content'))return meta.getAttribute('content');
714
+ var layout=document.querySelector('meta[name="stx-layout"]');
715
+ return defaultLayoutGroup(layout?layout.getAttribute('content'):'');
716
+ }
717
+ function getDocLayoutGroup(doc,layout){
718
+ var meta=doc&&doc.querySelector?doc.querySelector('meta[name="stx-layout-group"]'):null;
719
+ return meta&&meta.getAttribute('content')?meta.getAttribute('content'):defaultLayoutGroup(layout||'');
696
720
  }
697
- function checkLayoutChange(newLayout,targetUrl){
721
+ function checkLayoutChange(newLayout,targetUrl,newGroup){
698
722
  var currentLayout=document.querySelector('meta[name="stx-layout"]');
699
723
  var curLayoutName=currentLayout?currentLayout.getAttribute('content'):'';
700
- var curGroup=getLayoutGroup(curLayoutName);
701
- var newGroup=getLayoutGroup(newLayout);
702
- console.log('[router] layout check: current='+curLayoutName+'('+curGroup+') new='+(newLayout||'')+'('+newGroup+')');
724
+ var curGroup=getCurrentLayoutGroup();
725
+ var nextGroup=newGroup||defaultLayoutGroup(newLayout);
726
+ log('[router] layout check: current='+curLayoutName+'('+curGroup+') new='+(newLayout||'')+'('+nextGroup+')');
703
727
  // Different layout GROUP (e.g. app \u2192 auth): full reload
704
- if(curGroup!==newGroup){
705
- console.log('[router] layout group change:',curGroup,'\u2192',newGroup,'\u2014 full reload to:',targetUrl);
728
+ if(curGroup!==nextGroup){
729
+ log('[router] layout group change:',curGroup,'\u2192',nextGroup,'\u2014 full reload to:',targetUrl);
706
730
  return true;
707
731
  }
708
732
  // Same group but different SPECIFIC layout (e.g. layouts/app \u2192 layouts/coach):
709
733
  // full body swap so nav, sidebar, and other layout-level elements update
710
734
  if(curLayoutName && newLayout && curLayoutName!==newLayout){
711
- console.log('[router] layout name change:',curLayoutName,'\u2192',newLayout,'\u2014 full body swap');
735
+ log('[router] layout name change:',curLayoutName,'\u2192',newLayout,'\u2014 full body swap');
712
736
  return true;
713
737
  }
714
738
  return false;
715
739
  }
716
740
 
717
741
  function navigate(url,pushState,force){
718
- console.log('[router] navigate() called:',url,'isNavigating:',isNavigating);
742
+ log('[router] navigate() called:',url,'isNavigating:',isNavigating);
719
743
  if(isNavigating)return;
720
744
  var t=new URL(url,location.origin);
721
745
 
@@ -728,27 +752,33 @@ function getRouterScript() {
728
752
  return;
729
753
  }
730
754
 
731
- if(t.href===location.href&&!t.hash&&!force)return;
755
+ // Skip if user clicks a link to the page they're already on. We
756
+ // deliberately allow popstate (pushState===false) through because
757
+ // the browser has already updated location.href to the popped
758
+ // entry \u2014 without this allowance, hitting back would early-return
759
+ // and the visible page content would be left frozen on the
760
+ // forward-navigation page.
761
+ if(pushState!==false&&t.href===location.href&&!t.hash&&!force)return;
732
762
 
733
763
  isNavigating=true;
734
764
  document.body.classList.add(o.loadingClass);
735
765
  startProgress();
736
766
 
737
767
  var targetHref=t.href;
738
- var targetPath=t.pathname;
768
+ var targetPath=cacheKey(url);
739
769
  var targetHash=t.hash;
740
770
 
741
771
  function done(){isNavigating=false;document.body.classList.remove(o.loadingClass);finishProgress()}
742
772
 
743
773
  if(o.cache&&cache[targetPath]&&!force){
744
- if(checkLayoutChange(layoutCache[targetPath],url)){
774
+ if(checkLayoutChange(layoutCache[targetPath],url,layoutGroupCache[targetPath])){
745
775
  // Layout changed \u2014 fetch full page and do full document swap
746
- console.log('[router] cache hit but layout changed \u2014 fetching full page');
776
+ log('[router] cache hit but layout changed \u2014 fetching full page');
747
777
  fetch(url,{headers:{'Accept':'text/html'}}).then(function(r){
748
778
  if(!r.ok)throw new Error(r.status);
749
779
  return r.text();
750
780
  }).then(function(html){
751
- console.log('[router] full page fetched from cache path, len:',html.length);
781
+ log('[router] full page fetched from cache path, len:',html.length);
752
782
  swap(html,targetPath,pushState,targetHash);
753
783
  }).catch(function(err){
754
784
  console.error('[router] full page fetch error:',err);
@@ -766,23 +796,24 @@ else {
766
796
  if(!r.ok)throw new Error(r.status);
767
797
  var isFragment=r.headers.get('X-STX-Fragment')==='true';
768
798
  var newLayout=r.headers.get('X-STX-Layout')||'';
799
+ var newGroup=r.headers.get('X-STX-Layout-Group')||'';
769
800
  // Layout change? Fetch the FULL page (no X-STX-Router header) and do full document swap
770
- if(isFragment&&checkLayoutChange(newLayout,url)){
771
- console.log('[router] layout change \u2014 fetching full page for document swap');
801
+ if(isFragment&&checkLayoutChange(newLayout,url,newGroup)){
802
+ log('[router] layout change \u2014 fetching full page for document swap');
772
803
  return fetch(url,{headers:{'Accept':'text/html'}}).then(function(fullRes){
773
- console.log('[router] full page fetched:',fullRes.status,'ok:',fullRes.ok);
804
+ log('[router] full page fetched:',fullRes.status,'ok:',fullRes.ok);
774
805
  if(!fullRes.ok)throw new Error(fullRes.status);
775
806
  return fullRes.text().then(function(html){
776
- console.log('[router] full page html length:',html.length);
777
- return{html:html,isFragment:false,layout:newLayout};
807
+ log('[router] full page html length:',html.length);
808
+ return{html:html,isFragment:false,layout:newLayout,layoutGroup:newGroup};
778
809
  });
779
810
  });
780
811
  }
781
- return r.text().then(function(html){return{html:html,isFragment:isFragment,layout:newLayout}});
812
+ return r.text().then(function(html){return{html:html,isFragment:isFragment,layout:newLayout,layoutGroup:newGroup}});
782
813
  }).then(function(result){
783
814
  if(!result)return;
784
815
  if(result.isFragment)result.html='<!--stx-fragment-->'+result.html;
785
- if(o.cache){cache[targetPath]=result.html;layoutCache[targetPath]=result.layout}
816
+ if(o.cache){cache[targetPath]=result.html;layoutCache[targetPath]=result.layout;layoutGroupCache[targetPath]=result.layoutGroup}
786
817
  swap(result.html,targetPath,pushState,targetHash);
787
818
  }).catch(function(err){
788
819
  console.error('[router] fetch error:',err);
@@ -795,8 +826,8 @@ else {
795
826
  var isFragment=html.indexOf('<!--stx-fragment-->')===0;
796
827
  if(isFragment)html=html.slice('<!--stx-fragment-->'.length);
797
828
  var currentContent=getContainer();
798
- console.log('[router] swap: isFragment='+isFragment+' container='+!!currentContent+' htmlLen='+html.length);
799
- if(!currentContent){console.log('[router] no container \u2014 falling back');location.href=url;return}
829
+ log('[router] swap: isFragment='+isFragment+' container='+!!currentContent+' tag='+(currentContent&&currentContent.tagName)+' selector='+containerSel+' htmlLen='+html.length);
830
+ if(!currentContent){log('[router] no container \u2014 falling back');location.href=url;return}
800
831
 
801
832
  // Fragment mode: server returned just the page content (no document wrapper)
802
833
  if(isFragment){
@@ -851,10 +882,22 @@ else {
851
882
  else if(hash){var el=document.querySelector(hash);if(el)el.scrollIntoView({behavior:'smooth'})}
852
883
  window.dispatchEvent(new CustomEvent('stx:navigate',{detail:{url:url}}));
853
884
  // Execute page scripts FIRST \u2014 they define setup functions and set _latestSetup
854
- console.log('[router] frag scripts:', fragScripts.length);
885
+ log('[router] frag scripts:', fragScripts.length);
855
886
  document.querySelectorAll('script[data-stx-page]').forEach(function(s){s.remove()});
856
887
  fragScripts.forEach(function(code){
857
- console.log('[router] exec script len:', code.length, 'has __stx_setup:', code.indexOf('__stx_setup')>-1);
888
+ log('[router] exec script len:', code.length, 'has __stx_setup:', code.indexOf('__stx_setup')>-1);
889
+ // Skip scripts that were already executed (layout-level partials
890
+ // like theme.stx, stores.stx, nav.stx). Their top-level const/let
891
+ // declarations would throw "Identifier has already been declared"
892
+ // on re-execution. Setup functions (__stx_setup_) must always
893
+ // re-execute because each page has its own setup.
894
+ var h=hashScript(code);
895
+ var isSetup=code.indexOf('__stx_setup_')!==-1;
896
+ if(!isSetup&&executedScriptHashes[h]){
897
+ log('[router] skipping already-executed script (hash dedup)');
898
+ return;
899
+ }
900
+ executedScriptHashes[h]=1;
858
901
  var ns=document.createElement('script');
859
902
  // Wrap in block scope to prevent const/let collisions on re-navigation.
860
903
  // Without this, navigating away and back throws "Identifier has already
@@ -865,7 +908,7 @@ else {
865
908
  ns.setAttribute('data-stx-page','');
866
909
  document.body.appendChild(ns);
867
910
  });
868
- console.log('[router] scripts done. _latestSetup:', !!window.stx._latestSetup);
911
+ log('[router] scripts done. _latestSetup:', !!window.stx._latestSetup);
869
912
  // THEN fire stx:load \u2014 now _latestSetup is set and processElement has the right scope
870
913
  window.dispatchEvent(new Event('stx:load'));
871
914
  }
@@ -937,12 +980,14 @@ else {
937
980
  if(newBody){
938
981
  var newMeta=doc.querySelector('meta[name="stx-layout"]');
939
982
  var curMeta=document.querySelector('meta[name="stx-layout"]');
940
- var curGroup=getLayoutGroup(curMeta?curMeta.getAttribute('content'):'');
941
- var newGroup=getLayoutGroup(newMeta?newMeta.getAttribute('content'):'');
942
- isLayoutChange=curGroup!==newGroup;
983
+ var curLayout=curMeta?curMeta.getAttribute('content'):'';
984
+ var newLayout=newMeta?newMeta.getAttribute('content'):'';
985
+ var curGroup=getCurrentLayoutGroup();
986
+ var newGroup=getDocLayoutGroup(doc,newLayout);
987
+ isLayoutChange=curGroup!==newGroup||(curLayout&&newLayout&&curLayout!==newLayout);
943
988
  }
944
989
  if(isLayoutChange&&newBody){
945
- console.log('[router] full body swap for layout change');
990
+ log('[router] full body swap for layout change');
946
991
  // Replace entire body content \u2014 layout chrome and all
947
992
  var bodyHTML=newBody.innerHTML.replace(new RegExp('<scr'+'ipt\\\\b[^>]*>[\\\\s\\\\S]*?<\\\\/scr'+'ipt\\\\s*>','gi'),'');
948
993
  document.body.innerHTML=bodyHTML;
@@ -953,6 +998,10 @@ else {
953
998
  var freshMeta=doc.querySelector('meta[name="stx-layout"]');
954
999
  if(oldMeta&&freshMeta)oldMeta.setAttribute('content',freshMeta.getAttribute('content')||'');
955
1000
  else if(freshMeta){var m=document.createElement('meta');m.name='stx-layout';m.content=freshMeta.getAttribute('content')||'';document.head.appendChild(m)}
1001
+ var oldGroupMeta=document.querySelector('meta[name="stx-layout-group"]');
1002
+ var freshGroupMeta=doc.querySelector('meta[name="stx-layout-group"]');
1003
+ if(oldGroupMeta&&freshGroupMeta)oldGroupMeta.setAttribute('content',freshGroupMeta.getAttribute('content')||'');
1004
+ else if(freshGroupMeta){var gm=document.createElement('meta');gm.name='stx-layout-group';gm.content=freshGroupMeta.getAttribute('content')||'';document.head.appendChild(gm)}
956
1005
  // Update container reference for script execution below
957
1006
  currentContent=document.querySelector(containerSel)||document.querySelector('main')||document.body;
958
1007
  } else {
@@ -1140,12 +1189,12 @@ else {
1140
1189
  }
1141
1190
  if(!link){return}
1142
1191
  var href=link.getAttribute('href');
1143
- console.log('[router] click intercepted:',href,'container:',!!getContainer(),'defaultPrevented:',e.defaultPrevented);
1144
- if(!href||href.startsWith('http')||href.startsWith('#')||href.startsWith('mailto:')||href.startsWith('tel:')){console.log('[router] skipped:',href);return}
1192
+ log('[router] click intercepted:',href,'container:',!!getContainer(),'defaultPrevented:',e.defaultPrevented);
1193
+ if(!href||href.startsWith('http')||href.startsWith('#')||href.startsWith('mailto:')||href.startsWith('tel:')){log('[router] skipped:',href);return}
1145
1194
  if(link.target==='_blank'||link.hasAttribute('download'))return;
1146
1195
  e.preventDefault();
1147
1196
  e.stopPropagation();
1148
- console.log('[router] navigating to:',href);
1197
+ log('[router] navigating to:',href);
1149
1198
  navigate(href);
1150
1199
  },true);
1151
1200
 
@@ -1161,15 +1210,17 @@ else {
1161
1210
  var link=e.target.closest('[data-stx-link]');
1162
1211
  if(!link)return;
1163
1212
  var href=link.getAttribute('href');
1164
- if(cache[href]||prefetching[href])return;
1165
- prefetching[href]=true;
1213
+ var key=cacheKey(href);
1214
+ if(cache[key]||prefetching[key])return;
1215
+ prefetching[key]=true;
1166
1216
  fetch(href,{headers:{'X-STX-Router':'true','Accept':'text/html'}}).then(function(r){
1167
1217
  var isFrag=r.headers.get('X-STX-Fragment')==='true';
1168
1218
  var pLayout=r.headers.get('X-STX-Layout')||'';
1169
- return r.text().then(function(html){return{html:isFrag?'<!--stx-fragment-->'+html:html,layout:pLayout}});
1219
+ var pGroup=r.headers.get('X-STX-Layout-Group')||'';
1220
+ return r.text().then(function(html){return{html:isFrag?'<!--stx-fragment-->'+html:html,layout:pLayout,layoutGroup:pGroup}});
1170
1221
  }).then(function(result){
1171
- if(o.cache){cache[href]=result.html;layoutCache[href]=result.layout}
1172
- }).catch(function(){}).finally(function(){delete prefetching[href]});
1222
+ if(o.cache){cache[key]=result.html;layoutCache[key]=result.layout;layoutGroupCache[key]=result.layoutGroup}
1223
+ }).catch(function(){}).finally(function(){delete prefetching[key]});
1173
1224
  },true);
1174
1225
  }
1175
1226
 
@@ -1262,11 +1313,12 @@ else {
1262
1313
  navigate:navigate,
1263
1314
  navigateTo:navigate,
1264
1315
  prefetch:function(url){
1265
- if(!cache[url]){
1266
- fetch(url,{headers:{'X-STX-Router':'true'}}).then(function(r){var isFrag=r.headers.get('X-STX-Fragment')==='true';return r.text().then(function(html){return isFrag?'<!--stx-fragment-->'+html:html})}).then(function(html){cache[url]=html}).catch(function(){});
1316
+ var key=cacheKey(url);
1317
+ if(!cache[key]){
1318
+ fetch(url,{headers:{'X-STX-Router':'true'}}).then(function(r){var isFrag=r.headers.get('X-STX-Fragment')==='true';var pLayout=r.headers.get('X-STX-Layout')||'';var pGroup=r.headers.get('X-STX-Layout-Group')||'';return r.text().then(function(html){return{html:isFrag?'<!--stx-fragment-->'+html:html,layout:pLayout,layoutGroup:pGroup}})}).then(function(result){cache[key]=result.html;layoutCache[key]=result.layout;layoutGroupCache[key]=result.layoutGroup}).catch(function(){});
1267
1319
  }
1268
1320
  },
1269
- clearCache:function(){for(var k in cache)delete cache[k]},
1321
+ clearCache:function(){for(var k in cache)delete cache[k];for(var lk in layoutCache)delete layoutCache[lk];for(var gk in layoutGroupCache)delete layoutGroupCache[gk]},
1270
1322
  cache:cache,
1271
1323
  swap:swap,
1272
1324
  updateNav:updateNav
@@ -1308,6 +1360,8 @@ export {
1308
1360
  formatRoutes,
1309
1361
  findErrorPage,
1310
1362
  filePathToPattern,
1363
+ extractLayoutMetadata,
1364
+ deriveLayoutGroup,
1311
1365
  defineRoutes,
1312
1366
  defineRoute,
1313
1367
  defineMiddleware,
@@ -0,0 +1,23 @@
1
+ // @bun
2
+ // ../router/src/layout-metadata.ts
3
+ function deriveLayoutGroup(layoutPath) {
4
+ const normalized = layoutPath.replace(/\\/g, "/");
5
+ const segments = normalized.split("/").filter(Boolean);
6
+ const layoutsIndex = segments.lastIndexOf("layouts");
7
+ const layoutSegment = layoutsIndex >= 0 ? segments[layoutsIndex + 1] : segments.at(-1);
8
+ if (!layoutSegment)
9
+ return "app";
10
+ return layoutSegment.replace(/\.stx$/i, "") || "app";
11
+ }
12
+ function extractLayoutMetadata(html) {
13
+ const layoutMeta = html.match(/<meta\b[^>]*name=["']stx-layout["'][^>]*content=["']([^"']+)["'][^>]*>/i);
14
+ const groupMeta = html.match(/<meta\b[^>]*name=["']stx-layout-group["'][^>]*content=["']([^"']+)["'][^>]*>/i);
15
+ const layoutComment = html.match(/<!--\s*stx-layout:\s*([^ ]+)\s*-->/i);
16
+ const layout = layoutMeta?.[1] || layoutComment?.[1] || "";
17
+ return {
18
+ layout,
19
+ group: groupMeta?.[1] || deriveLayoutGroup(layout)
20
+ };
21
+ }
22
+
23
+ export { deriveLayoutGroup, extractLayoutMetadata };
package/dist/serve.js CHANGED
@@ -1,5 +1,8 @@
1
1
  #!/usr/bin/env bun
2
2
  // @bun
3
+ import {
4
+ deriveLayoutGroup
5
+ } from "./chunk-93ete56h.js";
3
6
  import {
4
7
  __require
5
8
  } from "./chunk-2mx7fq49.js";
@@ -8956,7 +8959,7 @@ After installing: bun add bun-plugin-stx`);
8956
8959
  pageMiddlewarePatterns.length = 0;
8957
8960
  await Promise.all(files.map((f) => detectPageMiddleware(f)));
8958
8961
  try {
8959
- const { Router } = await import("./chunk-mf3agv0w.js");
8962
+ const { Router } = await import("./chunk-0nwya50v.js");
8960
8963
  const pagesDirs = patterns.map((p) => p.replace(/\/$/, "")).filter(Boolean);
8961
8964
  const router = new Router(process19.cwd(), { pagesDirs });
8962
8965
  console.log(`[stx] Generated ${router.routes.length} routes \u2192 .stx/routes.ts`);
@@ -9540,6 +9543,7 @@ ${output}`;
9540
9543
  if (isSpaNav) {
9541
9544
  const layoutMatch = content.match(/<!-- stx-layout: ([^ ]+) -->/);
9542
9545
  const pageLayout = layoutMatch ? layoutMatch[1] : "default";
9546
+ const pageLayoutGroup = deriveLayoutGroup(pageLayout);
9543
9547
  let fragment = content;
9544
9548
  const headStyles = [];
9545
9549
  const headMatch = content.match(/<head\b[^>]*>([\s\S]*?)<\/head>/i);
@@ -9608,6 +9612,7 @@ ${pageSetupScripts.join(`
9608
9612
  "Content-Type": "text/html; charset=utf-8",
9609
9613
  "X-STX-Fragment": "true",
9610
9614
  "X-STX-Layout": pageLayout,
9615
+ "X-STX-Layout-Group": pageLayoutGroup,
9611
9616
  "Cache-Control": "no-cache",
9612
9617
  ...corsHeaders
9613
9618
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "bun-plugin-stx",
3
3
  "type": "module",
4
- "version": "0.2.47",
4
+ "version": "0.2.51",
5
5
  "description": "A Bun plugin that allows for using Laravel Blade-like syntax.",
6
6
  "author": "Chris Breuer <chris@stacksjs.org>",
7
7
  "license": "MIT",
@@ -43,8 +43,9 @@
43
43
  "import": "./dist/serve.js"
44
44
  },
45
45
  "./*": {
46
- "bun": "./dist/*",
47
- "import": "./dist/*"
46
+ "bun": "./dist/*.js",
47
+ "types": "./dist/*.d.ts",
48
+ "import": "./dist/*.js"
48
49
  }
49
50
  },
50
51
  "module": "./dist/index.js",
@@ -72,6 +73,7 @@
72
73
  "typecheck": "bun tsc --noEmit"
73
74
  },
74
75
  "dependencies": {
75
- "@stacksjs/stx": "0.2.47"
76
+ "@stacksjs/stx": "0.2.51",
77
+ "stx-router": "0.2.51"
76
78
  }
77
79
  }