bun-plugin-stx 0.2.384 → 0.2.386

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.
@@ -860,6 +860,83 @@ function routerSource() {
860
860
  }
861
861
  }
862
862
 
863
+ // \u2500\u2500 Instant navigation: one frame, already filled \u2500\u2500
864
+ // A tab swap used to paint the new screen before its scripts had run: a page
865
+ // script with an import is a module, and a module runs after the task that
866
+ // inserted it, so for a few frames the screen showed its bare markup and then
867
+ // filled in, on every visit. Each inserted page script now reports when it
868
+ // has run, and an instant navigation holds the old frame (a View Transition
869
+ // with no animation) until they all have, capped so it can never hang.
870
+ var scriptsPending=0;
871
+ var hydrateWaiters=[];
872
+ window.__stxScriptRan=function(){
873
+ if(scriptsPending>0)scriptsPending--;
874
+ if(!scriptsPending){var w=hydrateWaiters;hydrateWaiters=[];w.forEach(function(f){f()})}
875
+ };
876
+ function markRan(code){scriptsPending++;return code+String.fromCharCode(10)+';window.__stxScriptRan&&window.__stxScriptRan();'}
877
+ function whenHydrated(capMs){
878
+ return new Promise(function(resolve){
879
+ var done=false;
880
+ function finish(){if(done)return;done=true;setTimeout(resolve,0)}
881
+ if(!scriptsPending){done=true;resolve();return}
882
+ hydrateWaiters.push(finish);
883
+ setTimeout(finish,capMs);
884
+ });
885
+ }
886
+ // Whether a fragment's scripts all run inside the swap's own task: none is
887
+ // a module, and every file it names is already loaded. Then an instant
888
+ // navigation needs no held frame at all: swapped, run and bound before the
889
+ // browser next paints, it shows the new screen whole in the first frame.
890
+ function swapRunsInOneTask(html,base){
891
+ var sync=true;
892
+ html.replace(new RegExp('<scr'+'ipt\\\\b([^>]*)>([\\\\s\\\\S]*?)<\\\\/scr'+'ipt>','gi'),function(m,attrs,code){
893
+ if(!sync)return m;
894
+ var typeMatch=attrs.match(/\\btype\\s*=\\s*(?:"([^"]*)"|'([^']*)'|([^\\s>]+))/i);
895
+ var scriptType=((typeMatch&&(typeMatch[1]||typeMatch[2]||typeMatch[3]))||'').trim().toLowerCase();
896
+ if(scriptType==='module'){sync=false;return m}
897
+ if(scriptType&&scriptType!=='text/javascript'&&scriptType!=='application/javascript')return m;
898
+ var srcMatch=attrs.match(/\\bsrc\\s*=\\s*(?:"([^"]*)"|'([^']*)'|([^\\s>]+))/i);
899
+ var src=srcMatch&&(srcMatch[1]||srcMatch[2]||srcMatch[3]);
900
+ if(src){
901
+ if(!isSharedClientScriptAttrs(attrs)){var key=externalScriptKey(src,base);if(key&&!loadedExternalScripts[key])sync=false}
902
+ return m;
903
+ }
904
+ if(code&&hasStaticImport(code))sync=false;
905
+ return m;
906
+ });
907
+ return sync;
908
+ }
909
+ function runInOneTask(complete){
910
+ // The runtime binds on stx:load after a debounce; bind now, in this task.
911
+ function flush(){if(window.stx&&typeof window.stx._flushLoad==='function')window.stx._flushLoad()}
912
+ window.addEventListener('stx:load',flush,{once:true});
913
+ try{complete()}finally{window.removeEventListener('stx:load',flush)}
914
+ }
915
+ function runInstantSwap(complete){
916
+ if(!document.startViewTransition)return false;
917
+ var root=document.documentElement;
918
+ root.classList.add('stx-instant');
919
+ var clear=function(){root.classList.remove('stx-instant')};
920
+ try{
921
+ var transition=document.startViewTransition(function(){
922
+ scriptsPending=0;
923
+ // The runtime binds the page on stx:load after a short debounce; run
924
+ // it now, so the frame this reveals is bound rather than showing its
925
+ // {{ }} for a frame before they fill in.
926
+ var loaded=new Promise(function(resolve){window.addEventListener('stx:load',function(){if(window.stx&&typeof window.stx._flushLoad==='function')window.stx._flushLoad();resolve()},{once:true});setTimeout(resolve,300)});
927
+ complete();
928
+ return loaded.then(function(){return whenHydrated(300)});
929
+ });
930
+ if(transition&&transition.finished&&transition.finished.then)transition.finished.then(clear,clear);else clear();
931
+ if(transition&&transition.ready&&transition.ready.catch)transition.ready.catch(function(){});
932
+ return true;
933
+ }catch(err){
934
+ clear();
935
+ log('[router] instant transition unavailable:',err&&err.message?err.message:err);
936
+ return false;
937
+ }
938
+ }
939
+
863
940
  // \u2500\u2500 Progress bar (0\u2192100% at top of viewport) \u2500\u2500
