@vertz/ui-server 0.2.20 → 0.2.22
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/bun-dev-server.d.ts +9 -1
- package/dist/bun-dev-server.js +337 -33
- package/dist/bun-plugin/index.js +1 -0
- package/dist/dom-shim/index.d.ts +26 -1
- package/dist/dom-shim/index.js +1 -1
- package/dist/index.d.ts +12 -1
- package/dist/index.js +2 -2
- package/dist/jsx-runtime/index.js +7 -2
- package/dist/shared/{chunk-9jjdzz8c.js → chunk-1363r1qw.js} +142 -2
- package/dist/shared/{chunk-c5ee9yf1.js → chunk-qzdhe6bn.js} +53 -5
- package/dist/ssr/index.d.ts +25 -4
- package/dist/ssr/index.js +34 -3
- package/package.json +6 -6
package/dist/bun-dev-server.d.ts
CHANGED
|
@@ -83,6 +83,7 @@ interface ErrorDetail {
|
|
|
83
83
|
stack?: string;
|
|
84
84
|
}
|
|
85
85
|
type ErrorCategory = "build" | "resolve" | "runtime" | "ssr";
|
|
86
|
+
declare function isStaleGraphError(message: string): boolean;
|
|
86
87
|
/** A resolved stack frame for terminal logging. */
|
|
87
88
|
interface TerminalStackFrame {
|
|
88
89
|
functionName: string | null;
|
|
@@ -108,6 +109,13 @@ declare function createRuntimeErrorDeduplicator(): {
|
|
|
108
109
|
interface BunDevServer {
|
|
109
110
|
start(): Promise<void>;
|
|
110
111
|
stop(): Promise<void>;
|
|
112
|
+
/**
|
|
113
|
+
* Soft-restart the dev server: stops Bun.serve(), clears all caches,
|
|
114
|
+
* creates a fresh Bun.serve() with a clean HMR module graph.
|
|
115
|
+
* Broadcasts { type: 'restarting' } to clients before stopping.
|
|
116
|
+
* Skips one-time setup (plugin registration, console.error patching).
|
|
117
|
+
*/
|
|
118
|
+
restart(): Promise<void>;
|
|
111
119
|
/** Broadcast an error to all connected WebSocket clients. */
|
|
112
120
|
broadcastError(category: ErrorCategory, errors: ErrorDetail[]): void;
|
|
113
121
|
/** Clear current error and notify all connected WebSocket clients.
|
|
@@ -185,4 +193,4 @@ declare function clearSSRRequireCache(): number;
|
|
|
185
193
|
* SSR is always on. HMR always works. No mode toggle needed.
|
|
186
194
|
*/
|
|
187
195
|
declare function createBunDevServer(options: BunDevServerOptions): BunDevServer;
|
|
188
|
-
export { parseHMRAssets, generateSSRPageHtml, formatTerminalRuntimeError, detectFaviconTag, createRuntimeErrorDeduplicator, createFetchInterceptor, createBunDevServer, clearSSRRequireCache, buildScriptTag, SSRPageHtmlOptions, HMRAssets, FetchInterceptorOptions, ErrorDetail, ErrorCategory, BunDevServerOptions, BunDevServer };
|
|
196
|
+
export { parseHMRAssets, isStaleGraphError, generateSSRPageHtml, formatTerminalRuntimeError, detectFaviconTag, createRuntimeErrorDeduplicator, createFetchInterceptor, createBunDevServer, clearSSRRequireCache, buildScriptTag, SSRPageHtmlOptions, HMRAssets, FetchInterceptorOptions, ErrorDetail, ErrorCategory, BunDevServerOptions, BunDevServer };
|
package/dist/bun-dev-server.js
CHANGED
|
@@ -611,6 +611,9 @@ class SSRComment extends SSRNode {
|
|
|
611
611
|
}
|
|
612
612
|
}
|
|
613
613
|
|
|
614
|
+
// src/dom-shim/ssr-element.ts
|
|
615
|
+
import { __styleStr } from "@vertz/ui/internals";
|
|
616
|
+
|
|
614
617
|
// src/types.ts
|
|
615
618
|
function rawHtml(html) {
|
|
616
619
|
return { __raw: true, html };
|
|
@@ -654,6 +657,52 @@ class SSRDocumentFragment extends SSRNode {
|
|
|
654
657
|
}
|
|
655
658
|
|
|
656
659
|
// src/dom-shim/ssr-element.ts
|
|
660
|
+
function camelToKebab(str) {
|
|
661
|
+
return str.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
|
|
662
|
+
}
|
|
663
|
+
function kebabToCamel(str) {
|
|
664
|
+
return str.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
665
|
+
}
|
|
666
|
+
function createDatasetProxy(element) {
|
|
667
|
+
return new Proxy({}, {
|
|
668
|
+
set(_target, prop, value) {
|
|
669
|
+
if (typeof prop === "string") {
|
|
670
|
+
element.setAttribute(`data-${camelToKebab(prop)}`, String(value));
|
|
671
|
+
}
|
|
672
|
+
return true;
|
|
673
|
+
},
|
|
674
|
+
get(_target, prop) {
|
|
675
|
+
if (typeof prop === "string") {
|
|
676
|
+
return element.getAttribute(`data-${camelToKebab(prop)}`) ?? undefined;
|
|
677
|
+
}
|
|
678
|
+
return;
|
|
679
|
+
},
|
|
680
|
+
has(_target, prop) {
|
|
681
|
+
if (typeof prop === "string") {
|
|
682
|
+
return element.getAttribute(`data-${camelToKebab(prop)}`) !== null;
|
|
683
|
+
}
|
|
684
|
+
return false;
|
|
685
|
+
},
|
|
686
|
+
deleteProperty(_target, prop) {
|
|
687
|
+
if (typeof prop === "string") {
|
|
688
|
+
element.removeAttribute(`data-${camelToKebab(prop)}`);
|
|
689
|
+
}
|
|
690
|
+
return true;
|
|
691
|
+
},
|
|
692
|
+
ownKeys() {
|
|
693
|
+
return Object.keys(element.attrs).filter((k) => k.startsWith("data-")).map((k) => kebabToCamel(k.slice(5)));
|
|
694
|
+
},
|
|
695
|
+
getOwnPropertyDescriptor(_target, prop) {
|
|
696
|
+
if (typeof prop === "string") {
|
|
697
|
+
const val = element.getAttribute(`data-${camelToKebab(prop)}`);
|
|
698
|
+
if (val !== null) {
|
|
699
|
+
return { configurable: true, enumerable: true, value: val };
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
});
|
|
705
|
+
}
|
|
657
706
|
function createStyleProxy(element) {
|
|
658
707
|
const styles = {};
|
|
659
708
|
return new Proxy(styles, {
|
|
@@ -685,16 +734,27 @@ class SSRElement extends SSRNode {
|
|
|
685
734
|
_textContent = null;
|
|
686
735
|
_innerHTML = null;
|
|
687
736
|
style;
|
|
737
|
+
dataset;
|
|
688
738
|
constructor(tag) {
|
|
689
739
|
super();
|
|
690
740
|
this.tag = tag;
|
|
691
741
|
this.style = createStyleProxy(this);
|
|
742
|
+
this.dataset = createDatasetProxy(this);
|
|
692
743
|
}
|
|
693
744
|
setAttribute(name, value) {
|
|
694
|
-
if (name === "
|
|
745
|
+
if (name === "style" && typeof value === "object" && value !== null) {
|
|
746
|
+
this.attrs.style = __styleStr(value);
|
|
747
|
+
for (const [k, v] of Object.entries(value)) {
|
|
748
|
+
if (v != null)
|
|
749
|
+
this.style[k] = String(v);
|
|
750
|
+
}
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
const attrName = name === "className" ? "class" : name;
|
|
754
|
+
if (attrName === "class") {
|
|
695
755
|
this._classList = new Set(value.split(/\s+/).filter(Boolean));
|
|
696
756
|
}
|
|
697
|
-
this.attrs[
|
|
757
|
+
this.attrs[attrName] = value;
|
|
698
758
|
}
|
|
699
759
|
getAttribute(name) {
|
|
700
760
|
return this.attrs[name] ?? null;
|
|
@@ -831,6 +891,86 @@ class SSRElement extends SSRNode {
|
|
|
831
891
|
get innerHTML() {
|
|
832
892
|
return this._innerHTML ?? "";
|
|
833
893
|
}
|
|
894
|
+
get placeholder() {
|
|
895
|
+
return this.attrs.placeholder ?? "";
|
|
896
|
+
}
|
|
897
|
+
set placeholder(value) {
|
|
898
|
+
this.attrs.placeholder = value;
|
|
899
|
+
}
|
|
900
|
+
get type() {
|
|
901
|
+
return this.attrs.type ?? "";
|
|
902
|
+
}
|
|
903
|
+
set type(value) {
|
|
904
|
+
this.attrs.type = value;
|
|
905
|
+
}
|
|
906
|
+
get name() {
|
|
907
|
+
return this.attrs.name ?? "";
|
|
908
|
+
}
|
|
909
|
+
set name(value) {
|
|
910
|
+
this.attrs.name = value;
|
|
911
|
+
}
|
|
912
|
+
get value() {
|
|
913
|
+
return this.attrs.value ?? "";
|
|
914
|
+
}
|
|
915
|
+
set value(value) {
|
|
916
|
+
this.attrs.value = value;
|
|
917
|
+
}
|
|
918
|
+
get src() {
|
|
919
|
+
return this.attrs.src ?? "";
|
|
920
|
+
}
|
|
921
|
+
set src(value) {
|
|
922
|
+
this.attrs.src = value;
|
|
923
|
+
}
|
|
924
|
+
get alt() {
|
|
925
|
+
return this.attrs.alt ?? "";
|
|
926
|
+
}
|
|
927
|
+
set alt(value) {
|
|
928
|
+
this.attrs.alt = value;
|
|
929
|
+
}
|
|
930
|
+
get htmlFor() {
|
|
931
|
+
return this.attrs.for ?? "";
|
|
932
|
+
}
|
|
933
|
+
set htmlFor(value) {
|
|
934
|
+
this.attrs.for = value;
|
|
935
|
+
}
|
|
936
|
+
get disabled() {
|
|
937
|
+
return "disabled" in this.attrs;
|
|
938
|
+
}
|
|
939
|
+
set disabled(value) {
|
|
940
|
+
if (value) {
|
|
941
|
+
this.attrs.disabled = "";
|
|
942
|
+
} else {
|
|
943
|
+
delete this.attrs.disabled;
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
get checked() {
|
|
947
|
+
return "checked" in this.attrs;
|
|
948
|
+
}
|
|
949
|
+
set checked(value) {
|
|
950
|
+
if (value) {
|
|
951
|
+
this.attrs.checked = "";
|
|
952
|
+
} else {
|
|
953
|
+
delete this.attrs.checked;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
get rows() {
|
|
957
|
+
return Number(this.attrs.rows) || 0;
|
|
958
|
+
}
|
|
959
|
+
set rows(value) {
|
|
960
|
+
this.attrs.rows = String(value);
|
|
961
|
+
}
|
|
962
|
+
get scope() {
|
|
963
|
+
return this.attrs.scope ?? "";
|
|
964
|
+
}
|
|
965
|
+
set scope(value) {
|
|
966
|
+
this.attrs.scope = value;
|
|
967
|
+
}
|
|
968
|
+
get href() {
|
|
969
|
+
return this.attrs.href ?? "";
|
|
970
|
+
}
|
|
971
|
+
set href(value) {
|
|
972
|
+
this.attrs.href = value;
|
|
973
|
+
}
|
|
834
974
|
addEventListener(_event, _handler) {}
|
|
835
975
|
removeEventListener(_event, _handler) {}
|
|
836
976
|
toVNode() {
|
|
@@ -1347,7 +1487,8 @@ async function ssrRenderToString(module, url, options) {
|
|
|
1347
1487
|
css,
|
|
1348
1488
|
ssrData,
|
|
1349
1489
|
headTags: themePreloadTags,
|
|
1350
|
-
discoveredRoutes: ctx.discoveredRoutes
|
|
1490
|
+
discoveredRoutes: ctx.discoveredRoutes,
|
|
1491
|
+
matchedRoutePatterns: ctx.matchedRoutePatterns
|
|
1351
1492
|
};
|
|
1352
1493
|
} finally {
|
|
1353
1494
|
clearGlobalSSRTimeout();
|
|
@@ -1468,6 +1609,14 @@ function detectFaviconTag(projectRoot) {
|
|
|
1468
1609
|
const faviconPath = resolve(projectRoot, "public", "favicon.svg");
|
|
1469
1610
|
return existsSync(faviconPath) ? '<link rel="icon" type="image/svg+xml" href="/favicon.svg">' : "";
|
|
1470
1611
|
}
|
|
1612
|
+
var STALE_GRAPH_PATTERNS = [
|
|
1613
|
+
/Export named ['"].*['"] not found in module/i,
|
|
1614
|
+
/No matching export in ['"].*['"] for import/i,
|
|
1615
|
+
/does not provide an export named/i
|
|
1616
|
+
];
|
|
1617
|
+
function isStaleGraphError(message) {
|
|
1618
|
+
return STALE_GRAPH_PATTERNS.some((pattern) => pattern.test(message));
|
|
1619
|
+
}
|
|
1471
1620
|
var MAX_TERMINAL_STACK_FRAMES = 5;
|
|
1472
1621
|
function formatTerminalRuntimeError(errors, parsedStack) {
|
|
1473
1622
|
const primary = errors[0];
|
|
@@ -1562,6 +1711,24 @@ function buildErrorChannelScript(editor) {
|
|
|
1562
1711
|
"V._src=null;",
|
|
1563
1712
|
"V._hadClientError=false;",
|
|
1564
1713
|
"V._needsReload=false;",
|
|
1714
|
+
"V._restarting=false;",
|
|
1715
|
+
`V.isStaleGraph=function(m){return/Export named ['"].*['"] not found in module/i.test(m)||/No matching export in ['"].*['"] for import/i.test(m)||/does not provide an export named/i.test(m)};`,
|
|
1716
|
+
"V._canAutoRestart=function(){",
|
|
1717
|
+
"var raw=sessionStorage.getItem('__vertz_auto_restart');",
|
|
1718
|
+
"var ts;try{ts=raw?JSON.parse(raw):[]}catch(e){ts=[]}",
|
|
1719
|
+
"var now=Date.now();",
|
|
1720
|
+
"ts=ts.filter(function(t){return now-t<10000});",
|
|
1721
|
+
"return ts.length<3};",
|
|
1722
|
+
"V._autoRestart=function(){",
|
|
1723
|
+
"if(V._restarting)return;",
|
|
1724
|
+
"var raw=sessionStorage.getItem('__vertz_auto_restart');",
|
|
1725
|
+
"var ts;try{ts=raw?JSON.parse(raw):[]}catch(e){ts=[]}",
|
|
1726
|
+
"var now=Date.now();",
|
|
1727
|
+
"ts=ts.filter(function(t){return now-t<10000});",
|
|
1728
|
+
"if(ts.length>=3)return;",
|
|
1729
|
+
"ts.push(now);",
|
|
1730
|
+
"sessionStorage.setItem('__vertz_auto_restart',JSON.stringify(ts));",
|
|
1731
|
+
"if(V._ws&&V._ws.readyState===1){V._ws.send(JSON.stringify({type:'restart'}))}};",
|
|
1565
1732
|
'var rts=sessionStorage.getItem("__vertz_recovering");',
|
|
1566
1733
|
"V._recovering=rts&&(Date.now()-Number(rts)<10000);",
|
|
1567
1734
|
'if(V._recovering)sessionStorage.removeItem("__vertz_recovering");',
|
|
@@ -1602,7 +1769,7 @@ function buildErrorChannelScript(editor) {
|
|
|
1602
1769
|
`var link=href?'<a href="'+href+'" style="color:var(--ve-link);text-decoration:underline;text-underline-offset:2px">'+loc+'</a>':'<span>'+loc+'</span>';`,
|
|
1603
1770
|
`return'<div style="font-size:11px;color:'+color+';margin:1px 0;font-family:ui-monospace,monospace">'+V.esc(name)+' '+link+'</div>'};`,
|
|
1604
1771
|
"V.removeOverlay=function(){V._src=null;var e=document.getElementById('__vertz_error');if(e)e.remove();" + "var d=document.getElementById('__vertz_error_data');if(d)d.remove()};",
|
|
1605
|
-
"V.showOverlay=function(t,body,payload,src){",
|
|
1772
|
+
"V.showOverlay=function(t,body,payload,src,restartable){",
|
|
1606
1773
|
"V.removeOverlay();",
|
|
1607
1774
|
"V._src=src||'ws';",
|
|
1608
1775
|
"var d=document,c=d.createElement('div');",
|
|
@@ -1621,12 +1788,16 @@ function buildErrorChannelScript(editor) {
|
|
|
1621
1788
|
"--ve-error:hsl(0 72% 65%);--ve-link:hsl(217 91% 70%);--ve-border:hsl(0 0% 18%);",
|
|
1622
1789
|
"--ve-code:hsl(36 80% 65%);--ve-code-bg:hsl(0 0% 11%);--ve-btn:hsl(0 0% 93%);--ve-btn-fg:hsl(0 0% 7%)}}';",
|
|
1623
1790
|
"d.head.appendChild(st);",
|
|
1791
|
+
`var btns=restartable?'<div style="display:flex;gap:6px">'`,
|
|
1792
|
+
`+'<button id="__vertz_restart" style="background:var(--ve-btn);color:var(--ve-btn-fg);border:none;border-radius:6px;padding:4px 12px;font-size:12px;cursor:pointer;font-weight:500">Restart Server</button>'`,
|
|
1793
|
+
`+'<button id="__vertz_retry" style="background:transparent;color:var(--ve-muted);border:1px solid var(--ve-border);border-radius:6px;padding:4px 12px;font-size:12px;cursor:pointer">Retry</button></div>'`,
|
|
1794
|
+
`:'<button id="__vertz_retry" style="background:var(--ve-btn);color:var(--ve-btn-fg);border:none;border-radius:6px;padding:4px 12px;font-size:12px;cursor:pointer;font-weight:500">Retry</button>';`,
|
|
1624
1795
|
`c.innerHTML='<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:10px">'`,
|
|
1625
1796
|
`+'<span style="font-size:13px;font-weight:600;color:var(--ve-error)">'+V.esc(t)+'</span>'`,
|
|
1626
|
-
|
|
1627
|
-
"+'</div>'+body;",
|
|
1797
|
+
"+btns+'</div>'+body;",
|
|
1628
1798
|
"(d.body||d.documentElement).appendChild(c);",
|
|
1629
1799
|
"d.getElementById('__vertz_retry').onclick=function(){location.reload()};",
|
|
1800
|
+
"var rb=d.getElementById('__vertz_restart');if(rb){rb.onclick=function(){if(V._ws&&V._ws.readyState===1){V._ws.send(JSON.stringify({type:'restart'}))}}};",
|
|
1630
1801
|
"if(payload){var s=d.createElement('script');s.type='application/json';s.id='__vertz_error_data';s.textContent=JSON.stringify(payload);(d.body||d.documentElement).appendChild(s)}};",
|
|
1631
1802
|
"var delay=1000,maxDelay=30000;",
|
|
1632
1803
|
"function connect(){",
|
|
@@ -1637,16 +1808,30 @@ function buildErrorChannelScript(editor) {
|
|
|
1637
1808
|
"try{var m=JSON.parse(e.data);",
|
|
1638
1809
|
"if(m.type==='error'){",
|
|
1639
1810
|
"if(V._recovering)return;",
|
|
1640
|
-
"
|
|
1811
|
+
"var sg=m.errors&&m.errors.some(function(e){return V.isStaleGraph(e.message)});",
|
|
1812
|
+
"V.showOverlay(m.category==='build'?'Build failed':m.category==='ssr'?'SSR error':m.category==='resolve'?'Module not found':'Runtime error',V.formatErrors(m.errors)+V.formatStack(m.parsedStack),m,'ws',sg)}",
|
|
1641
1813
|
"else if(m.type==='clear'){",
|
|
1642
1814
|
"if(V._needsReload){V._needsReload=false;V.removeOverlay();sessionStorage.setItem('__vertz_recovering',String(Date.now()));_reload();return}",
|
|
1643
1815
|
"var a=document.getElementById('app');",
|
|
1644
1816
|
"if(!a||a.innerHTML.length<50){V.removeOverlay();sessionStorage.setItem('__vertz_recovering',String(Date.now()));_reload();return}",
|
|
1645
1817
|
"if(V._hadClientError)return;",
|
|
1646
1818
|
"V.removeOverlay()}",
|
|
1647
|
-
"else if(m.type==='
|
|
1819
|
+
"else if(m.type==='restarting'){",
|
|
1820
|
+
"V._restarting=true;",
|
|
1821
|
+
"V.removeOverlay();",
|
|
1822
|
+
"V._src='ws';",
|
|
1823
|
+
"sessionStorage.removeItem('__vertz_reload_count');sessionStorage.removeItem('__vertz_reload_ts');",
|
|
1824
|
+
"var d2=document,c2=d2.createElement('div');c2.id='__vertz_error';",
|
|
1825
|
+
"c2.style.cssText='position:fixed;bottom:16px;left:50%;transform:translateX(-50%);z-index:2147483647;background:#1a1a1a;color:#fff;border-radius:8px;padding:14px 20px;font-family:ui-sans-serif,system-ui,sans-serif;font-size:13px;box-shadow:0 4px 24px rgba(0,0,0,0.2)';",
|
|
1826
|
+
"c2.textContent='Restarting dev server\\u2026';",
|
|
1827
|
+
"(d2.body||d2.documentElement).appendChild(c2);",
|
|
1828
|
+
"V._restartTimer=setTimeout(function(){var el=d2.getElementById('__vertz_error');if(el){el.textContent='Restart timed out. Try restarting manually (Ctrl+C and re-run).'}V._restarting=false},10000)}",
|
|
1829
|
+
"else if(m.type==='connected'){delay=1000;",
|
|
1830
|
+
"var restartEl=document.getElementById('__vertz_error');",
|
|
1831
|
+
"var timedOut=restartEl&&restartEl.textContent&&restartEl.textContent.indexOf('timed out')!==-1;",
|
|
1832
|
+
"if(V._restarting||timedOut){V._restarting=false;if(V._restartTimer){clearTimeout(V._restartTimer);V._restartTimer=null}_reload()}}",
|
|
1648
1833
|
"}catch(ex){}};",
|
|
1649
|
-
"ws.onclose=function(){V._ws=null;setTimeout(function(){delay=Math.min(delay*2,maxDelay)
|
|
1834
|
+
"ws.onclose=function(){V._ws=null;var d3=V._restarting?100:delay;setTimeout(function(){if(!V._restarting){delay=Math.min(delay*2,maxDelay)}connect()},d3)};",
|
|
1650
1835
|
"ws.onerror=function(){ws.close()}}",
|
|
1651
1836
|
"connect();",
|
|
1652
1837
|
"V._sendResolveStack=function(stack,msg){",
|
|
@@ -1656,8 +1841,11 @@ function buildErrorChannelScript(editor) {
|
|
|
1656
1841
|
"if(V._recovering&&a&&a.innerHTML.length>50)return;",
|
|
1657
1842
|
"if(V._recovering)V._recovering=false;",
|
|
1658
1843
|
"V._hadClientError=true;",
|
|
1659
|
-
"
|
|
1844
|
+
"var sg2=errors&&errors.some(function(e){return V.isStaleGraph(e.message)});",
|
|
1845
|
+
"V.showOverlay(title,V.formatErrors(errors),payload,'client',sg2);",
|
|
1846
|
+
"if(sg2){V._autoRestart()}}",
|
|
1660
1847
|
"if(V._recovering){setTimeout(function(){V._recovering=false},5000)}",
|
|
1848
|
+
"setTimeout(function(){sessionStorage.removeItem('__vertz_auto_restart')},5000);",
|
|
1661
1849
|
"window.addEventListener('error',function(e){",
|
|
1662
1850
|
"var msg=e.message||String(e.error);",
|
|
1663
1851
|
"var stk=e.error&&e.error.stack;",
|
|
@@ -1675,7 +1863,7 @@ function buildErrorChannelScript(editor) {
|
|
|
1675
1863
|
"var t=Array.prototype.join.call(arguments,' ');",
|
|
1676
1864
|
"var hmr=t.match(/\\[vertz-hmr\\] Error re-mounting (\\w+): ([\\s\\S]*?)(?:\\n\\s+at |$)/);",
|
|
1677
1865
|
"if(hmr){hmrErr=true;V._hadClientError=true;",
|
|
1678
|
-
"V.showOverlay('Runtime error',V.formatErrors([{message:
|
|
1866
|
+
"var hmrMsg=hmr[2].split('\\n')[0];var hmrSg=V.isStaleGraph(hmrMsg);V.showOverlay('Runtime error',V.formatErrors([{message:hmrMsg}]),{type:'error',category:'runtime',errors:[{message:hmrMsg}]},'client',hmrSg);if(hmrSg){V._autoRestart()}}",
|
|
1679
1867
|
"origCE.apply(console,arguments)};",
|
|
1680
1868
|
"console.log=function(){",
|
|
1681
1869
|
"var t=Array.prototype.join.call(arguments,' ');",
|
|
@@ -1819,6 +2007,16 @@ function createBunDevServer(options) {
|
|
|
1819
2007
|
let clearGraceUntil = 0;
|
|
1820
2008
|
let runtimeDebounceTimer = null;
|
|
1821
2009
|
let pendingRuntimeError = null;
|
|
2010
|
+
const autoRestartTimestamps = [];
|
|
2011
|
+
const AUTO_RESTART_CAP = 3;
|
|
2012
|
+
const AUTO_RESTART_WINDOW_MS = 1e4;
|
|
2013
|
+
function canAutoRestart() {
|
|
2014
|
+
const now = Date.now();
|
|
2015
|
+
while (autoRestartTimestamps.length > 0 && now - (autoRestartTimestamps[0] ?? 0) > AUTO_RESTART_WINDOW_MS) {
|
|
2016
|
+
autoRestartTimestamps.shift();
|
|
2017
|
+
}
|
|
2018
|
+
return autoRestartTimestamps.length < AUTO_RESTART_CAP;
|
|
2019
|
+
}
|
|
1822
2020
|
function broadcastError(category, errors) {
|
|
1823
2021
|
if (currentError?.category === "build" && category !== "build") {
|
|
1824
2022
|
return;
|
|
@@ -1827,6 +2025,26 @@ function createBunDevServer(options) {
|
|
|
1827
2025
|
return;
|
|
1828
2026
|
}
|
|
1829
2027
|
if (category === "runtime") {
|
|
2028
|
+
if (errors.some((e) => isStaleGraphError(e.message ?? ""))) {
|
|
2029
|
+
currentError = { category: "runtime", errors };
|
|
2030
|
+
const errMsg = JSON.stringify({ type: "error", category: "runtime", errors });
|
|
2031
|
+
for (const ws of wsClients) {
|
|
2032
|
+
ws.sendText(errMsg);
|
|
2033
|
+
}
|
|
2034
|
+
if (!isRestarting && canAutoRestart()) {
|
|
2035
|
+
autoRestartTimestamps.push(Date.now());
|
|
2036
|
+
if (logRequests) {
|
|
2037
|
+
const truncated = errors[0]?.message?.slice(0, 80) ?? "";
|
|
2038
|
+
console.log(`[Server] Stale graph detected: ${truncated}`);
|
|
2039
|
+
}
|
|
2040
|
+
devServer.restart();
|
|
2041
|
+
} else if (!isRestarting && !canAutoRestart()) {
|
|
2042
|
+
if (logRequests) {
|
|
2043
|
+
console.log("[Server] Auto-restart cap reached (3 in 10s), waiting for manual restart");
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
return;
|
|
2047
|
+
}
|
|
1830
2048
|
if (!pendingRuntimeError || errors.some((e) => e.file)) {
|
|
1831
2049
|
pendingRuntimeError = errors;
|
|
1832
2050
|
}
|
|
@@ -2034,32 +2252,42 @@ function createBunDevServer(options) {
|
|
|
2034
2252
|
}
|
|
2035
2253
|
return new Response("OpenAPI spec not found", { status: 404 });
|
|
2036
2254
|
};
|
|
2255
|
+
let isRestarting = false;
|
|
2256
|
+
let pluginsRegistered = false;
|
|
2257
|
+
let stableUpdateManifest = null;
|
|
2037
2258
|
async function start() {
|
|
2038
2259
|
const { plugin } = await Promise.resolve(globalThis.Bun);
|
|
2039
2260
|
const { createVertzBunPlugin } = await import("./bun-plugin/index.js");
|
|
2040
2261
|
const entryPath = resolve(projectRoot, entry);
|
|
2041
2262
|
const rawClientSrc = clientEntryOption ?? entry;
|
|
2042
2263
|
const clientSrc = rawClientSrc.replace(/^\.\//, "/");
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
build
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2264
|
+
if (!pluginsRegistered) {
|
|
2265
|
+
plugin({
|
|
2266
|
+
name: "vertz-ssr-jsx-swap",
|
|
2267
|
+
setup(build) {
|
|
2268
|
+
build.onResolve({ filter: /^@vertz\/ui\/jsx-runtime$/ }, () => ({
|
|
2269
|
+
path: "@vertz/ui-server/jsx-runtime",
|
|
2270
|
+
external: false
|
|
2271
|
+
}));
|
|
2272
|
+
build.onResolve({ filter: /^@vertz\/ui\/jsx-dev-runtime$/ }, () => ({
|
|
2273
|
+
path: "@vertz/ui-server/jsx-runtime",
|
|
2274
|
+
external: false
|
|
2275
|
+
}));
|
|
2276
|
+
}
|
|
2277
|
+
});
|
|
2278
|
+
}
|
|
2279
|
+
if (!pluginsRegistered) {
|
|
2280
|
+
const { plugin: serverPlugin, updateManifest } = createVertzBunPlugin({
|
|
2281
|
+
hmr: false,
|
|
2282
|
+
fastRefresh: false,
|
|
2283
|
+
logger,
|
|
2284
|
+
diagnostics
|
|
2285
|
+
});
|
|
2286
|
+
plugin(serverPlugin);
|
|
2287
|
+
stableUpdateManifest = updateManifest;
|
|
2288
|
+
}
|
|
2289
|
+
pluginsRegistered = true;
|
|
2290
|
+
const updateServerManifest = stableUpdateManifest;
|
|
2063
2291
|
let ssrMod;
|
|
2064
2292
|
try {
|
|
2065
2293
|
ssrMod = await import(entryPath);
|
|
@@ -2068,6 +2296,9 @@ function createBunDevServer(options) {
|
|
|
2068
2296
|
}
|
|
2069
2297
|
} catch (e) {
|
|
2070
2298
|
console.error("[Server] Failed to load SSR module:", e);
|
|
2299
|
+
if (isRestarting) {
|
|
2300
|
+
throw e;
|
|
2301
|
+
}
|
|
2071
2302
|
process.exit(1);
|
|
2072
2303
|
}
|
|
2073
2304
|
let fontFallbackMetrics;
|
|
@@ -2272,6 +2503,18 @@ data: {}
|
|
|
2272
2503
|
console.warn("[Server] Session resolver failed:", resolverErr instanceof Error ? resolverErr.message : resolverErr);
|
|
2273
2504
|
}
|
|
2274
2505
|
}
|
|
2506
|
+
if (ssrAuth && apiHandler) {
|
|
2507
|
+
try {
|
|
2508
|
+
const origin = `http://${host}:${server?.port}`;
|
|
2509
|
+
const provRes = await apiHandler(new Request(`${origin}${skipSSRPaths[0]}auth/providers`));
|
|
2510
|
+
if (provRes.ok) {
|
|
2511
|
+
const providers = await provRes.json();
|
|
2512
|
+
ssrAuth.providers = providers;
|
|
2513
|
+
sessionScript += `
|
|
2514
|
+
<script>window.__VERTZ_PROVIDERS__=${safeSerialize(providers)};</script>`;
|
|
2515
|
+
}
|
|
2516
|
+
} catch {}
|
|
2517
|
+
}
|
|
2275
2518
|
const doRender = async () => {
|
|
2276
2519
|
logger.log("ssr", "render-start", { url: pathname });
|
|
2277
2520
|
const ssrStart = performance.now();
|
|
@@ -2355,7 +2598,9 @@ data: {}
|
|
|
2355
2598
|
message(ws, msg) {
|
|
2356
2599
|
try {
|
|
2357
2600
|
const data = JSON.parse(typeof msg === "string" ? msg : new TextDecoder().decode(msg));
|
|
2358
|
-
if (data.type === "
|
|
2601
|
+
if (data.type === "restart") {
|
|
2602
|
+
devServer.restart();
|
|
2603
|
+
} else if (data.type === "ping") {
|
|
2359
2604
|
ws.sendText(JSON.stringify({ type: "pong" }));
|
|
2360
2605
|
} else if (data.type === "resolve-stack" && data.stack) {
|
|
2361
2606
|
const selfFetch = async (url) => {
|
|
@@ -2609,7 +2854,7 @@ data: {}
|
|
|
2609
2854
|
});
|
|
2610
2855
|
}
|
|
2611
2856
|
}
|
|
2612
|
-
|
|
2857
|
+
const devServer = {
|
|
2613
2858
|
start,
|
|
2614
2859
|
broadcastError,
|
|
2615
2860
|
clearError,
|
|
@@ -2635,11 +2880,70 @@ data: {}
|
|
|
2635
2880
|
server.stop(true);
|
|
2636
2881
|
server = null;
|
|
2637
2882
|
}
|
|
2883
|
+
},
|
|
2884
|
+
async restart() {
|
|
2885
|
+
if (isRestarting) {
|
|
2886
|
+
if (logRequests) {
|
|
2887
|
+
console.log("[Server] Restart already in progress, skipping");
|
|
2888
|
+
}
|
|
2889
|
+
return;
|
|
2890
|
+
}
|
|
2891
|
+
isRestarting = true;
|
|
2892
|
+
if (logRequests) {
|
|
2893
|
+
console.log("[Server] Restarting dev server...");
|
|
2894
|
+
}
|
|
2895
|
+
const restartMsg = JSON.stringify({ type: "restarting" });
|
|
2896
|
+
for (const ws of wsClients) {
|
|
2897
|
+
try {
|
|
2898
|
+
ws.sendText(restartMsg);
|
|
2899
|
+
} catch {}
|
|
2900
|
+
}
|
|
2901
|
+
await devServer.stop();
|
|
2902
|
+
wsClients.clear();
|
|
2903
|
+
currentError = null;
|
|
2904
|
+
if (runtimeDebounceTimer) {
|
|
2905
|
+
clearTimeout(runtimeDebounceTimer);
|
|
2906
|
+
runtimeDebounceTimer = null;
|
|
2907
|
+
}
|
|
2908
|
+
pendingRuntimeError = null;
|
|
2909
|
+
lastBuildError = "";
|
|
2910
|
+
lastBroadcastedError = "";
|
|
2911
|
+
lastChangedFile = "";
|
|
2912
|
+
clearGraceUntil = 0;
|
|
2913
|
+
terminalDedup.reset();
|
|
2914
|
+
clearSSRRequireCache();
|
|
2915
|
+
sourceMapResolver.invalidate();
|
|
2916
|
+
const retryDelays = [100, 200, 500];
|
|
2917
|
+
let lastErr;
|
|
2918
|
+
for (let attempt = 0;attempt < retryDelays.length; attempt++) {
|
|
2919
|
+
await new Promise((r) => setTimeout(r, retryDelays[attempt]));
|
|
2920
|
+
try {
|
|
2921
|
+
await start();
|
|
2922
|
+
if (logRequests) {
|
|
2923
|
+
console.log(`[Server] Dev server restarted on port ${port}`);
|
|
2924
|
+
}
|
|
2925
|
+
lastErr = null;
|
|
2926
|
+
break;
|
|
2927
|
+
} catch (e) {
|
|
2928
|
+
lastErr = e;
|
|
2929
|
+
if (logRequests) {
|
|
2930
|
+
const errMsg = e instanceof Error ? e.message : String(e);
|
|
2931
|
+
console.log(`[Server] Restart attempt ${attempt + 1} failed: ${errMsg}`);
|
|
2932
|
+
}
|
|
2933
|
+
}
|
|
2934
|
+
}
|
|
2935
|
+
if (lastErr) {
|
|
2936
|
+
const errMsg = lastErr instanceof Error ? lastErr.message : String(lastErr);
|
|
2937
|
+
console.error(`[Server] Restart failed after ${retryDelays.length} attempts: ${errMsg}`);
|
|
2938
|
+
}
|
|
2939
|
+
isRestarting = false;
|
|
2638
2940
|
}
|
|
2639
2941
|
};
|
|
2942
|
+
return devServer;
|
|
2640
2943
|
}
|
|
2641
2944
|
export {
|
|
2642
2945
|
parseHMRAssets,
|
|
2946
|
+
isStaleGraphError,
|
|
2643
2947
|
generateSSRPageHtml,
|
|
2644
2948
|
formatTerminalRuntimeError,
|
|
2645
2949
|
detectFaviconTag,
|
package/dist/bun-plugin/index.js
CHANGED
package/dist/dom-shim/index.d.ts
CHANGED
|
@@ -63,8 +63,9 @@ declare class SSRElement extends SSRNode {
|
|
|
63
63
|
display: string;
|
|
64
64
|
[key: string]: any;
|
|
65
65
|
};
|
|
66
|
+
dataset: DOMStringMap;
|
|
66
67
|
constructor(tag: string);
|
|
67
|
-
setAttribute(name: string, value: string): void;
|
|
68
|
+
setAttribute(name: string, value: string | Record<string, any>): void;
|
|
68
69
|
getAttribute(name: string): string | null;
|
|
69
70
|
removeAttribute(name: string): void;
|
|
70
71
|
appendChild(child: SSRElement | SSRTextNode | SSRComment | SSRDocumentFragment): void;
|
|
@@ -83,6 +84,30 @@ declare class SSRElement extends SSRNode {
|
|
|
83
84
|
get textContent(): string | null;
|
|
84
85
|
set innerHTML(value: string);
|
|
85
86
|
get innerHTML(): string;
|
|
87
|
+
get placeholder(): string;
|
|
88
|
+
set placeholder(value: string);
|
|
89
|
+
get type(): string;
|
|
90
|
+
set type(value: string);
|
|
91
|
+
get name(): string;
|
|
92
|
+
set name(value: string);
|
|
93
|
+
get value(): string;
|
|
94
|
+
set value(value: string);
|
|
95
|
+
get src(): string;
|
|
96
|
+
set src(value: string);
|
|
97
|
+
get alt(): string;
|
|
98
|
+
set alt(value: string);
|
|
99
|
+
get htmlFor(): string;
|
|
100
|
+
set htmlFor(value: string);
|
|
101
|
+
get disabled(): boolean;
|
|
102
|
+
set disabled(value: boolean);
|
|
103
|
+
get checked(): boolean;
|
|
104
|
+
set checked(value: boolean);
|
|
105
|
+
get rows(): number;
|
|
106
|
+
set rows(value: number);
|
|
107
|
+
get scope(): string;
|
|
108
|
+
set scope(value: string);
|
|
109
|
+
get href(): string;
|
|
110
|
+
set href(value: string);
|
|
86
111
|
addEventListener(_event: string, _handler: any): void;
|
|
87
112
|
removeEventListener(_event: string, _handler: any): void;
|
|
88
113
|
/** Convert to a VNode tree for @vertz/ui-server */
|
package/dist/dom-shim/index.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -374,7 +374,7 @@ declare function clearGlobalSSRTimeout(): void;
|
|
|
374
374
|
*/
|
|
375
375
|
declare function getGlobalSSRTimeout(): number | undefined;
|
|
376
376
|
import { FontFallbackMetrics as FontFallbackMetrics4 } from "@vertz/ui";
|
|
377
|
-
import { FontFallbackMetrics as FontFallbackMetrics3, Theme as Theme2 } from "@vertz/ui";
|
|
377
|
+
import { CompiledRoute, FontFallbackMetrics as FontFallbackMetrics3, Theme as Theme2 } from "@vertz/ui";
|
|
378
378
|
import { SSRAuth as SSRAuth_jq1nwm } from "@vertz/ui/internals";
|
|
379
379
|
interface SSRModule {
|
|
380
380
|
default?: () => unknown;
|
|
@@ -390,6 +390,8 @@ interface SSRModule {
|
|
|
390
390
|
* SSR renderer. Export `getInjectedCSS` from @vertz/ui in the app entry.
|
|
391
391
|
*/
|
|
392
392
|
getInjectedCSS?: () => string[];
|
|
393
|
+
/** Compiled routes exported from the app for build-time SSG with generateParams. */
|
|
394
|
+
routes?: CompiledRoute[];
|
|
393
395
|
}
|
|
394
396
|
interface SSRRenderResult {
|
|
395
397
|
html: string;
|
|
@@ -402,6 +404,8 @@ interface SSRRenderResult {
|
|
|
402
404
|
headTags: string;
|
|
403
405
|
/** Route patterns discovered by createRouter() during SSR (for build-time pre-rendering). */
|
|
404
406
|
discoveredRoutes?: string[];
|
|
407
|
+
/** Route patterns that matched the current URL (for per-route modulepreload). */
|
|
408
|
+
matchedRoutePatterns?: string[];
|
|
405
409
|
/** Set when ProtectedRoute writes a redirect during SSR. Server should return 302. */
|
|
406
410
|
redirect?: {
|
|
407
411
|
to: string;
|
|
@@ -500,6 +504,13 @@ interface SSRHandlerOptions {
|
|
|
500
504
|
fallbackMetrics?: Record<string, FontFallbackMetrics4>;
|
|
501
505
|
/** Paths to inject as `<link rel="modulepreload">` in `<head>`. */
|
|
502
506
|
modulepreload?: string[];
|
|
507
|
+
/**
|
|
508
|
+
* Route chunk manifest for per-route modulepreload injection.
|
|
509
|
+
* When provided, only chunks for the matched route are preloaded instead of all chunks.
|
|
510
|
+
*/
|
|
511
|
+
routeChunkManifest?: {
|
|
512
|
+
routes: Record<string, string[]>;
|
|
513
|
+
};
|
|
503
514
|
/** Cache-Control header for HTML responses. Omit or undefined = no header (safe default). */
|
|
504
515
|
cacheControl?: string;
|
|
505
516
|
/**
|
package/dist/index.js
CHANGED
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
ssrDiscoverQueries,
|
|
20
20
|
ssrRenderToString,
|
|
21
21
|
streamToString
|
|
22
|
-
} from "./shared/chunk-
|
|
22
|
+
} from "./shared/chunk-qzdhe6bn.js";
|
|
23
23
|
import {
|
|
24
24
|
clearGlobalSSRTimeout,
|
|
25
25
|
createSSRAdapter,
|
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
registerSSRQuery,
|
|
32
32
|
setGlobalSSRTimeout,
|
|
33
33
|
ssrStorage
|
|
34
|
-
} from "./shared/chunk-
|
|
34
|
+
} from "./shared/chunk-1363r1qw.js";
|
|
35
35
|
|
|
36
36
|
// src/asset-pipeline.ts
|
|
37
37
|
function renderAssetTags(assets) {
|
|
@@ -29,13 +29,18 @@ function jsx(tag, props) {
|
|
|
29
29
|
}
|
|
30
30
|
const { children, ...attrs } = props || {};
|
|
31
31
|
const serializableAttrs = {};
|
|
32
|
+
const resolvedClass = unwrapSignal(attrs.className) ?? unwrapSignal(attrs.class);
|
|
32
33
|
for (const [key, rawValue] of Object.entries(attrs)) {
|
|
33
34
|
if (key.startsWith("on") && typeof rawValue === "function") {
|
|
34
35
|
continue;
|
|
35
36
|
}
|
|
36
37
|
const value = unwrapSignal(rawValue);
|
|
37
|
-
if (key === "
|
|
38
|
-
|
|
38
|
+
if (key === "className" || key === "class") {
|
|
39
|
+
if (key === "class" && attrs.className != null)
|
|
40
|
+
continue;
|
|
41
|
+
if (resolvedClass != null) {
|
|
42
|
+
serializableAttrs.class = String(resolvedClass);
|
|
43
|
+
}
|
|
39
44
|
continue;
|
|
40
45
|
}
|
|
41
46
|
if (key === "style" && value != null) {
|
|
@@ -61,6 +61,9 @@ class SSRComment extends SSRNode {
|
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
// src/dom-shim/ssr-element.ts
|
|
65
|
+
import { __styleStr } from "@vertz/ui/internals";
|
|
66
|
+
|
|
64
67
|
// src/types.ts
|
|
65
68
|
function rawHtml(html) {
|
|
66
69
|
return { __raw: true, html };
|
|
@@ -104,6 +107,52 @@ class SSRDocumentFragment extends SSRNode {
|
|
|
104
107
|
}
|
|
105
108
|
|
|
106
109
|
// src/dom-shim/ssr-element.ts
|
|
110
|
+
function camelToKebab(str) {
|
|
111
|
+
return str.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
|
|
112
|
+
}
|
|
113
|
+
function kebabToCamel(str) {
|
|
114
|
+
return str.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
115
|
+
}
|
|
116
|
+
function createDatasetProxy(element) {
|
|
117
|
+
return new Proxy({}, {
|
|
118
|
+
set(_target, prop, value) {
|
|
119
|
+
if (typeof prop === "string") {
|
|
120
|
+
element.setAttribute(`data-${camelToKebab(prop)}`, String(value));
|
|
121
|
+
}
|
|
122
|
+
return true;
|
|
123
|
+
},
|
|
124
|
+
get(_target, prop) {
|
|
125
|
+
if (typeof prop === "string") {
|
|
126
|
+
return element.getAttribute(`data-${camelToKebab(prop)}`) ?? undefined;
|
|
127
|
+
}
|
|
128
|
+
return;
|
|
129
|
+
},
|
|
130
|
+
has(_target, prop) {
|
|
131
|
+
if (typeof prop === "string") {
|
|
132
|
+
return element.getAttribute(`data-${camelToKebab(prop)}`) !== null;
|
|
133
|
+
}
|
|
134
|
+
return false;
|
|
135
|
+
},
|
|
136
|
+
deleteProperty(_target, prop) {
|
|
137
|
+
if (typeof prop === "string") {
|
|
138
|
+
element.removeAttribute(`data-${camelToKebab(prop)}`);
|
|
139
|
+
}
|
|
140
|
+
return true;
|
|
141
|
+
},
|
|
142
|
+
ownKeys() {
|
|
143
|
+
return Object.keys(element.attrs).filter((k) => k.startsWith("data-")).map((k) => kebabToCamel(k.slice(5)));
|
|
144
|
+
},
|
|
145
|
+
getOwnPropertyDescriptor(_target, prop) {
|
|
146
|
+
if (typeof prop === "string") {
|
|
147
|
+
const val = element.getAttribute(`data-${camelToKebab(prop)}`);
|
|
148
|
+
if (val !== null) {
|
|
149
|
+
return { configurable: true, enumerable: true, value: val };
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
}
|
|
107
156
|
function createStyleProxy(element) {
|
|
108
157
|
const styles = {};
|
|
109
158
|
return new Proxy(styles, {
|
|
@@ -135,16 +184,27 @@ class SSRElement extends SSRNode {
|
|
|
135
184
|
_textContent = null;
|
|
136
185
|
_innerHTML = null;
|
|
137
186
|
style;
|
|
187
|
+
dataset;
|
|
138
188
|
constructor(tag) {
|
|
139
189
|
super();
|
|
140
190
|
this.tag = tag;
|
|
141
191
|
this.style = createStyleProxy(this);
|
|
192
|
+
this.dataset = createDatasetProxy(this);
|
|
142
193
|
}
|
|
143
194
|
setAttribute(name, value) {
|
|
144
|
-
if (name === "
|
|
195
|
+
if (name === "style" && typeof value === "object" && value !== null) {
|
|
196
|
+
this.attrs.style = __styleStr(value);
|
|
197
|
+
for (const [k, v] of Object.entries(value)) {
|
|
198
|
+
if (v != null)
|
|
199
|
+
this.style[k] = String(v);
|
|
200
|
+
}
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
const attrName = name === "className" ? "class" : name;
|
|
204
|
+
if (attrName === "class") {
|
|
145
205
|
this._classList = new Set(value.split(/\s+/).filter(Boolean));
|
|
146
206
|
}
|
|
147
|
-
this.attrs[
|
|
207
|
+
this.attrs[attrName] = value;
|
|
148
208
|
}
|
|
149
209
|
getAttribute(name) {
|
|
150
210
|
return this.attrs[name] ?? null;
|
|
@@ -281,6 +341,86 @@ class SSRElement extends SSRNode {
|
|
|
281
341
|
get innerHTML() {
|
|
282
342
|
return this._innerHTML ?? "";
|
|
283
343
|
}
|
|
344
|
+
get placeholder() {
|
|
345
|
+
return this.attrs.placeholder ?? "";
|
|
346
|
+
}
|
|
347
|
+
set placeholder(value) {
|
|
348
|
+
this.attrs.placeholder = value;
|
|
349
|
+
}
|
|
350
|
+
get type() {
|
|
351
|
+
return this.attrs.type ?? "";
|
|
352
|
+
}
|
|
353
|
+
set type(value) {
|
|
354
|
+
this.attrs.type = value;
|
|
355
|
+
}
|
|
356
|
+
get name() {
|
|
357
|
+
return this.attrs.name ?? "";
|
|
358
|
+
}
|
|
359
|
+
set name(value) {
|
|
360
|
+
this.attrs.name = value;
|
|
361
|
+
}
|
|
362
|
+
get value() {
|
|
363
|
+
return this.attrs.value ?? "";
|
|
364
|
+
}
|
|
365
|
+
set value(value) {
|
|
366
|
+
this.attrs.value = value;
|
|
367
|
+
}
|
|
368
|
+
get src() {
|
|
369
|
+
return this.attrs.src ?? "";
|
|
370
|
+
}
|
|
371
|
+
set src(value) {
|
|
372
|
+
this.attrs.src = value;
|
|
373
|
+
}
|
|
374
|
+
get alt() {
|
|
375
|
+
return this.attrs.alt ?? "";
|
|
376
|
+
}
|
|
377
|
+
set alt(value) {
|
|
378
|
+
this.attrs.alt = value;
|
|
379
|
+
}
|
|
380
|
+
get htmlFor() {
|
|
381
|
+
return this.attrs.for ?? "";
|
|
382
|
+
}
|
|
383
|
+
set htmlFor(value) {
|
|
384
|
+
this.attrs.for = value;
|
|
385
|
+
}
|
|
386
|
+
get disabled() {
|
|
387
|
+
return "disabled" in this.attrs;
|
|
388
|
+
}
|
|
389
|
+
set disabled(value) {
|
|
390
|
+
if (value) {
|
|
391
|
+
this.attrs.disabled = "";
|
|
392
|
+
} else {
|
|
393
|
+
delete this.attrs.disabled;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
get checked() {
|
|
397
|
+
return "checked" in this.attrs;
|
|
398
|
+
}
|
|
399
|
+
set checked(value) {
|
|
400
|
+
if (value) {
|
|
401
|
+
this.attrs.checked = "";
|
|
402
|
+
} else {
|
|
403
|
+
delete this.attrs.checked;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
get rows() {
|
|
407
|
+
return Number(this.attrs.rows) || 0;
|
|
408
|
+
}
|
|
409
|
+
set rows(value) {
|
|
410
|
+
this.attrs.rows = String(value);
|
|
411
|
+
}
|
|
412
|
+
get scope() {
|
|
413
|
+
return this.attrs.scope ?? "";
|
|
414
|
+
}
|
|
415
|
+
set scope(value) {
|
|
416
|
+
this.attrs.scope = value;
|
|
417
|
+
}
|
|
418
|
+
get href() {
|
|
419
|
+
return this.attrs.href ?? "";
|
|
420
|
+
}
|
|
421
|
+
set href(value) {
|
|
422
|
+
this.attrs.href = value;
|
|
423
|
+
}
|
|
284
424
|
addEventListener(_event, _handler) {}
|
|
285
425
|
removeEventListener(_event, _handler) {}
|
|
286
426
|
toVNode() {
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
setGlobalSSRTimeout,
|
|
7
7
|
ssrStorage,
|
|
8
8
|
toVNode
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-1363r1qw.js";
|
|
10
10
|
|
|
11
11
|
// src/html-serializer.ts
|
|
12
12
|
var VOID_ELEMENTS = new Set([
|
|
@@ -330,7 +330,8 @@ async function ssrRenderToString(module, url, options) {
|
|
|
330
330
|
css,
|
|
331
331
|
ssrData,
|
|
332
332
|
headTags: themePreloadTags,
|
|
333
|
-
discoveredRoutes: ctx.discoveredRoutes
|
|
333
|
+
discoveredRoutes: ctx.discoveredRoutes,
|
|
334
|
+
matchedRoutePatterns: ctx.matchedRoutePatterns
|
|
334
335
|
};
|
|
335
336
|
} finally {
|
|
336
337
|
clearGlobalSSRTimeout();
|
|
@@ -536,7 +537,7 @@ function injectIntoTemplate(options) {
|
|
|
536
537
|
if (template.includes("<!--ssr-outlet-->")) {
|
|
537
538
|
html = template.replace("<!--ssr-outlet-->", appHtml);
|
|
538
539
|
} else {
|
|
539
|
-
html = template
|
|
540
|
+
html = replaceAppDivContent(template, appHtml);
|
|
540
541
|
}
|
|
541
542
|
if (headTags) {
|
|
542
543
|
html = html.replace("</head>", `${headTags}
|
|
@@ -560,6 +561,37 @@ function injectIntoTemplate(options) {
|
|
|
560
561
|
}
|
|
561
562
|
return html;
|
|
562
563
|
}
|
|
564
|
+
function replaceAppDivContent(template, appHtml) {
|
|
565
|
+
const openMatch = template.match(/<div[^>]*id="app"[^>]*>/);
|
|
566
|
+
if (!openMatch || openMatch.index == null)
|
|
567
|
+
return template;
|
|
568
|
+
const openTag = openMatch[0];
|
|
569
|
+
const contentStart = openMatch.index + openTag.length;
|
|
570
|
+
let depth = 1;
|
|
571
|
+
let i = contentStart;
|
|
572
|
+
const len = template.length;
|
|
573
|
+
while (i < len && depth > 0) {
|
|
574
|
+
if (template[i] === "<") {
|
|
575
|
+
if (template.startsWith("</div>", i)) {
|
|
576
|
+
depth--;
|
|
577
|
+
if (depth === 0)
|
|
578
|
+
break;
|
|
579
|
+
i += 6;
|
|
580
|
+
} else if (template.startsWith("<div", i) && /^<div[\s>]/.test(template.slice(i, i + 5))) {
|
|
581
|
+
depth++;
|
|
582
|
+
i += 4;
|
|
583
|
+
} else {
|
|
584
|
+
i++;
|
|
585
|
+
}
|
|
586
|
+
} else {
|
|
587
|
+
i++;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
if (depth !== 0) {
|
|
591
|
+
return template;
|
|
592
|
+
}
|
|
593
|
+
return template.slice(0, contentStart) + appHtml + template.slice(i);
|
|
594
|
+
}
|
|
563
595
|
|
|
564
596
|
// src/ssr-handler.ts
|
|
565
597
|
import { compileTheme as compileTheme2 } from "@vertz/ui";
|
|
@@ -595,6 +627,7 @@ function createSSRHandler(options) {
|
|
|
595
627
|
nonce,
|
|
596
628
|
fallbackMetrics,
|
|
597
629
|
modulepreload,
|
|
630
|
+
routeChunkManifest,
|
|
598
631
|
cacheControl,
|
|
599
632
|
sessionResolver
|
|
600
633
|
} = options;
|
|
@@ -646,7 +679,7 @@ function createSSRHandler(options) {
|
|
|
646
679
|
console.warn("[Server] Session resolver failed:", resolverErr instanceof Error ? resolverErr.message : resolverErr);
|
|
647
680
|
}
|
|
648
681
|
}
|
|
649
|
-
return handleHTMLRequest(module, template, pathname + url.search, ssrTimeout, nonce, fallbackMetrics, linkHeader, modulepreloadTags, cacheControl, sessionScript, ssrAuth);
|
|
682
|
+
return handleHTMLRequest(module, template, pathname + url.search, ssrTimeout, nonce, fallbackMetrics, linkHeader, modulepreloadTags, routeChunkManifest, cacheControl, sessionScript, ssrAuth);
|
|
650
683
|
};
|
|
651
684
|
}
|
|
652
685
|
async function handleNavRequest(module, url, ssrTimeout) {
|
|
@@ -672,7 +705,7 @@ data: {}
|
|
|
672
705
|
});
|
|
673
706
|
}
|
|
674
707
|
}
|
|
675
|
-
async function handleHTMLRequest(module, template, url, ssrTimeout, nonce, fallbackMetrics, linkHeader,
|
|
708
|
+
async function handleHTMLRequest(module, template, url, ssrTimeout, nonce, fallbackMetrics, linkHeader, staticModulepreloadTags, routeChunkManifest, cacheControl, sessionScript, ssrAuth) {
|
|
676
709
|
try {
|
|
677
710
|
const result = await ssrRenderToString(module, url, { ssrTimeout, fallbackMetrics, ssrAuth });
|
|
678
711
|
if (result.redirect) {
|
|
@@ -681,6 +714,21 @@ async function handleHTMLRequest(module, template, url, ssrTimeout, nonce, fallb
|
|
|
681
714
|
headers: { Location: result.redirect.to }
|
|
682
715
|
});
|
|
683
716
|
}
|
|
717
|
+
let modulepreloadTags = staticModulepreloadTags;
|
|
718
|
+
if (routeChunkManifest && result.matchedRoutePatterns?.length) {
|
|
719
|
+
const chunkPaths = new Set;
|
|
720
|
+
for (const pattern of result.matchedRoutePatterns) {
|
|
721
|
+
const chunks = routeChunkManifest.routes[pattern];
|
|
722
|
+
if (chunks) {
|
|
723
|
+
for (const chunk of chunks) {
|
|
724
|
+
chunkPaths.add(chunk);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
if (chunkPaths.size > 0) {
|
|
729
|
+
modulepreloadTags = buildModulepreloadTags([...chunkPaths]);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
684
732
|
const allHeadTags = [result.headTags, modulepreloadTags].filter(Boolean).join(`
|
|
685
733
|
`);
|
|
686
734
|
const html = injectIntoTemplate({
|
package/dist/ssr/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { CompiledRoute } from "@vertz/ui";
|
|
2
|
-
import { FontFallbackMetrics, Theme } from "@vertz/ui";
|
|
1
|
+
import { CompiledRoute as CompiledRoute2 } from "@vertz/ui";
|
|
2
|
+
import { CompiledRoute, FontFallbackMetrics, Theme } from "@vertz/ui";
|
|
3
3
|
import { SSRAuth as SSRAuth_jq1nwm } from "@vertz/ui/internals";
|
|
4
4
|
interface SSRModule {
|
|
5
5
|
default?: () => unknown;
|
|
@@ -15,6 +15,8 @@ interface SSRModule {
|
|
|
15
15
|
* SSR renderer. Export `getInjectedCSS` from @vertz/ui in the app entry.
|
|
16
16
|
*/
|
|
17
17
|
getInjectedCSS?: () => string[];
|
|
18
|
+
/** Compiled routes exported from the app for build-time SSG with generateParams. */
|
|
19
|
+
routes?: CompiledRoute[];
|
|
18
20
|
}
|
|
19
21
|
interface SSRRenderResult {
|
|
20
22
|
html: string;
|
|
@@ -27,6 +29,8 @@ interface SSRRenderResult {
|
|
|
27
29
|
headTags: string;
|
|
28
30
|
/** Route patterns discovered by createRouter() during SSR (for build-time pre-rendering). */
|
|
29
31
|
discoveredRoutes?: string[];
|
|
32
|
+
/** Route patterns that matched the current URL (for per-route modulepreload). */
|
|
33
|
+
matchedRoutePatterns?: string[];
|
|
30
34
|
/** Set when ProtectedRoute writes a redirect during SSR. Server should return 302. */
|
|
31
35
|
redirect?: {
|
|
32
36
|
to: string;
|
|
@@ -88,7 +92,7 @@ declare function discoverRoutes(module: SSRModule): Promise<string[]>;
|
|
|
88
92
|
* - Routes with `*` wildcard
|
|
89
93
|
* - Routes with `prerender: false` (looked up in compiledRoutes)
|
|
90
94
|
*/
|
|
91
|
-
declare function filterPrerenderableRoutes(patterns: string[], compiledRoutes?:
|
|
95
|
+
declare function filterPrerenderableRoutes(patterns: string[], compiledRoutes?: CompiledRoute2[]): string[];
|
|
92
96
|
/**
|
|
93
97
|
* Pre-render a list of routes into complete HTML strings.
|
|
94
98
|
*
|
|
@@ -110,6 +114,16 @@ declare function prerenderRoutes(module: SSRModule, template: string, options: P
|
|
|
110
114
|
* pages (like /manifesto) ship zero JavaScript.
|
|
111
115
|
*/
|
|
112
116
|
declare function stripScriptsFromStaticHTML(html: string): string;
|
|
117
|
+
/**
|
|
118
|
+
* Collect all paths that should be pre-rendered from compiled routes.
|
|
119
|
+
*
|
|
120
|
+
* Walks the route tree and collects:
|
|
121
|
+
* - Static routes with `prerender: true`
|
|
122
|
+
* - Dynamic routes with `generateParams` (expanded to concrete paths)
|
|
123
|
+
* - Skips routes with `prerender: false`
|
|
124
|
+
* - Routes without `prerender` or `generateParams` are not collected
|
|
125
|
+
*/
|
|
126
|
+
declare function collectPrerenderPaths(routes: CompiledRoute2[], prefix?: string): Promise<string[]>;
|
|
113
127
|
import { FontFallbackMetrics as FontFallbackMetrics2 } from "@vertz/ui";
|
|
114
128
|
import { AccessSet } from "@vertz/ui/auth";
|
|
115
129
|
interface SessionData {
|
|
@@ -167,6 +181,13 @@ interface SSRHandlerOptions {
|
|
|
167
181
|
fallbackMetrics?: Record<string, FontFallbackMetrics2>;
|
|
168
182
|
/** Paths to inject as `<link rel="modulepreload">` in `<head>`. */
|
|
169
183
|
modulepreload?: string[];
|
|
184
|
+
/**
|
|
185
|
+
* Route chunk manifest for per-route modulepreload injection.
|
|
186
|
+
* When provided, only chunks for the matched route are preloaded instead of all chunks.
|
|
187
|
+
*/
|
|
188
|
+
routeChunkManifest?: {
|
|
189
|
+
routes: Record<string, string[]>;
|
|
190
|
+
};
|
|
170
191
|
/** Cache-Control header for HTML responses. Omit or undefined = no header (safe default). */
|
|
171
192
|
cacheControl?: string;
|
|
172
193
|
/**
|
|
@@ -197,4 +218,4 @@ interface InjectIntoTemplateOptions {
|
|
|
197
218
|
* injects CSS before </head>, and ssrData before </body>.
|
|
198
219
|
*/
|
|
199
220
|
declare function injectIntoTemplate(options: InjectIntoTemplateOptions): string;
|
|
200
|
-
export { stripScriptsFromStaticHTML, ssrRenderToString, ssrDiscoverQueries, prerenderRoutes, injectIntoTemplate, filterPrerenderableRoutes, discoverRoutes, createSSRHandler, SSRRenderResult, SSRModule, SSRHandlerOptions, SSRDiscoverResult, PrerenderResult, PrerenderOptions };
|
|
221
|
+
export { stripScriptsFromStaticHTML, ssrRenderToString, ssrDiscoverQueries, prerenderRoutes, injectIntoTemplate, filterPrerenderableRoutes, discoverRoutes, createSSRHandler, collectPrerenderPaths, SSRRenderResult, SSRModule, SSRHandlerOptions, SSRDiscoverResult, PrerenderResult, PrerenderOptions };
|
package/dist/ssr/index.js
CHANGED
|
@@ -3,8 +3,8 @@ import {
|
|
|
3
3
|
injectIntoTemplate,
|
|
4
4
|
ssrDiscoverQueries,
|
|
5
5
|
ssrRenderToString
|
|
6
|
-
} from "../shared/chunk-
|
|
7
|
-
import"../shared/chunk-
|
|
6
|
+
} from "../shared/chunk-qzdhe6bn.js";
|
|
7
|
+
import"../shared/chunk-1363r1qw.js";
|
|
8
8
|
|
|
9
9
|
// src/prerender.ts
|
|
10
10
|
async function discoverRoutes(module) {
|
|
@@ -56,6 +56,36 @@ function stripScriptsFromStaticHTML(html) {
|
|
|
56
56
|
result = result.replace(/<link\b[^>]*\brel=["']modulepreload["'][^>]*\/?>/gi, "");
|
|
57
57
|
return result;
|
|
58
58
|
}
|
|
59
|
+
async function collectPrerenderPaths(routes, prefix = "") {
|
|
60
|
+
const paths = [];
|
|
61
|
+
for (const route of routes) {
|
|
62
|
+
const fullPattern = joinPatterns(prefix, route.pattern);
|
|
63
|
+
const optedOut = route.prerender === false;
|
|
64
|
+
if (!optedOut) {
|
|
65
|
+
if (route.generateParams) {
|
|
66
|
+
const paramSets = await route.generateParams();
|
|
67
|
+
for (const params of paramSets) {
|
|
68
|
+
let path = fullPattern;
|
|
69
|
+
for (const [key, value] of Object.entries(params)) {
|
|
70
|
+
path = path.replaceAll(`:${key}`, value);
|
|
71
|
+
}
|
|
72
|
+
const unreplaced = path.match(/:(\w+)/);
|
|
73
|
+
if (unreplaced) {
|
|
74
|
+
throw new Error(`generateParams for "${fullPattern}" returned params missing key "${unreplaced[1]}"`);
|
|
75
|
+
}
|
|
76
|
+
paths.push(path);
|
|
77
|
+
}
|
|
78
|
+
} else if (route.prerender === true) {
|
|
79
|
+
paths.push(fullPattern);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (route.children) {
|
|
83
|
+
const childPaths = await collectPrerenderPaths(route.children, fullPattern);
|
|
84
|
+
paths.push(...childPaths);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return paths;
|
|
88
|
+
}
|
|
59
89
|
function findCompiledRoute(routes, pattern, prefix = "") {
|
|
60
90
|
for (const route of routes) {
|
|
61
91
|
const fullPattern = joinPatterns(prefix, route.pattern);
|
|
@@ -84,5 +114,6 @@ export {
|
|
|
84
114
|
injectIntoTemplate,
|
|
85
115
|
filterPrerenderableRoutes,
|
|
86
116
|
discoverRoutes,
|
|
87
|
-
createSSRHandler
|
|
117
|
+
createSSRHandler,
|
|
118
|
+
collectPrerenderPaths
|
|
88
119
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vertz/ui-server",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.22",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Vertz UI server-side rendering runtime",
|
|
@@ -58,16 +58,16 @@
|
|
|
58
58
|
"@ampproject/remapping": "^2.3.0",
|
|
59
59
|
"@capsizecss/unpack": "^4.0.0",
|
|
60
60
|
"@jridgewell/trace-mapping": "^0.3.31",
|
|
61
|
-
"@vertz/core": "^0.2.
|
|
62
|
-
"@vertz/ui": "^0.2.
|
|
63
|
-
"@vertz/ui-compiler": "^0.2.
|
|
61
|
+
"@vertz/core": "^0.2.21",
|
|
62
|
+
"@vertz/ui": "^0.2.21",
|
|
63
|
+
"@vertz/ui-compiler": "^0.2.21",
|
|
64
64
|
"magic-string": "^0.30.0",
|
|
65
65
|
"sharp": "^0.34.5",
|
|
66
66
|
"ts-morph": "^27.0.2"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
|
-
"@vertz/codegen": "^0.2.
|
|
70
|
-
"@vertz/ui-auth": "^0.2.
|
|
69
|
+
"@vertz/codegen": "^0.2.21",
|
|
70
|
+
"@vertz/ui-auth": "^0.2.19",
|
|
71
71
|
"bun-types": "^1.3.10",
|
|
72
72
|
"bunup": "^0.16.31",
|
|
73
73
|
"@happy-dom/global-registrator": "^20.8.3",
|