bun-plugin-stx 0.2.152 → 0.2.153
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/dist/{chunk-8nbccnre.js → chunk-3dygdr9g.js} +95 -15
- package/dist/serve.d.ts +2 -0
- package/dist/serve.js +16 -4
- package/package.json +2 -1
|
@@ -621,6 +621,28 @@ function getRouterScript() {
|
|
|
621
621
|
var containerSel=o.container;
|
|
622
622
|
var debug=!!o.debug;
|
|
623
623
|
function log(){if(debug&&typeof console!=='undefined'&&console.log)console.log.apply(console,arguments)}
|
|
624
|
+
|
|
625
|
+
// \u2500\u2500 Build skew (stacksjs/stx#1772) \u2500\u2500
|
|
626
|
+
// The build that rendered THIS document, and therefore the build the runtime
|
|
627
|
+
// executing right now came from. Under bun --watch a save restarts the server,
|
|
628
|
+
// so the next navigation can fetch a fragment produced by a newer build; when
|
|
629
|
+
// the scoped-script or binding format drifted between the two, the old runtime
|
|
630
|
+
// cannot hydrate the new fragment \u2014 literal moustaches, dead bindings, stale
|
|
631
|
+
// canvas. Sporadic, and never on a clean boot.
|
|
632
|
+
//
|
|
633
|
+
// Conservative on purpose: act only when BOTH ids are known. A missing id is
|
|
634
|
+
// "no information", never "mismatch", so statically hosted output (no headers)
|
|
635
|
+
// and older servers behave exactly as before.
|
|
636
|
+
var buildMeta=document.querySelector('meta[name="stx-build"]');
|
|
637
|
+
var loadedBuild=buildMeta?(buildMeta.getAttribute('content')||''):'';
|
|
638
|
+
window.__stxBuild=loadedBuild;
|
|
639
|
+
function isBuildSkew(incoming){
|
|
640
|
+
return !!(loadedBuild&&incoming&&incoming!==loadedBuild);
|
|
641
|
+
}
|
|
642
|
+
function reloadForSkew(url,incoming){
|
|
643
|
+
log('[router] build skew: page is',loadedBuild,'server is',incoming,'\u2014 full navigation');
|
|
644
|
+
location.href=url;
|
|
645
|
+
}
|
|
624
646
|
function runViewTransition(callback){
|
|
625
647
|
if(!o.viewTransitions||!document.startViewTransition)return false;
|
|
626
648
|
try{
|
|
@@ -816,6 +838,27 @@ function getRouterScript() {
|
|
|
816
838
|
// Layout-level scripts (theme, nav setup) execute on initial page load and
|
|
817
839
|
// should NOT re-execute when navigating to another page with the same layout.
|
|
818
840
|
var executedScriptHashes={};
|
|
841
|
+
|
|
842
|
+
// \u2500\u2500 Re-execution contract (stacksjs/stx#1773) \u2500\u2500
|
|
843
|
+
// Whether a generated script may run again after an SPA swap used to be
|
|
844
|
+
// SNIFFED from its source: does it start with '(' or ';', or mention
|
|
845
|
+
// window.stx.mount. That is a property of the emitted shape, not of intent,
|
|
846
|
+
// so every change to how scripts are wrapped was one edit away from silently
|
|
847
|
+
// reclassifying them \u2014 and the symptom of getting it wrong is a component
|
|
848
|
+
// that renders completely dead (literal moustaches, stuck :show) only on a
|
|
849
|
+
// REVISIT, because nav-away already disposed its scope.
|
|
850
|
+
//
|
|
851
|
+
// Emitters now declare it: data-stx-run="always" on self-contained scope
|
|
852
|
+
// IIFEs and mount() wrappers, which own their scope and whose registration
|
|
853
|
+
// _cleanupContainer removed on the way out. Anything unstamped falls back to
|
|
854
|
+
// the old sniff, so a page rendered by an older server behaves exactly as
|
|
855
|
+
// before.
|
|
856
|
+
function runsAlways(declared, code){
|
|
857
|
+
if(declared==='always')return true;
|
|
858
|
+
if(declared==='once')return false;
|
|
859
|
+
var head=code.trimStart();
|
|
860
|
+
return head.charAt(0)==='('||head.charAt(0)===';'||code.indexOf('window.stx.mount')>-1;
|
|
861
|
+
}
|
|
819
862
|
function hashScript(code){
|
|
820
863
|
var h=0;for(var i=0;i<code.length;i++){h=((h<<5)-h)+code.charCodeAt(i);h|=0}
|
|
821
864
|
return h;
|
|
@@ -964,6 +1007,10 @@ else {
|
|
|
964
1007
|
var wantsFragment=shouldUseFragmentResponse();
|
|
965
1008
|
return fetch(url,{headers:wantsFragment?{'X-STX-Router':'true','Accept':'text/html'}:{'Accept':'text/html'}}).then(function(r){
|
|
966
1009
|
if(!r.ok)throw new Error(r.status);
|
|
1010
|
+
// Before anything is parsed or swapped: a fragment from a different
|
|
1011
|
+
// build must not be handed to this page's runtime (#1772).
|
|
1012
|
+
var incomingBuild=r.headers.get('X-STX-Build')||'';
|
|
1013
|
+
if(isBuildSkew(incomingBuild)){reloadForSkew(url,incomingBuild);return null}
|
|
967
1014
|
var isFragment=wantsFragment&&r.headers.get('X-STX-Fragment')==='true';
|
|
968
1015
|
var newLayout=r.headers.get('X-STX-Layout')||'';
|
|
969
1016
|
var newGroup=r.headers.get('X-STX-Layout-Group')||'';
|
|
@@ -1033,12 +1080,15 @@ else {
|
|
|
1033
1080
|
if(code&&code.trim()&&!isSignalsRuntimeScript({hasAttribute:function(name){return name==='data-stx-runtime'&&attrs.indexOf('data-stx-runtime')!==-1}},code)){
|
|
1034
1081
|
var slot='fragment-'+(++fragScriptId);
|
|
1035
1082
|
var scoped=/(?:^|\\s)data-stx-scoped(?:\\s|=|$)/i.test(attrs);
|
|
1036
|
-
|
|
1083
|
+
var runDecl=(attrs.match(/data-stx-run\\s*=\\s*["']?(always|once)["']?/i)||[])[1];
|
|
1084
|
+
fragScripts.push({text:code,slot:slot,setupName:generatedSetupName(code),scoped:scoped,run:runDecl?runDecl.toLowerCase():''});
|
|
1037
1085
|
// Retain scoped setup code in its inert placeholder. A placeholder
|
|
1038
1086
|
// inside template.content is unreachable through document, so the
|
|
1039
1087
|
// repeated component runtime must execute it for each clone.
|
|
1040
1088
|
return '<scr'+'ipt type="application/stx-pending" data-stx-route-script="'+slot+'"'
|
|
1041
|
-
+(scoped?' data-stx-scoped':'')
|
|
1089
|
+
+(scoped?' data-stx-scoped':'')
|
|
1090
|
+
+(runDecl?' data-stx-run="'+runDecl.toLowerCase()+'"':'')
|
|
1091
|
+
+'>'+code+'<\\/scr'+'ipt>';
|
|
1042
1092
|
}
|
|
1043
1093
|
return '';
|
|
1044
1094
|
});
|
|
@@ -1141,17 +1191,14 @@ else {
|
|
|
1141
1191
|
// re-execute because each page has its own setup.
|
|
1142
1192
|
var h=hashScript(code);
|
|
1143
1193
|
var isSetup=code.indexOf('__stx_setup_')!==-1;
|
|
1144
|
-
//
|
|
1145
|
-
//
|
|
1146
|
-
//
|
|
1147
|
-
//
|
|
1148
|
-
//
|
|
1149
|
-
//
|
|
1150
|
-
//
|
|
1151
|
-
|
|
1152
|
-
// and all :if branches stay visible at once.
|
|
1153
|
-
// Mirrors the full-doc swap path's runAlways escape below.
|
|
1154
|
-
var isAlreadyScoped=code.trimStart().charAt(0)==='('||code.trimStart().charAt(0)===';'||code.indexOf('window.stx.mount')>-1;
|
|
1194
|
+
// Self-contained scope scripts must re-execute on every navigation:
|
|
1195
|
+
// they own their scope, won't collide with prior runs, and
|
|
1196
|
+
// _cleanupContainer deleted their entry from window.stx._scopes on
|
|
1197
|
+
// the way out. Skipping one leaves the page leaf with no scope
|
|
1198
|
+
// registered \u2014 bindIf/@event never re-bind and every :if branch
|
|
1199
|
+
// stays visible at once. Declared by the emitter via data-stx-run,
|
|
1200
|
+
// sniffed only as a fallback (#1773).
|
|
1201
|
+
var isAlreadyScoped=runsAlways(entry.run,code);
|
|
1155
1202
|
if(!isSetup&&!isAlreadyScoped&&executedScriptHashes[h]){
|
|
1156
1203
|
log('[router] skipping already-executed script (hash dedup)');
|
|
1157
1204
|
return;
|
|
@@ -1190,6 +1237,11 @@ else {
|
|
|
1190
1237
|
// Full document mode: parse with DOMParser and extract container content
|
|
1191
1238
|
var parser=new DOMParser();
|
|
1192
1239
|
var doc=parser.parseFromString(html,'text/html');
|
|
1240
|
+
// Full documents carry the build id as a meta rather than a header, and
|
|
1241
|
+
// this path also serves cached HTML, so check here too (#1772).
|
|
1242
|
+
var docBuildMeta=doc.querySelector('meta[name="stx-build"]');
|
|
1243
|
+
var docBuild=docBuildMeta?(docBuildMeta.getAttribute('content')||''):'';
|
|
1244
|
+
if(isBuildSkew(docBuild)){reloadForSkew(url,docBuild);return Promise.resolve(false)}
|
|
1193
1245
|
var newContent=doc.querySelector(containerSel)||doc.querySelector('[data-stx-content]')||doc.querySelector('main');
|
|
1194
1246
|
if(!newContent){location.href=url;return Promise.resolve(false)}
|
|
1195
1247
|
|
|
@@ -1303,7 +1355,7 @@ else {
|
|
|
1303
1355
|
var slot='document-'+(++routedBodyScriptId);
|
|
1304
1356
|
var setupName=generatedSetupName(text);
|
|
1305
1357
|
if(setupName)incomingSetupName=setupName;
|
|
1306
|
-
routedBodyScripts.push({text:text,runAlways:true,slot:slot,setupName:setupName});
|
|
1358
|
+
routedBodyScripts.push({text:text,runAlways:true,slot:slot,setupName:setupName,run:s.getAttribute('data-stx-run')||''});
|
|
1307
1359
|
s.textContent='';
|
|
1308
1360
|
s.setAttribute('type','application/stx-pending');
|
|
1309
1361
|
s.setAttribute('data-stx-route-script',slot);
|
|
@@ -1431,6 +1483,34 @@ else {
|
|
|
1431
1483
|
document.documentElement.lang=doc.documentElement.lang;
|
|
1432
1484
|
}
|
|
1433
1485
|
|
|
1486
|
+
// \u2500\u2500 Reconcile <html> attributes (stacksjs/stx#1798) \u2500\u2500
|
|
1487
|
+
// A layout that scopes its design tokens to the root element
|
|
1488
|
+
// (html.marketing { --bg: \u2026 }) needs that class to LEAVE when you
|
|
1489
|
+
// navigate to a layout that doesn't want it \u2014 otherwise both token sets
|
|
1490
|
+
// match at once and the second layout paints with the first one's
|
|
1491
|
+
// palette. Head stylesheets are additive across a swap, so the class is
|
|
1492
|
+
// the only thing disambiguating them.
|
|
1493
|
+
//
|
|
1494
|
+
// Only what stx wrote is touched, per the markers emitted by
|
|
1495
|
+
// document-shell.ts. Diffing the whole element against the incoming
|
|
1496
|
+
// document would strip the color-mode boot's dark class and
|
|
1497
|
+
// data-reduced-motion, which exist only on the live page.
|
|
1498
|
+
if(doc.documentElement){
|
|
1499
|
+
var curRoot=document.documentElement,incRoot=doc.documentElement;
|
|
1500
|
+
var tokens=function(el,attr){var v=el.getAttribute(attr);return v?v.split(/\\s+/).filter(Boolean):[]};
|
|
1501
|
+
var prevCls=tokens(curRoot,'data-stx-html-class'),nextCls=tokens(incRoot,'data-stx-html-class');
|
|
1502
|
+
prevCls.forEach(function(c){if(nextCls.indexOf(c)===-1)curRoot.classList.remove(c)});
|
|
1503
|
+
nextCls.forEach(function(c){curRoot.classList.add(c)});
|
|
1504
|
+
if(nextCls.length)curRoot.setAttribute('data-stx-html-class',nextCls.join(' '));
|
|
1505
|
+
else curRoot.removeAttribute('data-stx-html-class');
|
|
1506
|
+
|
|
1507
|
+
var prevNames=tokens(curRoot,'data-stx-html-attrs'),nextNames=tokens(incRoot,'data-stx-html-attrs');
|
|
1508
|
+
prevNames.forEach(function(n){if(nextNames.indexOf(n)===-1)curRoot.removeAttribute(n)});
|
|
1509
|
+
nextNames.forEach(function(n){var v=incRoot.getAttribute(n);if(v!==null)curRoot.setAttribute(n,v)});
|
|
1510
|
+
if(nextNames.length)curRoot.setAttribute('data-stx-html-attrs',nextNames.join(' '));
|
|
1511
|
+
else curRoot.removeAttribute('data-stx-html-attrs');
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1434
1514
|
window.dispatchEvent(new CustomEvent('stx:navigate',{detail:{url:url}}));
|
|
1435
1515
|
|
|
1436
1516
|
// Execute page scripts FIRST \u2014 they define setup functions and set _latestSetup
|
|
@@ -1460,7 +1540,7 @@ else {
|
|
|
1460
1540
|
// Skip the wrap for module scripts \u2014 they get their own scope from
|
|
1461
1541
|
// ESM, and top-level 'import' is illegal inside a block, which
|
|
1462
1542
|
// would throw SyntaxError before the script ever runs.
|
|
1463
|
-
var alreadyScoped=
|
|
1543
|
+
var alreadyScoped=runsAlways(typeof entry==='string'?'':entry.run,text);
|
|
1464
1544
|
ns.textContent=(hasImport||alreadyScoped)?text:'{'+text+'}';
|
|
1465
1545
|
ns.setAttribute('data-stx-page','');
|
|
1466
1546
|
var placeholder=entry.slot?document.querySelector('script[data-stx-route-script="'+entry.slot+'"]'):null;
|
package/dist/serve.d.ts
CHANGED
|
@@ -65,6 +65,7 @@ export declare function render404Page(opts: {
|
|
|
65
65
|
routes?: string[]
|
|
66
66
|
isProduction: boolean
|
|
67
67
|
}): string;
|
|
68
|
+
export declare function resolveServeOpenUrl(port: number, openPath?: string): string;
|
|
68
69
|
export declare function isRenderableCacheCandidate(html: string): boolean;
|
|
69
70
|
export declare function bundleBrowserAsset(entrypoint: string): Promise<Response>;
|
|
70
71
|
/**
|
|
@@ -112,6 +113,7 @@ export declare interface ServeRequestContext {
|
|
|
112
113
|
export declare interface ServeOptions {
|
|
113
114
|
patterns: string[]
|
|
114
115
|
port?: number
|
|
116
|
+
openPath?: string
|
|
115
117
|
componentsDir?: string
|
|
116
118
|
layoutsDir?: string
|
|
117
119
|
partialsDir?: string
|
package/dist/serve.js
CHANGED
|
@@ -8718,7 +8718,7 @@ var defaultConfigDir4 = /* @__PURE__ */ resolve12(process20.cwd(), "config");
|
|
|
8718
8718
|
var defaultGeneratedDir4 = /* @__PURE__ */ resolve12(process20.cwd(), "src/generated");
|
|
8719
8719
|
|
|
8720
8720
|
// src/serve.ts
|
|
8721
|
-
import { extractPageResponseStatus, stateDir, stateDirName } from "@stacksjs/stx";
|
|
8721
|
+
import { BUILD_ID_HEADER, extractPageResponseStatus, getBuildId, stateDir, stateDirName } from "@stacksjs/stx";
|
|
8722
8722
|
var defaultStxModule = import("@stacksjs/stx");
|
|
8723
8723
|
var REACTIVE_TEMPLATE_DIRECTIVE_RE = /@for|:for|@if|:if|@else|:else|x-for|x-if|x-else/;
|
|
8724
8724
|
function extractServerScriptsFromTemplate(content) {
|
|
@@ -8878,6 +8878,15 @@ function render404Page(opts) {
|
|
|
8878
8878
|
</body>
|
|
8879
8879
|
</html>`;
|
|
8880
8880
|
}
|
|
8881
|
+
function resolveServeOpenUrl(port, openPath = "/") {
|
|
8882
|
+
const baseUrl = `http://localhost:${port}`;
|
|
8883
|
+
try {
|
|
8884
|
+
const url = new URL(openPath || "/", `${baseUrl}/`);
|
|
8885
|
+
return url.origin === baseUrl ? url.toString() : `${baseUrl}/`;
|
|
8886
|
+
} catch {
|
|
8887
|
+
return `${baseUrl}/`;
|
|
8888
|
+
}
|
|
8889
|
+
}
|
|
8881
8890
|
function isRenderableCacheCandidate(html) {
|
|
8882
8891
|
return !html.includes("<!-- Template Processing failed:") && !html.includes("<!-- stx rendering error -->");
|
|
8883
8892
|
}
|
|
@@ -9362,7 +9371,7 @@ After installing: bun add bun-plugin-stx`);
|
|
|
9362
9371
|
pageMiddlewarePatterns.length = 0;
|
|
9363
9372
|
await Promise.all(files.map((f) => detectPageMiddleware(f)));
|
|
9364
9373
|
try {
|
|
9365
|
-
const { Router } = await import("./chunk-
|
|
9374
|
+
const { Router } = await import("./chunk-3dygdr9g.js");
|
|
9366
9375
|
const pagesDirs = patterns.map((p) => p.replace(/\/$/, "")).filter(Boolean);
|
|
9367
9376
|
const router = new Router(process19.cwd(), { pagesDirs, stateDir: stateDirName() });
|
|
9368
9377
|
if (!options.quiet)
|
|
@@ -10322,6 +10331,7 @@ ${trailingPageScripts.join(`
|
|
|
10322
10331
|
"X-STX-Layout": pageLayout,
|
|
10323
10332
|
"X-STX-Layout-Group": pageLayoutGroup,
|
|
10324
10333
|
...pageTitle && { "X-STX-Title": encodeURIComponent(pageTitle) },
|
|
10334
|
+
[BUILD_ID_HEADER]: getBuildId(),
|
|
10325
10335
|
...containerAttrs && { "X-STX-Container-Attrs": encodeURIComponent(containerAttrs) },
|
|
10326
10336
|
"Cache-Control": "no-store",
|
|
10327
10337
|
...corsHeaders
|
|
@@ -10538,10 +10548,11 @@ ${trailingPageScripts.join(`
|
|
|
10538
10548
|
const elapsed = (performance.now() - startTime).toFixed(0);
|
|
10539
10549
|
const routeCount = sourceFiles?.length || 0;
|
|
10540
10550
|
const patternsStr = patterns.join(", ");
|
|
10551
|
+
const openUrl = resolveServeOpenUrl(actualPort, options.openPath);
|
|
10541
10552
|
console.log();
|
|
10542
10553
|
console.log(` \x1B[36m\x1B[1mstx\x1B[0m`);
|
|
10543
10554
|
console.log();
|
|
10544
|
-
console.log(` \x1B[32m\u279C\x1B[0m \x1B[1mLocal\x1B[0m: \x1B[
|
|
10555
|
+
console.log(` \x1B[32m\u279C\x1B[0m \x1B[1mLocal\x1B[0m: \x1B[36m${openUrl}\x1B[0m`);
|
|
10545
10556
|
console.log(` \x1B[32m\u279C\x1B[0m \x1B[1mRoutes\x1B[0m: \x1B[2m${routeCount} files from ${patternsStr}\x1B[0m`);
|
|
10546
10557
|
console.log();
|
|
10547
10558
|
console.log(` \x1B[2mready in ${elapsed}ms\x1B[0m`);
|
|
@@ -10557,7 +10568,7 @@ ${trailingPageScripts.join(`
|
|
|
10557
10568
|
const cmd = line.trim().toLowerCase();
|
|
10558
10569
|
if (cmd === "o") {
|
|
10559
10570
|
const openCmd = process19.platform === "darwin" ? "open" : process19.platform === "win32" ? "start" : "xdg-open";
|
|
10560
|
-
Bun.spawn([openCmd,
|
|
10571
|
+
Bun.spawn([openCmd, openUrl]);
|
|
10561
10572
|
} else if (cmd === "q") {
|
|
10562
10573
|
process19.exit(0);
|
|
10563
10574
|
}
|
|
@@ -10582,6 +10593,7 @@ if (import.meta.main) {
|
|
|
10582
10593
|
export {
|
|
10583
10594
|
staticCacheControl,
|
|
10584
10595
|
serve,
|
|
10596
|
+
resolveServeOpenUrl,
|
|
10585
10597
|
render404Page,
|
|
10586
10598
|
isRenderableCacheCandidate,
|
|
10587
10599
|
isProductionServe,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bun-plugin-stx",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.153",
|
|
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",
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"import": "./dist/preload.js"
|
|
40
40
|
},
|
|
41
41
|
"./serve": {
|
|
42
|
+
"types": "./dist/serve.d.ts",
|
|
42
43
|
"bun": "./dist/serve.js",
|
|
43
44
|
"import": "./dist/serve.js"
|
|
44
45
|
},
|