864
941
  // Native loading indicator baked into the router. Starts when navigation
865
942
  // begins, trickles up to ~90% during fetch, snaps to 100% + fades on
@@ -1171,19 +1248,13 @@ function routerSource() {
1171
1248
  * Column-anchored: a declaration nested inside a function is indented, and
1172
1249
  * is not in scope at the end of the block anyway.
1173
1250
  */
1174
- var TOP_LEVEL_DECL=/^(?:export\\s+)?(?:async\\s+)?(?:function\\s*\\*?|class|const|let|var)\\s+([A-Za-z_$][\\w$]*)/gm;
1251
+ var TLD=/^(?:export\\s+)?(?:async\\s+)?(?:function\\s*\\*?|class|const|let|var)\\s+([$\\w]+)/gm;
1175
1252
  function republishTopLevel(src){
1176
- var names=[],m;
1177
- TOP_LEVEL_DECL.lastIndex=0;
1178
- while((m=TOP_LEVEL_DECL.exec(src))!==null){
1179
- if(names.indexOf(m[1])===-1)names.push(m[1]);
1180
- }
1181
- if(!names.length)return '';
1182
- var out='\\n;';
1183
- for(var i=0;i<names.length;i++){
1184
- out+='try{if(typeof '+names[i]+'==="function")window.'+names[i]+'='+names[i]+'}catch(e){}';
1185
- }
1186
- return out;
1253
+ var n=[],m,o='',i;
1254
+ TLD.lastIndex=0;
1255
+ while((m=TLD.exec(src))!==null)if(n.indexOf(m[1])<0)n.push(m[1]);
1256
+ for(i=0;i<n.length;i++)o+='try{typeof '+n[i]+'=="function"&&(window.'+n[i]+'='+n[i]+')}catch(e){}';
1257
+ return o?'\\n;'+o:'';
1187
1258
  }
1188
1259
  function runsAlways(declared, code){
1189
1260
  if(declared==='always')return true;
@@ -1991,7 +2062,7 @@ else {
1991
2062
  if(hasImport){
1992
2063
  ns.type='module';
1993
2064
  }
1994
- ns.textContent=(hasImport||isAlreadyScoped)?code:'{'+code+'}';
2065
+ ns.textContent=markRan((hasImport||isAlreadyScoped)?code:'{'+code+'}');
1995
2066
  ns.setAttribute('data-stx-page','');
1996
2067
  if(placeholder&&placeholder.parentNode){
1997
2068
  ns.setAttribute('data-stx-positioned','');
@@ -2032,7 +2103,9 @@ else {
2032
2103
  resolve(true);
2033
2104
  }catch(err){reject(err)}
2034
2105
  }
2035
- if(runViewTransition(completeFragSwap)){}
2106
+ if(instantNav&&swapRunsInOneTask(html,url)){runInOneTask(completeFragSwap)}
2107
+ else if(instantNav&&runInstantSwap(completeFragSwap)){}
2108
+ else if(runViewTransition(completeFragSwap)){}
2036
2109
  else if(instantNav){completeFragSwap()}
2037
2110
  else{currentContent.style.transition='opacity 0.12s ease-out';currentContent.style.opacity='0';setTimeout(function(){completeFragSwap();currentContent.style.opacity='1';setTimeout(function(){currentContent.style.transition=''},150)},120)}
2038
2111
  });
@@ -2448,7 +2521,7 @@ else {
2448
2521
  // ESM, and top-level 'import' is illegal inside a block, which
2449
2522
  // would throw SyntaxError before the script ever runs.
2450
2523
  var alreadyScoped=runsAlways(typeof entry==='string'?'':entry.run,text);
2451
- ns.textContent=(hasImport||alreadyScoped)?text:'{'+text+republishTopLevel(text)+'}';
2524
+ ns.textContent=markRan((hasImport||alreadyScoped)?text:'{'+text+republishTopLevel(text)+'}');
2452
2525
  ns.setAttribute('data-stx-page','');
2453
2526
  var placeholder=entry.slot?qs('script[data-stx-route-script="'+entry.slot+'"]'):null;
2454
2527
  if(placeholder&&placeholder.parentNode){
@@ -2482,7 +2555,9 @@ else {
2482
2555
  resolve(true);
2483
2556
  }catch(err){reject(err)}
2484
2557
  }
2485
- if(runViewTransition(completeSwap)){
2558
+ if(instantNav&&runInstantSwap(completeSwap)){
2559
+ }
2560
+ else if(runViewTransition(completeSwap)){
2486
2561
  }
2487
2562
  else if(instantNav){
2488
2563
  // A tab: swap at once, no fade either way.
@@ -2777,9 +2852,25 @@ else {
2777
2852
  return readPrefetchResponse(r,wantsFragment);
2778
2853
  }).then(function(result){
2779
2854
  if(result&&o.cache)setCache(key,result.html,result.layout,result.layoutGroup,result.title,result.containerAttrs);
2855
+ if(result)loadModuleRegistries(result.html,href);
2780
2856
  }).catch(function(){}).finally(function(){delete prefetching[key]});
2781
2857
  }
2782
2858
 
2859
+ // A prefetched page's module registry (the file its imports are bundled
2860
+ // into, one per page) is loaded with the prefetch, not on the tap. It only
2861
+ // registers modules, once, so loading it early changes nothing but timing,
2862
+ // and the tap then finds every script it needs already here: the swap runs
2863
+ // in one task and the first visit to a tab paints whole, like the second.
2864
+ function loadModuleRegistries(html,base){
2865
+ html.replace(new RegExp('<scr'+'ipt\\\\b([^>]*)>','gi'),function(m,attrs){
2866
+ if(!/(?:^|\\s)data-stx-modules(?:[\\s=]|$)/i.test(attrs))return m;
2867
+ var srcMatch=attrs.match(/\\bsrc\\s*=\\s*(?:"([^"]*)"|'([^']*)'|([^\\s>]+))/i);
2868
+ var src=srcMatch&&(srcMatch[1]||srcMatch[2]||srcMatch[3]);
2869
+ if(src)loadExternalScript(src,base);
2870
+ return m;
2871
+ });
2872
+ }
2873
+
2783
2874
  // Links marked data-stx-prefetch="eager", fetched once the page is idle, so
2784
2875
  // even their first tap is served from the cache: the way a phone's tab bar
2785
2876
  // switches instantly. Not on a connection that asked to save data.
@@ -2974,7 +3065,7 @@ else {
2974
3065
  // The container the router focuses after a navigation is not a
2975
3066
  // control, so it draws no focus ring: Safari drew one around the whole
2976
3067
  // page, a blue line along its bottom edge on every screen.
2977
- var css='[data-stx-route-focus]:focus{outline:none}.stx-navigating{cursor:wait}.stx-navigating a,.stx-navigating button{pointer-events:none}#stx-router-progress{position:fixed;top:0;left:0;right:0;height:'+ph+';background:'+pc+';box-shadow:0 0 8px '+pc+',0 0 4px '+pc+';transform:scaleX(0);transform-origin:left;transition:transform .18s ease-out,opacity .26s ease;opacity:0;pointer-events:none;z-index:999999}';
3068
+ var css='[data-stx-route-focus]:focus{outline:none}.stx-navigating{cursor:wait}.stx-navigating a,.stx-navigating button{pointer-events:none}#stx-router-progress{position:fixed;top:0;left:0;right:0;height:'+ph+';background:'+pc+';box-shadow:0 0 8px '+pc+',0 0 4px '+pc+';transform:scaleX(0);transform-origin:left;transition:transform .18s ease-out,opacity .26s ease;opacity:0;pointer-events:none;z-index:999999}html.stx-instant::view-transition-group(*),html.stx-instant::view-transition-old(*),html.stx-instant::view-transition-new(*){animation:none!important}';
2978
3069
  if(o.viewTransitions&&'startViewTransition' in document){
2979
3070
  var dur=(o.viewTransitionDuration||220)+'ms';
2980
3071
  var ease=o.viewTransitionEasing||'cubic-bezier(0.16, 1, 0.3, 1)';
package/dist/serve.js CHANGED
@@ -9800,7 +9800,7 @@ function __stxOverlay(errs){
9800
9800
  rebuildDiscoveredPagePaths(kept);
9801
9801
  await indexPageMiddleware(kept);
9802
9802
  try {
9803
- const { Router } = await import("./chunk-c7nhgech.js");
9803
+ const { Router } = await import("./chunk-cmaavsv0.js");
9804
9804
  const pagesDirs = patterns.map((p) => p.replace(/\/$/, "")).filter(Boolean);
9805
9805
  const router = new Router(process19.cwd(), { pagesDirs, stateDir: stateDirName(), exclude: options.exclude });
9806
9806
  if (!options.quiet)
@@ -10457,7 +10457,7 @@ ${block}`;
10457
10457
  _server = bunServe({
10458
10458
  port: actualPort,
10459
10459
  reusePort: options.reusePort ?? false,
10460
- idleTimeout: production ? 30 : 0,
10460
+ idleTimeout: production ? 120 : 0,
10461
10461
  async fetch(req, server) {
10462
10462
  const warmup = awaitImageWarmup();
10463
10463
  if (warmup)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "bun-plugin-stx",
3
3
  "type": "module",
4
- "version": "0.2.384",
4
+ "version": "0.2.386",
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",
@@ -74,7 +74,7 @@
74
74
  "typecheck": "bun tsc --noEmit"
75
75
  },
76
76
  "dependencies": {
77
- "@stacksjs/stx": "0.2.384",
78
- "stx-router": "0.2.384"
77
+ "@stacksjs/stx": "0.2.386",
78
+ "stx-router": "0.2.386"
79
79
  }
80
80
  }