bun-plugin-stx 0.2.249 → 0.2.251
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.
|
@@ -1011,10 +1011,68 @@ function getRouterScript() {
|
|
|
1011
1011
|
function fragmentMarker(declared){
|
|
1012
1012
|
return '<!--stx-fragment'+(declared==='true'?' rt=1':declared==='false'?' rt=0':'')+'-->';
|
|
1013
1013
|
}
|
|
1014
|
+
// \u2500\u2500 External scripts inside the routed container \u2500\u2500
|
|
1015
|
+
//
|
|
1016
|
+
// A page whose x-data factory lives in its own file \u2014 a
|
|
1017
|
+
// <script src="dashboard-xdata.js"> sitting inside [data-stx-content] \u2014 lost that
|
|
1018
|
+
// file on every SPA navigation. Both swap paths dropped it: the fragment
|
|
1019
|
+
// path's regex sees an empty body and returns '', and the full-document path
|
|
1020
|
+
// removed it outright. The inline scripts beside it were re-executed with
|
|
1021
|
+
// care; the external ones were deleted.
|
|
1022
|
+
//
|
|
1023
|
+
// The result was a screen that rendered on a cold load and came back empty
|
|
1024
|
+
// after a click, reporting "dashboardXData is not defined" with every binding
|
|
1025
|
+
// in the container left unevaluated \u2014 because nothing had defined it since
|
|
1026
|
+
// the file was thrown away.
|
|
1027
|
+
//
|
|
1028
|
+
// Loaded once per URL, never re-run. What these files define is a global that
|
|
1029
|
+
// outlives the navigation, so a second execution buys nothing and costs
|
|
1030
|
+
// whatever side effects the file has: a mount helper firing again on every
|
|
1031
|
+
// click through the same page is its own bug.
|
|
1032
|
+
var loadedExternalScripts={};
|
|
1033
|
+
// The base is resolved first because callers pass what they have: the
|
|
1034
|
+
// fragment path knows only the path it navigated to ('/reports/disk'), and
|
|
1035
|
+
// new URL(src, '/reports/disk') throws \u2014 a base must be absolute. Silently
|
|
1036
|
+
// returning '' there meant the file was never requested, which is the bug
|
|
1037
|
+
// this whole block exists to fix, reintroduced one level down.
|
|
1038
|
+
function externalScriptKey(src,base){
|
|
1039
|
+
try{
|
|
1040
|
+
var absoluteBase=base?new URL(base,location.href).href:location.href;
|
|
1041
|
+
return new URL(src,absoluteBase).href;
|
|
1042
|
+
}catch(e){return ''}
|
|
1043
|
+
}
|
|
1044
|
+
function rememberExternalScript(src,base){
|
|
1045
|
+
var key=externalScriptKey(src,base);
|
|
1046
|
+
if(key)loadedExternalScripts[key]=1;
|
|
1047
|
+
return key;
|
|
1048
|
+
}
|
|
1049
|
+
// Returns a promise, or null when there is nothing to wait for \u2014 an already
|
|
1050
|
+
// loaded file, or an unparseable src. Resolves rather than rejects on error:
|
|
1051
|
+
// one 404 must not stop the rest of the page hydrating, matching what the
|
|
1052
|
+
// head-script path already does by sending failures to execScripts too.
|
|
1053
|
+
function loadExternalScript(src,base){
|
|
1054
|
+
var key=externalScriptKey(src,base);
|
|
1055
|
+
if(!key||loadedExternalScripts[key])return null;
|
|
1056
|
+
loadedExternalScripts[key]=1;
|
|
1057
|
+
return new Promise(function(resolve){
|
|
1058
|
+
var el=document.createElement('script');
|
|
1059
|
+
el.src=key;
|
|
1060
|
+
// Deliberately NOT data-stx-page: both swap paths clear those on every
|
|
1061
|
+
// navigation, which would sweep this away and \u2014 since it is never loaded
|
|
1062
|
+
// twice \u2014 leave the document without it. These are loaded once and stay,
|
|
1063
|
+
// the same as the external head scripts beside them.
|
|
1064
|
+
el.setAttribute('data-stx-external','');
|
|
1065
|
+
el.onload=function(){resolve()};
|
|
1066
|
+
el.onerror=function(){log('[router] container script failed:',key);resolve()};
|
|
1067
|
+
document.head.appendChild(el);
|
|
1068
|
+
});
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1014
1071
|
// Record scripts from the initial page load
|
|
1015
1072
|
document.querySelectorAll('script').forEach(function(s){
|
|
1016
1073
|
var text=s.textContent||'';
|
|
1017
|
-
if(
|
|
1074
|
+
if(s.hasAttribute('src'))rememberExternalScript(s.getAttribute('src'));
|
|
1075
|
+
else if(text.trim())executedScriptHashes[hashScript(text)]=1;
|
|
1018
1076
|
});
|
|
1019
1077
|
function cacheKey(url){
|
|
1020
1078
|
// Base is the CURRENT document, not the origin root: a relative key like
|
|
@@ -1318,6 +1376,7 @@ else {
|
|
|
1318
1376
|
var fragStyles=[];
|
|
1319
1377
|
var fragCrosswindHrefs=[];
|
|
1320
1378
|
var fragCrosswindCSS=null;
|
|
1379
|
+
var fragExternalScripts=[];
|
|
1321
1380
|
var cleanFrag=html.replace(new RegExp('<scr'+'ipt\\\\b([^>]*)>([\\\\s\\\\S]*?)<\\\\/scr'+'ipt>','gi'),function(m,attrs,code){
|
|
1322
1381
|
// A data block is not code, and everything below this line assumes it
|
|
1323
1382
|
// is: the body gets stashed in fragScripts, re-emitted as a pending
|
|
@@ -1335,6 +1394,12 @@ else {
|
|
|
1335
1394
|
var typeMatch=attrs.match(/\\btype\\s*=\\s*(?:"([^"]*)"|'([^']*)'|([^\\s>]+))/i);
|
|
1336
1395
|
var scriptType=((typeMatch&&(typeMatch[1]||typeMatch[2]||typeMatch[3]))||'').trim().toLowerCase();
|
|
1337
1396
|
if(scriptType&&scriptType!=='text/javascript'&&scriptType!=='application/javascript'&&scriptType!=='module')return m;
|
|
1397
|
+
// An external script has no body, so every check below reads it as
|
|
1398
|
+
// nothing to run and the fragment loses it. Queue it instead; the
|
|
1399
|
+
// file is where the page's factory usually lives.
|
|
1400
|
+
var srcMatch=attrs.match(/\\bsrc\\s*=\\s*(?:"([^"]*)"|'([^']*)'|([^\\s>]+))/i);
|
|
1401
|
+
var fragSrc=srcMatch&&(srcMatch[1]||srcMatch[2]||srcMatch[3]);
|
|
1402
|
+
if(fragSrc){fragExternalScripts.push(fragSrc);return ''}
|
|
1338
1403
|
if(code&&code.trim()&&!isSignalsRuntimeScript({hasAttribute:function(name){return name==='data-stx-runtime'&&attrs.indexOf('data-stx-runtime')!==-1}},code)){
|
|
1339
1404
|
var slot='fragment-'+(++fragScriptId);
|
|
1340
1405
|
var scoped=/(?:^|\\s)data-stx-scoped(?:\\s|=|$)/i.test(attrs);
|
|
@@ -1422,6 +1487,7 @@ else {
|
|
|
1422
1487
|
log('[router] frag scripts:', fragScripts.length);
|
|
1423
1488
|
document.querySelectorAll('script[data-stx-page]').forEach(function(s){s.remove()});
|
|
1424
1489
|
fragScripts.sort(function(a,b){return (a.setupName?1:0)-(b.setupName?1:0)});
|
|
1490
|
+
function runFragScripts(){
|
|
1425
1491
|
fragScripts.forEach(function(entry){
|
|
1426
1492
|
var code=entry.text;
|
|
1427
1493
|
log('[router] exec script len:', code.length, 'has __stx_setup:', code.indexOf('__stx_setup')>-1);
|
|
@@ -1490,6 +1556,20 @@ else {
|
|
|
1490
1556
|
log('[router] scripts done. _latestSetup:', !!(window.stx&&window.stx._latestSetup));
|
|
1491
1557
|
// THEN fire stx:load \u2014 now _latestSetup is set and processElement has the right scope
|
|
1492
1558
|
window.dispatchEvent(new Event('stx:load'));
|
|
1559
|
+
}
|
|
1560
|
+
// The container's own files first, for the same reason as the
|
|
1561
|
+
// full-document path: the inline scripts call into them. Only awaited
|
|
1562
|
+
// when something actually has to load, so a fragment with no external
|
|
1563
|
+
// scripts \u2014 every fragment before this change \u2014 still runs its inline
|
|
1564
|
+
// scripts in the same synchronous turn and nothing about its timing
|
|
1565
|
+
// moves.
|
|
1566
|
+
var fragPending=[];
|
|
1567
|
+
fragExternalScripts.forEach(function(src){
|
|
1568
|
+
var pending=loadExternalScript(src,url);
|
|
1569
|
+
if(pending)fragPending.push(pending);
|
|
1570
|
+
});
|
|
1571
|
+
if(fragPending.length)Promise.all(fragPending).then(runFragScripts).catch(runFragScripts);
|
|
1572
|
+
else runFragScripts();
|
|
1493
1573
|
}
|
|
1494
1574
|
return new Promise(function(resolve,reject){
|
|
1495
1575
|
function completeFragSwap(){
|
|
@@ -1646,12 +1726,15 @@ else {
|
|
|
1646
1726
|
// scripts as placeholders so re-execution keeps document.currentScript
|
|
1647
1727
|
// beside the component template it owns.
|
|
1648
1728
|
var routedBodyScripts=[];
|
|
1729
|
+
var routedExternalScripts=[];
|
|
1649
1730
|
var routedBodyScriptId=0;
|
|
1650
1731
|
function prepareRoutedBodyScripts(root){
|
|
1651
1732
|
if(!root)return;
|
|
1652
1733
|
root.querySelectorAll('script').forEach(function(s){
|
|
1653
1734
|
var text=s.textContent||'';
|
|
1654
|
-
|
|
1735
|
+
// Not discarded: queued against docBase, so a relative src resolves
|
|
1736
|
+
// against the page being navigated to rather than the current URL.
|
|
1737
|
+
if(s.hasAttribute('src')){routedExternalScripts.push(s.getAttribute('src'));s.remove();return}
|
|
1655
1738
|
var type=(s.getAttribute('type')||'').trim().toLowerCase();
|
|
1656
1739
|
var executable=!type||type==='text/javascript'||type==='application/javascript'||type==='module';
|
|
1657
1740
|
if(!executable||!text.trim())return;
|
|
@@ -1715,6 +1798,14 @@ else {
|
|
|
1715
1798
|
}));
|
|
1716
1799
|
});
|
|
1717
1800
|
|
|
1801
|
+
// Before the inline scripts, and through the same barrier: an inline
|
|
1802
|
+
// script's whole reason for existing is often to call into one of these,
|
|
1803
|
+
// so running it first would fail exactly the way this bug did.
|
|
1804
|
+
routedExternalScripts.forEach(function(src){
|
|
1805
|
+
var pending=loadExternalScript(src,docBase);
|
|
1806
|
+
if(pending)extPromises.push(pending);
|
|
1807
|
+
});
|
|
1808
|
+
|
|
1718
1809
|
// \u2500\u2500 Script re-execution \u2500\u2500
|
|
1719
1810
|
// Remove previously injected page scripts
|
|
1720
1811
|
document.querySelectorAll('script[data-stx-page]').forEach(function(s){s.remove()});
|
|
@@ -1960,6 +2051,21 @@ else {
|
|
|
1960
2051
|
// mode where every page is part of the same SPA shell.
|
|
1961
2052
|
document.addEventListener('click',function(e){
|
|
1962
2053
|
if(e.metaKey||e.ctrlKey||e.shiftKey||e.altKey||e.button!==0)return;
|
|
2054
|
+
// A handler on the page has already decided this click is not a
|
|
2055
|
+
// navigation, so the router must not perform one - the same contract the
|
|
2056
|
+
// browser honours for a plain anchor.
|
|
2057
|
+
//
|
|
2058
|
+
// This listener used to run in the CAPTURE phase, which broke that twice
|
|
2059
|
+
// over: it saw the click before the anchor did, so defaultPrevented was
|
|
2060
|
+
// necessarily false by the time it was read here, and the stopPropagation
|
|
2061
|
+
// below then stopped the event ever reaching the element - so a page's own
|
|
2062
|
+
// click handler on an intercepted link never ran AT ALL. Progressive
|
|
2063
|
+
// enhancement on an anchor was impossible; the only way out was to swap it
|
|
2064
|
+
// for a button in script (stacksjs/stacks#2393).
|
|
2065
|
+
//
|
|
2066
|
+
// Bubbling instead puts the page's handlers first, which is where a
|
|
2067
|
+
// decision about the page's own markup belongs.
|
|
2068
|
+
if(e.defaultPrevented)return;
|
|
1963
2069
|
if(!e.target||!e.target.closest)return;
|
|
1964
2070
|
var link=e.target.closest('[data-stx-link]');
|
|
1965
2071
|
// A marked link still has to clear the opt-out set. It used to skip it
|
|
@@ -1977,7 +2083,7 @@ else {
|
|
|
1977
2083
|
e.stopPropagation();
|
|
1978
2084
|
log('[router] navigating to:',href);
|
|
1979
2085
|
navigate(withCurrentLocale(href));
|
|
1980
|
-
}
|
|
2086
|
+
});
|
|
1981
2087
|
|
|
1982
2088
|
// \u2500\u2500 Form submission \u2500\u2500
|
|
1983
2089
|
// A submit IS a navigation, and the router only ever watched clicks. So any
|
package/dist/serve.js
CHANGED
|
@@ -9592,7 +9592,7 @@ function __stxOverlay(errs){
|
|
|
9592
9592
|
pageMiddlewarePatterns.length = 0;
|
|
9593
9593
|
await Promise.all(files.map((f) => detectPageMiddleware(f)));
|
|
9594
9594
|
try {
|
|
9595
|
-
const { Router } = await import("./chunk-
|
|
9595
|
+
const { Router } = await import("./chunk-xg4124d4.js");
|
|
9596
9596
|
const pagesDirs = patterns.map((p) => p.replace(/\/$/, "")).filter(Boolean);
|
|
9597
9597
|
const router = new Router(process19.cwd(), { pagesDirs, stateDir: stateDirName() });
|
|
9598
9598
|
if (!options.quiet)
|
package/package.json
CHANGED