hadars 0.1.35 → 0.1.37
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/README.md +77 -0
- package/cli-lib.ts +90 -5
- package/dist/chunk-F7IIMM3J.js +1060 -0
- package/dist/chunk-UNQSQIOO.js +60 -0
- package/dist/cli.js +285 -261
- package/dist/hadars-B3b_6sj2.d.cts +188 -0
- package/dist/hadars-B3b_6sj2.d.ts +188 -0
- package/dist/index.cjs +11 -19
- package/dist/index.d.cts +105 -0
- package/dist/index.d.ts +4 -175
- package/dist/index.js +14 -42
- package/dist/{jsx-runtime-97ca74a5.d.ts → jsx-runtime-BOYrELJb.d.cts} +1 -1
- package/dist/jsx-runtime-BOYrELJb.d.ts +18 -0
- package/dist/lambda.cjs +1405 -0
- package/dist/lambda.d.cts +93 -0
- package/dist/lambda.d.ts +93 -0
- package/dist/lambda.js +499 -0
- package/dist/loader.cjs +22 -44
- package/dist/slim-react/index.cjs +29 -58
- package/dist/slim-react/index.d.cts +190 -0
- package/dist/slim-react/index.d.ts +3 -3
- package/dist/slim-react/index.js +34 -1043
- package/dist/slim-react/jsx-runtime.d.cts +1 -0
- package/dist/slim-react/jsx-runtime.d.ts +1 -1
- package/dist/ssr-render-worker.js +44 -78
- package/dist/ssr-watch.js +24 -7
- package/dist/utils/Head.tsx +2 -2
- package/package.json +11 -6
- package/src/build.ts +8 -166
- package/src/lambda.ts +287 -0
- package/src/types/hadars.ts +10 -0
- package/src/utils/Head.tsx +2 -2
- package/src/utils/rspack.ts +27 -0
- package/src/utils/ssrHandler.ts +185 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { b as Fragment, j as jsx, j as jsxDEV, j as jsxs } from '../jsx-runtime-BOYrELJb.cjs';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { b as Fragment, j as jsx, j as jsxDEV, j as jsxs } from '../jsx-runtime-BOYrELJb.js';
|
|
@@ -26,14 +26,12 @@ function processSegmentCache(html) {
|
|
|
26
26
|
const cacheM = /data-cache="([^"]+)"/.exec(attrs);
|
|
27
27
|
const keyM = /data-key="([^"]+)"/.exec(attrs);
|
|
28
28
|
const ttlM = /data-ttl="(\d+)"/.exec(attrs);
|
|
29
|
-
if (!cacheM || !keyM)
|
|
30
|
-
return match;
|
|
29
|
+
if (!cacheM || !keyM) return match;
|
|
31
30
|
if (cacheM[1] === "miss") {
|
|
32
31
|
setSegment(keyM[1], content, ttlM ? Number(ttlM[1]) : void 0);
|
|
33
32
|
return content;
|
|
34
33
|
}
|
|
35
|
-
if (cacheM[1] === "hit")
|
|
36
|
-
return content;
|
|
34
|
+
if (cacheM[1] === "hit") return content;
|
|
37
35
|
return match;
|
|
38
36
|
}
|
|
39
37
|
);
|
|
@@ -42,10 +40,10 @@ function processSegmentCache(html) {
|
|
|
42
40
|
}
|
|
43
41
|
|
|
44
42
|
// src/slim-react/types.ts
|
|
45
|
-
var SLIM_ELEMENT = Symbol.for("react.element");
|
|
46
|
-
var REACT19_ELEMENT = Symbol.for("react.transitional.element");
|
|
47
|
-
var FRAGMENT_TYPE = Symbol.for("react.fragment");
|
|
48
|
-
var SUSPENSE_TYPE = Symbol.for("react.suspense");
|
|
43
|
+
var SLIM_ELEMENT = /* @__PURE__ */ Symbol.for("react.element");
|
|
44
|
+
var REACT19_ELEMENT = /* @__PURE__ */ Symbol.for("react.transitional.element");
|
|
45
|
+
var FRAGMENT_TYPE = /* @__PURE__ */ Symbol.for("react.fragment");
|
|
46
|
+
var SUSPENSE_TYPE = /* @__PURE__ */ Symbol.for("react.suspense");
|
|
49
47
|
|
|
50
48
|
// src/slim-react/jsx.ts
|
|
51
49
|
function createElement(type, props, ...children) {
|
|
@@ -68,8 +66,7 @@ function createElement(type, props, ...children) {
|
|
|
68
66
|
// src/slim-react/renderContext.ts
|
|
69
67
|
var MAP_KEY = "__slimReactContextMap";
|
|
70
68
|
var _g = globalThis;
|
|
71
|
-
if (!("__slimReactContextMap" in _g))
|
|
72
|
-
_g[MAP_KEY] = null;
|
|
69
|
+
if (!("__slimReactContextMap" in _g)) _g[MAP_KEY] = null;
|
|
73
70
|
function swapContextMap(map) {
|
|
74
71
|
const prev = _g[MAP_KEY];
|
|
75
72
|
_g[MAP_KEY] = map;
|
|
@@ -80,8 +77,7 @@ function captureMap() {
|
|
|
80
77
|
}
|
|
81
78
|
function getContextValue(context) {
|
|
82
79
|
const map = _g[MAP_KEY];
|
|
83
|
-
if (map && map.has(context))
|
|
84
|
-
return map.get(context);
|
|
80
|
+
if (map && map.has(context)) return map.get(context);
|
|
85
81
|
const c = context;
|
|
86
82
|
return "_defaultValue" in c ? c._defaultValue : c._currentValue;
|
|
87
83
|
}
|
|
@@ -163,8 +159,7 @@ function restoreContext(snap) {
|
|
|
163
159
|
}
|
|
164
160
|
function getTreeId() {
|
|
165
161
|
const { id, overflow } = s().currentTreeContext;
|
|
166
|
-
if (id === 1)
|
|
167
|
-
return overflow;
|
|
162
|
+
if (id === 1) return overflow;
|
|
168
163
|
const stripped = (id & ~(1 << 31 - Math.clz32(id))).toString(32);
|
|
169
164
|
return stripped + overflow;
|
|
170
165
|
}
|
|
@@ -173,8 +168,7 @@ function makeId() {
|
|
|
173
168
|
const treeId = getTreeId();
|
|
174
169
|
const n = st.localIdCounter++;
|
|
175
170
|
let id = "_R_" + st.idPrefix + treeId;
|
|
176
|
-
if (n > 0)
|
|
177
|
-
id += "H" + n.toString(32);
|
|
171
|
+
if (n > 0) id += "H" + n.toString(32);
|
|
178
172
|
return id + "_";
|
|
179
173
|
}
|
|
180
174
|
|
|
@@ -229,10 +223,8 @@ function use(usable) {
|
|
|
229
223
|
return getContextValue(usable);
|
|
230
224
|
}
|
|
231
225
|
const promise = usable;
|
|
232
|
-
if (promise.status === "fulfilled")
|
|
233
|
-
|
|
234
|
-
if (promise.status === "rejected")
|
|
235
|
-
throw promise.reason;
|
|
226
|
+
if (promise.status === "fulfilled") return promise.value;
|
|
227
|
+
if (promise.status === "rejected") throw promise.reason;
|
|
236
228
|
throw promise;
|
|
237
229
|
}
|
|
238
230
|
|
|
@@ -266,15 +258,13 @@ var slimDispatcher = {
|
|
|
266
258
|
useHostTransitionStatus: () => false
|
|
267
259
|
};
|
|
268
260
|
function installDispatcher() {
|
|
269
|
-
if (!_internals)
|
|
270
|
-
return null;
|
|
261
|
+
if (!_internals) return null;
|
|
271
262
|
const prev = _internals.H;
|
|
272
263
|
_internals.H = slimDispatcher;
|
|
273
264
|
return prev;
|
|
274
265
|
}
|
|
275
266
|
function restoreDispatcher(prev) {
|
|
276
|
-
if (_internals)
|
|
277
|
-
_internals.H = prev;
|
|
267
|
+
if (_internals) _internals.H = prev;
|
|
278
268
|
}
|
|
279
269
|
|
|
280
270
|
// src/slim-react/render.ts
|
|
@@ -305,8 +295,7 @@ function escapeAttr(str) {
|
|
|
305
295
|
function styleObjectToString(style) {
|
|
306
296
|
let result = "";
|
|
307
297
|
for (const key in style) {
|
|
308
|
-
if (result)
|
|
309
|
-
result += ";";
|
|
298
|
+
if (result) result += ";";
|
|
310
299
|
const cssKey = key.replace(/[A-Z]/g, (m) => "-" + m.toLowerCase());
|
|
311
300
|
result += cssKey + ":" + style[key];
|
|
312
301
|
}
|
|
@@ -473,8 +462,7 @@ function renderAttributes(props, isSvg) {
|
|
|
473
462
|
}
|
|
474
463
|
if (key === "style" && typeof value === "object") {
|
|
475
464
|
const styleStr = styleObjectToString(value);
|
|
476
|
-
if (styleStr)
|
|
477
|
-
attrs += ` style="${escapeAttr(styleStr)}"`;
|
|
465
|
+
if (styleStr) attrs += ` style="${escapeAttr(styleStr)}"`;
|
|
478
466
|
continue;
|
|
479
467
|
}
|
|
480
468
|
attrs += ` ${attrName}="${escapeAttr(String(value))}"`;
|
|
@@ -493,14 +481,12 @@ var BufferWriter = class {
|
|
|
493
481
|
this.lastWasText = true;
|
|
494
482
|
}
|
|
495
483
|
flush(target) {
|
|
496
|
-
for (const c of this.chunks)
|
|
497
|
-
target.write(c);
|
|
484
|
+
for (const c of this.chunks) target.write(c);
|
|
498
485
|
target.lastWasText = this.lastWasText;
|
|
499
486
|
}
|
|
500
487
|
};
|
|
501
488
|
function renderNode(node, writer, isSvg = false) {
|
|
502
|
-
if (node == null || typeof node === "boolean")
|
|
503
|
-
return;
|
|
489
|
+
if (node == null || typeof node === "boolean") return;
|
|
504
490
|
if (typeof node === "string") {
|
|
505
491
|
writer.text(escapeHtml(node));
|
|
506
492
|
return;
|
|
@@ -521,8 +507,7 @@ function renderNode(node, writer, isSvg = false) {
|
|
|
521
507
|
}
|
|
522
508
|
if (typeof node === "object" && node !== null && "$$typeof" in node) {
|
|
523
509
|
const elType = node["$$typeof"];
|
|
524
|
-
if (elType !== SLIM_ELEMENT && elType !== REACT19_ELEMENT)
|
|
525
|
-
return;
|
|
510
|
+
if (elType !== SLIM_ELEMENT && elType !== REACT19_ELEMENT) return;
|
|
526
511
|
const element = node;
|
|
527
512
|
const { type, props } = element;
|
|
528
513
|
if (type === FRAGMENT_TYPE) {
|
|
@@ -543,17 +528,14 @@ function renderNode(node, writer, isSvg = false) {
|
|
|
543
528
|
}
|
|
544
529
|
}
|
|
545
530
|
function markSelectedOptionsMulti(children, selectedValues) {
|
|
546
|
-
if (children == null || typeof children === "boolean")
|
|
547
|
-
|
|
548
|
-
if (typeof children === "string" || typeof children === "number")
|
|
549
|
-
return children;
|
|
531
|
+
if (children == null || typeof children === "boolean") return children;
|
|
532
|
+
if (typeof children === "string" || typeof children === "number") return children;
|
|
550
533
|
if (Array.isArray(children)) {
|
|
551
534
|
return children.map((c) => markSelectedOptionsMulti(c, selectedValues));
|
|
552
535
|
}
|
|
553
536
|
if (typeof children === "object" && "$$typeof" in children) {
|
|
554
537
|
const elType = children["$$typeof"];
|
|
555
|
-
if (elType !== SLIM_ELEMENT && elType !== REACT19_ELEMENT)
|
|
556
|
-
return children;
|
|
538
|
+
if (elType !== SLIM_ELEMENT && elType !== REACT19_ELEMENT) return children;
|
|
557
539
|
const el = children;
|
|
558
540
|
if (el.type === "option") {
|
|
559
541
|
const optValue = el.props.value !== void 0 ? el.props.value : el.props.children;
|
|
@@ -574,8 +556,7 @@ function renderHostElement(tag, props, writer, isSvg) {
|
|
|
574
556
|
const textContent = props.value ?? props.defaultValue ?? props.children ?? "";
|
|
575
557
|
const filteredProps = {};
|
|
576
558
|
for (const k of Object.keys(props)) {
|
|
577
|
-
if (k !== "value" && k !== "defaultValue" && k !== "children")
|
|
578
|
-
filteredProps[k] = props[k];
|
|
559
|
+
if (k !== "value" && k !== "defaultValue" && k !== "children") filteredProps[k] = props[k];
|
|
579
560
|
}
|
|
580
561
|
writer.write(`<textarea${renderAttributes(filteredProps, false)}>`);
|
|
581
562
|
writer.text(escapeHtml(String(textContent)));
|
|
@@ -586,8 +567,7 @@ function renderHostElement(tag, props, writer, isSvg) {
|
|
|
586
567
|
const selectedValue = props.value ?? props.defaultValue;
|
|
587
568
|
const filteredProps = {};
|
|
588
569
|
for (const k of Object.keys(props)) {
|
|
589
|
-
if (k !== "value" && k !== "defaultValue")
|
|
590
|
-
filteredProps[k] = props[k];
|
|
570
|
+
if (k !== "value" && k !== "defaultValue") filteredProps[k] = props[k];
|
|
591
571
|
}
|
|
592
572
|
writer.write(`<select${renderAttributes(filteredProps, false)}>`);
|
|
593
573
|
const selectedSet = selectedValue == null ? null : Array.isArray(selectedValue) ? new Set(selectedValue.map(String)) : /* @__PURE__ */ new Set([String(selectedValue)]);
|
|
@@ -621,12 +601,12 @@ function renderHostElement(tag, props, writer, isSvg) {
|
|
|
621
601
|
}
|
|
622
602
|
writer.write(`</${tag}>`);
|
|
623
603
|
}
|
|
624
|
-
var REACT_MEMO = Symbol.for("react.memo");
|
|
625
|
-
var REACT_FORWARD_REF = Symbol.for("react.forward_ref");
|
|
626
|
-
var REACT_PROVIDER = Symbol.for("react.provider");
|
|
627
|
-
var REACT_CONTEXT = Symbol.for("react.context");
|
|
628
|
-
var REACT_CONSUMER = Symbol.for("react.consumer");
|
|
629
|
-
var REACT_LAZY = Symbol.for("react.lazy");
|
|
604
|
+
var REACT_MEMO = /* @__PURE__ */ Symbol.for("react.memo");
|
|
605
|
+
var REACT_FORWARD_REF = /* @__PURE__ */ Symbol.for("react.forward_ref");
|
|
606
|
+
var REACT_PROVIDER = /* @__PURE__ */ Symbol.for("react.provider");
|
|
607
|
+
var REACT_CONTEXT = /* @__PURE__ */ Symbol.for("react.context");
|
|
608
|
+
var REACT_CONSUMER = /* @__PURE__ */ Symbol.for("react.consumer");
|
|
609
|
+
var REACT_LAZY = /* @__PURE__ */ Symbol.for("react.lazy");
|
|
630
610
|
function renderComponent(type, props, writer, isSvg) {
|
|
631
611
|
const typeOf = type?.$$typeof;
|
|
632
612
|
if (typeOf === REACT_MEMO) {
|
|
@@ -695,8 +675,7 @@ function renderComponent(type, props, writer, isSvg) {
|
|
|
695
675
|
const instance = new type(props);
|
|
696
676
|
if (typeof type.getDerivedStateFromProps === "function") {
|
|
697
677
|
const derived = type.getDerivedStateFromProps(props, instance.state ?? {});
|
|
698
|
-
if (derived != null)
|
|
699
|
-
instance.state = { ...instance.state ?? {}, ...derived };
|
|
678
|
+
if (derived != null) instance.state = { ...instance.state ?? {}, ...derived };
|
|
700
679
|
}
|
|
701
680
|
result = instance.render();
|
|
702
681
|
} else {
|
|
@@ -705,8 +684,7 @@ function renderComponent(type, props, writer, isSvg) {
|
|
|
705
684
|
} catch (e) {
|
|
706
685
|
restoreDispatcher(prevDispatcher);
|
|
707
686
|
popComponentScope(savedScope);
|
|
708
|
-
if (isProvider)
|
|
709
|
-
popContextValue(ctx, prevCtxValue);
|
|
687
|
+
if (isProvider) popContextValue(ctx, prevCtxValue);
|
|
710
688
|
throw e;
|
|
711
689
|
}
|
|
712
690
|
restoreDispatcher(prevDispatcher);
|
|
@@ -715,11 +693,9 @@ function renderComponent(type, props, writer, isSvg) {
|
|
|
715
693
|
savedIdTree = pushTreeContext(1, 0);
|
|
716
694
|
}
|
|
717
695
|
const finish = () => {
|
|
718
|
-
if (savedIdTree !== void 0)
|
|
719
|
-
popTreeContext(savedIdTree);
|
|
696
|
+
if (savedIdTree !== void 0) popTreeContext(savedIdTree);
|
|
720
697
|
popComponentScope(savedScope);
|
|
721
|
-
if (isProvider)
|
|
722
|
-
popContextValue(ctx, prevCtxValue);
|
|
698
|
+
if (isProvider) popContextValue(ctx, prevCtxValue);
|
|
723
699
|
};
|
|
724
700
|
if (result instanceof Promise) {
|
|
725
701
|
const m = captureMap();
|
|
@@ -730,11 +706,9 @@ function renderComponent(type, props, writer, isSvg) {
|
|
|
730
706
|
asyncSavedIdTree = pushTreeContext(1, 0);
|
|
731
707
|
}
|
|
732
708
|
const asyncFinish = () => {
|
|
733
|
-
if (asyncSavedIdTree !== void 0)
|
|
734
|
-
popTreeContext(asyncSavedIdTree);
|
|
709
|
+
if (asyncSavedIdTree !== void 0) popTreeContext(asyncSavedIdTree);
|
|
735
710
|
popComponentScope(savedScope);
|
|
736
|
-
if (isProvider)
|
|
737
|
-
popContextValue(ctx, prevCtxValue);
|
|
711
|
+
if (isProvider) popContextValue(ctx, prevCtxValue);
|
|
738
712
|
};
|
|
739
713
|
const r2 = renderNode(resolved, writer, isSvg);
|
|
740
714
|
if (r2 && typeof r2.then === "function") {
|
|
@@ -817,8 +791,7 @@ function renderChildArrayFrom(children, startIndex, writer, isSvg) {
|
|
|
817
791
|
}
|
|
818
792
|
}
|
|
819
793
|
function renderChildren(children, writer, isSvg = false) {
|
|
820
|
-
if (children == null)
|
|
821
|
-
return;
|
|
794
|
+
if (children == null) return;
|
|
822
795
|
if (Array.isArray(children)) {
|
|
823
796
|
return renderChildArray(children, writer, isSvg);
|
|
824
797
|
}
|
|
@@ -914,14 +887,12 @@ async function renderToString(element, options) {
|
|
|
914
887
|
var { ssrBundlePath } = workerData;
|
|
915
888
|
var _ssrMod = null;
|
|
916
889
|
async function init() {
|
|
917
|
-
if (_ssrMod)
|
|
918
|
-
return;
|
|
890
|
+
if (_ssrMod) return;
|
|
919
891
|
_ssrMod = await import(pathToFileURL(ssrBundlePath).href);
|
|
920
892
|
}
|
|
921
893
|
function deserializeRequest(s2) {
|
|
922
894
|
const init2 = { method: s2.method, headers: new Headers(s2.headers) };
|
|
923
|
-
if (s2.body)
|
|
924
|
-
init2.body = s2.body.buffer;
|
|
895
|
+
if (s2.body) init2.body = s2.body.buffer;
|
|
925
896
|
const req = new Request(s2.url, init2);
|
|
926
897
|
Object.assign(req, { pathname: s2.pathname, search: s2.search, location: s2.location, cookies: s2.cookies });
|
|
927
898
|
return req;
|
|
@@ -940,17 +911,14 @@ function renderHeadTag(tag, id, opts, selfClose = false) {
|
|
|
940
911
|
let a = ` id="${escAttr(id)}"`;
|
|
941
912
|
let inner = "";
|
|
942
913
|
for (const [k, v] of Object.entries(opts)) {
|
|
943
|
-
if (k === "key" || k === "children")
|
|
944
|
-
continue;
|
|
914
|
+
if (k === "key" || k === "children") continue;
|
|
945
915
|
if (k === "dangerouslySetInnerHTML") {
|
|
946
916
|
inner = v.__html ?? "";
|
|
947
917
|
continue;
|
|
948
918
|
}
|
|
949
919
|
const attr = HEAD_ATTR[k] ?? k;
|
|
950
|
-
if (v === true)
|
|
951
|
-
|
|
952
|
-
else if (v !== false && v != null)
|
|
953
|
-
a += ` ${attr}="${escAttr(String(v))}"`;
|
|
920
|
+
if (v === true) a += ` ${attr}`;
|
|
921
|
+
else if (v !== false && v != null) a += ` ${attr}="${escAttr(String(v))}"`;
|
|
954
922
|
}
|
|
955
923
|
return selfClose ? `<${tag}${a}>` : `<${tag}${a}>${inner}</${tag}>`;
|
|
956
924
|
}
|
|
@@ -996,8 +964,7 @@ async function runFullLifecycle(serialReq) {
|
|
|
996
964
|
const { context: _ctx, ...restProps } = getFinalProps ? await getFinalProps(props) : props;
|
|
997
965
|
const serverData = {};
|
|
998
966
|
for (const [key, entry] of unsuspend.cache) {
|
|
999
|
-
if (entry.status === "fulfilled")
|
|
1000
|
-
serverData[key] = entry.value;
|
|
967
|
+
if (entry.status === "fulfilled") serverData[key] = entry.value;
|
|
1001
968
|
}
|
|
1002
969
|
const clientProps = {
|
|
1003
970
|
...restProps,
|
|
@@ -1020,8 +987,7 @@ parentPort.on("message", async (msg) => {
|
|
|
1020
987
|
const { id, type, request } = msg;
|
|
1021
988
|
try {
|
|
1022
989
|
await init();
|
|
1023
|
-
if (type !== "renderFull")
|
|
1024
|
-
return;
|
|
990
|
+
if (type !== "renderFull") return;
|
|
1025
991
|
const { html, headHtml, status } = await runFullLifecycle(request);
|
|
1026
992
|
parentPort.postMessage({ id, html, headHtml, status });
|
|
1027
993
|
} catch (err) {
|
package/dist/ssr-watch.js
CHANGED
|
@@ -194,8 +194,7 @@ var buildCompilerConfig = (entry2, opts, includeHotPlugin) => {
|
|
|
194
194
|
if (!isServerBuild && opts.reactMode !== void 0) {
|
|
195
195
|
const rules = localConfig.module?.rules ?? [];
|
|
196
196
|
for (const rule of rules) {
|
|
197
|
-
if (!rule?.use || !Array.isArray(rule.use))
|
|
198
|
-
continue;
|
|
197
|
+
if (!rule?.use || !Array.isArray(rule.use)) continue;
|
|
199
198
|
for (const entry3 of rule.use) {
|
|
200
199
|
if (entry3?.loader?.includes("swc-loader")) {
|
|
201
200
|
entry3.options = entry3.options ?? {};
|
|
@@ -209,6 +208,25 @@ var buildCompilerConfig = (entry2, opts, includeHotPlugin) => {
|
|
|
209
208
|
}
|
|
210
209
|
}
|
|
211
210
|
const extraPlugins = [];
|
|
211
|
+
if (!isServerBuild) {
|
|
212
|
+
extraPlugins.push({
|
|
213
|
+
apply(compiler) {
|
|
214
|
+
compiler.hooks.compilation.tap("HadarsWorkerChunkLoading", (compilation) => {
|
|
215
|
+
compilation.hooks.childCompiler.tap(
|
|
216
|
+
"HadarsWorkerChunkLoading",
|
|
217
|
+
(childCompiler) => {
|
|
218
|
+
if (childCompiler.options?.output) {
|
|
219
|
+
childCompiler.options.output.chunkLoading = "import-scripts";
|
|
220
|
+
}
|
|
221
|
+
if (childCompiler.options?.experiments) {
|
|
222
|
+
childCompiler.options.experiments.outputModule = false;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
);
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
}
|
|
212
230
|
const defineValues = { ...opts.define ?? {} };
|
|
213
231
|
if (!isServerBuild && opts.reactMode !== void 0) {
|
|
214
232
|
defineValues["process.env.NODE_ENV"] = JSON.stringify(opts.reactMode);
|
|
@@ -285,7 +303,8 @@ var buildCompilerConfig = (entry2, opts, includeHotPlugin) => {
|
|
|
285
303
|
},
|
|
286
304
|
isDev && !isServerBuild && new ReactRefreshPlugin(),
|
|
287
305
|
includeHotPlugin && isDev && !isServerBuild && new rspack.HotModuleReplacementPlugin(),
|
|
288
|
-
...extraPlugins
|
|
306
|
+
...extraPlugins,
|
|
307
|
+
...opts.plugins ?? []
|
|
289
308
|
],
|
|
290
309
|
...localConfig,
|
|
291
310
|
// Merge base resolve (modules, tsConfig, extensions) with per-build resolve
|
|
@@ -361,11 +380,9 @@ var compileEntry = async (entry2, opts) => {
|
|
|
361
380
|
var args = process.argv.slice(2);
|
|
362
381
|
var argv = {};
|
|
363
382
|
for (const a of args) {
|
|
364
|
-
if (!a.startsWith("--"))
|
|
365
|
-
continue;
|
|
383
|
+
if (!a.startsWith("--")) continue;
|
|
366
384
|
const idx = a.indexOf("=");
|
|
367
|
-
if (idx === -1)
|
|
368
|
-
continue;
|
|
385
|
+
if (idx === -1) continue;
|
|
369
386
|
const key = a.slice(2, idx);
|
|
370
387
|
const val = a.slice(idx + 1);
|
|
371
388
|
argv[key] = val;
|
package/dist/utils/Head.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import type { AppContext, AppUnsuspend, LinkProps, MetaProps, ScriptProps, StyleProps } from '../types/hadars'
|
|
2
|
+
import type { AppContext as HadarsAppContext, AppUnsuspend, LinkProps, MetaProps, ScriptProps, StyleProps } from '../types/hadars'
|
|
3
3
|
|
|
4
4
|
interface InnerContext {
|
|
5
5
|
setTitle: (title: string) => void;
|
|
@@ -31,7 +31,7 @@ const AppContext = React.createContext<InnerContext>({
|
|
|
31
31
|
|
|
32
32
|
export const AppProviderSSR: React.FC<{
|
|
33
33
|
children: React.ReactNode,
|
|
34
|
-
context:
|
|
34
|
+
context: HadarsAppContext,
|
|
35
35
|
}> = React.memo( ({ children, context }) => {
|
|
36
36
|
|
|
37
37
|
const { head } = context;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hadars",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.37",
|
|
4
4
|
"description": "Minimal SSR framework for React — rspack, HMR, TypeScript, Bun/Node/Deno",
|
|
5
5
|
"module": "./dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -19,17 +19,22 @@
|
|
|
19
19
|
],
|
|
20
20
|
"exports": {
|
|
21
21
|
".": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
22
23
|
"import": "./dist/index.js",
|
|
23
|
-
"require": "./dist/index.cjs"
|
|
24
|
-
"types": "./dist/index.d.ts"
|
|
24
|
+
"require": "./dist/index.cjs"
|
|
25
25
|
},
|
|
26
26
|
"./cli": {
|
|
27
27
|
"import": "./dist/cli.js",
|
|
28
28
|
"require": "./dist/cli.js"
|
|
29
|
+
},
|
|
30
|
+
"./lambda": {
|
|
31
|
+
"types": "./dist/lambda.d.ts",
|
|
32
|
+
"import": "./dist/lambda.js",
|
|
33
|
+
"require": "./dist/lambda.cjs"
|
|
29
34
|
}
|
|
30
35
|
},
|
|
31
36
|
"scripts": {
|
|
32
|
-
"build:lib": "tsup src/index.tsx src/slim-react/index.ts src/slim-react/jsx-runtime.ts --format esm,cjs --dts --out-dir dist --clean --external '@rspack/*' --external '@rspack/binding'",
|
|
37
|
+
"build:lib": "tsup src/index.tsx src/lambda.ts src/slim-react/index.ts src/slim-react/jsx-runtime.ts --format esm,cjs --dts --out-dir dist --clean --external '@rspack/*' --external '@rspack/binding'",
|
|
33
38
|
"build:cli": "node build-scripts/build-cli.mjs",
|
|
34
39
|
"build:all": "npm run build:lib && npm run build:cli",
|
|
35
40
|
"test": "bun test test/render-compare.test.tsx && bun test test/ssr.test.ts",
|
|
@@ -72,14 +77,14 @@
|
|
|
72
77
|
"devDependencies": {
|
|
73
78
|
"@types/react": "^19.2.14",
|
|
74
79
|
"@types/react-dom": "^19.2.3",
|
|
75
|
-
"esbuild": "^0.19.12",
|
|
76
80
|
"react": "^19.2.4",
|
|
77
81
|
"react-dom": "^19.2.4",
|
|
78
|
-
"tsup": "^
|
|
82
|
+
"tsup": "^8.3.5",
|
|
79
83
|
"typescript": "^5.9.3"
|
|
80
84
|
},
|
|
81
85
|
"dependencies": {
|
|
82
86
|
"@svgr/webpack": "^8.1.0",
|
|
87
|
+
"esbuild": "^0.27.4",
|
|
83
88
|
"postcss": "^8.5.8",
|
|
84
89
|
"postcss-load-config": "^6.0.1",
|
|
85
90
|
"postcss-loader": "^8.2.1"
|
package/src/build.ts
CHANGED
|
@@ -16,7 +16,11 @@ import os from 'node:os';
|
|
|
16
16
|
import { spawn } from 'node:child_process';
|
|
17
17
|
import cluster from 'node:cluster';
|
|
18
18
|
import type { HadarsEntryModule, HadarsOptions, HadarsProps } from "./types/hadars";
|
|
19
|
-
|
|
19
|
+
import {
|
|
20
|
+
HEAD_MARKER, BODY_MARKER,
|
|
21
|
+
buildSsrResponse, makePrecontentHtmlGetter,
|
|
22
|
+
CacheFetchHandler, createRenderCache,
|
|
23
|
+
} from './utils/ssrHandler';
|
|
20
24
|
|
|
21
25
|
/**
|
|
22
26
|
* Reads an HTML template, processes any `<style>` blocks through PostCSS
|
|
@@ -69,11 +73,6 @@ async function processHtmlTemplate(templatePath: string): Promise<string> {
|
|
|
69
73
|
return cachedPath;
|
|
70
74
|
}
|
|
71
75
|
|
|
72
|
-
const HEAD_MARKER = '<meta name="HADARS_HEAD">';
|
|
73
|
-
const BODY_MARKER = '<meta name="HADARS_BODY">';
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
76
|
// Round-robin thread pool for SSR rendering — used on Bun/Deno where
|
|
78
77
|
// node:cluster is not available but node:worker_threads is.
|
|
79
78
|
|
|
@@ -192,34 +191,6 @@ class RenderWorkerPool {
|
|
|
192
191
|
}
|
|
193
192
|
}
|
|
194
193
|
|
|
195
|
-
async function buildSsrResponse(
|
|
196
|
-
bodyHtml: string,
|
|
197
|
-
clientProps: Record<string, unknown>,
|
|
198
|
-
headHtml: string,
|
|
199
|
-
status: number,
|
|
200
|
-
getPrecontentHtml: (headHtml: string) => Promise<[string, string]>,
|
|
201
|
-
): Promise<Response> {
|
|
202
|
-
const responseStream = new ReadableStream({
|
|
203
|
-
async start(controller) {
|
|
204
|
-
const [precontentHtml, postContent] = await getPrecontentHtml(headHtml);
|
|
205
|
-
// Flush the shell (precontentHtml) immediately so the browser can
|
|
206
|
-
// start loading CSS/fonts before the body is assembled.
|
|
207
|
-
controller.enqueue(encoder.encode(precontentHtml));
|
|
208
|
-
|
|
209
|
-
const scriptContent = JSON.stringify({ hadars: { props: clientProps } }).replace(/</g, '\\u003c');
|
|
210
|
-
controller.enqueue(encoder.encode(
|
|
211
|
-
`<div id="app">${bodyHtml}</div><script id="hadars" type="application/json">${scriptContent}</script>` + postContent
|
|
212
|
-
));
|
|
213
|
-
controller.close();
|
|
214
|
-
},
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
return new Response(responseStream, {
|
|
218
|
-
headers: { 'Content-Type': 'text/html; charset=utf-8' },
|
|
219
|
-
status,
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
|
|
223
194
|
/** Serialize a HadarsRequest into a structure-clonable object for postMessage. */
|
|
224
195
|
async function serializeRequest(req: any): Promise<SerializableRequest> {
|
|
225
196
|
const isGetOrHead = ['GET', 'HEAD'].includes(req.method ?? 'GET');
|
|
@@ -238,138 +209,6 @@ async function serializeRequest(req: any): Promise<SerializableRequest> {
|
|
|
238
209
|
};
|
|
239
210
|
}
|
|
240
211
|
|
|
241
|
-
/**
|
|
242
|
-
* Returns a function that parses `out.html` into pre-head, post-head, and
|
|
243
|
-
* post-content segments and caches the result. Call the returned function with
|
|
244
|
-
* the per-request headHtml to produce the full HTML prefix and suffix.
|
|
245
|
-
*/
|
|
246
|
-
const makePrecontentHtmlGetter = (htmlFilePromise: Promise<string>) => {
|
|
247
|
-
let preHead: string | null = null;
|
|
248
|
-
let postHead: string | null = null;
|
|
249
|
-
let postContent: string | null = null;
|
|
250
|
-
return async (headHtml: string): Promise<[string, string]> => {
|
|
251
|
-
if (preHead === null || postHead === null || postContent === null) {
|
|
252
|
-
const html = await htmlFilePromise;
|
|
253
|
-
const headEnd = html.indexOf(HEAD_MARKER);
|
|
254
|
-
const contentStart = html.indexOf(BODY_MARKER);
|
|
255
|
-
preHead = html.slice(0, headEnd);
|
|
256
|
-
postHead = html.slice(headEnd + HEAD_MARKER.length, contentStart);
|
|
257
|
-
postContent = html.slice(contentStart + BODY_MARKER.length);
|
|
258
|
-
}
|
|
259
|
-
return [preHead! + headHtml + postHead!, postContent!];
|
|
260
|
-
};
|
|
261
|
-
};
|
|
262
|
-
|
|
263
|
-
// ── SSR response cache ────────────────────────────────────────────────────────
|
|
264
|
-
|
|
265
|
-
interface CacheEntry {
|
|
266
|
-
/** Gzip-compressed response body — much cheaper to keep in RAM than raw HTML. */
|
|
267
|
-
body: Uint8Array;
|
|
268
|
-
status: number;
|
|
269
|
-
/** Headers with Content-Encoding: gzip already set. */
|
|
270
|
-
headers: [string, string][];
|
|
271
|
-
expiresAt: number | null;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
type CacheFetchHandler = (req: Request, ctx: any) => Promise<Response | undefined>;
|
|
275
|
-
|
|
276
|
-
async function transformStream(data: Uint8Array, stream: { writable: WritableStream; readable: ReadableStream<Uint8Array> }): Promise<Uint8Array> {
|
|
277
|
-
const writer = stream.writable.getWriter();
|
|
278
|
-
writer.write(data);
|
|
279
|
-
writer.close();
|
|
280
|
-
const chunks: Uint8Array[] = [];
|
|
281
|
-
const reader = stream.readable.getReader();
|
|
282
|
-
while (true) {
|
|
283
|
-
const { done, value } = await reader.read();
|
|
284
|
-
if (done) break;
|
|
285
|
-
chunks.push(value);
|
|
286
|
-
}
|
|
287
|
-
const total = chunks.reduce((n, c) => n + c.length, 0);
|
|
288
|
-
const out = new Uint8Array(total);
|
|
289
|
-
let offset = 0;
|
|
290
|
-
for (const c of chunks) { out.set(c, offset); offset += c.length; }
|
|
291
|
-
return out;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
const gzipCompress = (d: Uint8Array) => transformStream(d, new (globalThis as any).CompressionStream('gzip'));
|
|
295
|
-
const gzipDecompress = (d: Uint8Array) => transformStream(d, new (globalThis as any).DecompressionStream('gzip'));
|
|
296
|
-
|
|
297
|
-
async function buildCacheEntry(res: Response, ttl: number | undefined): Promise<CacheEntry> {
|
|
298
|
-
const buf = await res.arrayBuffer();
|
|
299
|
-
const body = await gzipCompress(new Uint8Array(buf));
|
|
300
|
-
const headers: [string, string][] = [];
|
|
301
|
-
res.headers.forEach((v, k) => {
|
|
302
|
-
if (k.toLowerCase() !== 'content-encoding' && k.toLowerCase() !== 'content-length') {
|
|
303
|
-
headers.push([k, v]);
|
|
304
|
-
}
|
|
305
|
-
});
|
|
306
|
-
headers.push(['content-encoding', 'gzip']);
|
|
307
|
-
return { body, status: res.status, headers, expiresAt: ttl != null ? Date.now() + ttl : null };
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
async function serveFromEntry(entry: CacheEntry, req: Request): Promise<Response> {
|
|
311
|
-
const accept = req.headers.get('Accept-Encoding') ?? '';
|
|
312
|
-
if (accept.includes('gzip')) {
|
|
313
|
-
return new Response(entry.body.buffer as ArrayBuffer, { status: entry.status, headers: entry.headers });
|
|
314
|
-
}
|
|
315
|
-
// Client doesn't support gzip — decompress before serving
|
|
316
|
-
const plain = await gzipDecompress(entry.body);
|
|
317
|
-
const headers = entry.headers.filter(([k]) => k.toLowerCase() !== 'content-encoding');
|
|
318
|
-
return new Response(plain.buffer as ArrayBuffer, { status: entry.status, headers });
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
function createRenderCache(
|
|
322
|
-
opts: NonNullable<HadarsOptions['cache']>,
|
|
323
|
-
handler: CacheFetchHandler,
|
|
324
|
-
): CacheFetchHandler {
|
|
325
|
-
const store = new Map<string, CacheEntry>();
|
|
326
|
-
// Single-flight map: coalesces concurrent misses for the same key onto one render.
|
|
327
|
-
const inFlight = new Map<string, Promise<CacheEntry | null>>();
|
|
328
|
-
|
|
329
|
-
return async (req, ctx) => {
|
|
330
|
-
const hadarsReq = parseRequest(req);
|
|
331
|
-
const cacheOpts = await opts(hadarsReq);
|
|
332
|
-
const key = cacheOpts?.key ?? null;
|
|
333
|
-
|
|
334
|
-
if (key != null) {
|
|
335
|
-
const entry = store.get(key);
|
|
336
|
-
if (entry) {
|
|
337
|
-
const expired = entry.expiresAt != null && Date.now() >= entry.expiresAt;
|
|
338
|
-
if (!expired) {
|
|
339
|
-
return serveFromEntry(entry, req);
|
|
340
|
-
}
|
|
341
|
-
store.delete(key);
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
// Single-flight: if a render for this key is already in progress, await
|
|
345
|
-
// it instead of starting a duplicate render (thundering herd prevention).
|
|
346
|
-
let flight = inFlight.get(key);
|
|
347
|
-
if (!flight) {
|
|
348
|
-
const ttl = cacheOpts?.ttl;
|
|
349
|
-
flight = handler(new Request(req), ctx)
|
|
350
|
-
.then(async (res) => {
|
|
351
|
-
if (!res || res.status < 200 || res.status >= 300 || res.headers.has('set-cookie')) {
|
|
352
|
-
return null;
|
|
353
|
-
}
|
|
354
|
-
const newEntry = await buildCacheEntry(res, ttl);
|
|
355
|
-
store.set(key, newEntry);
|
|
356
|
-
return newEntry;
|
|
357
|
-
})
|
|
358
|
-
.catch(() => null)
|
|
359
|
-
.finally(() => inFlight.delete(key));
|
|
360
|
-
inFlight.set(key, flight);
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
const newEntry = await flight;
|
|
364
|
-
if (newEntry) return serveFromEntry(newEntry, req);
|
|
365
|
-
// Render was uncacheable (error, Set-Cookie, etc.) — fall through to a
|
|
366
|
-
// fresh independent render for this request so it still gets a response.
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
return handler(req, ctx);
|
|
370
|
-
};
|
|
371
|
-
}
|
|
372
|
-
|
|
373
212
|
interface HadarsRuntimeOptions extends HadarsOptions {
|
|
374
213
|
mode: "development" | "production";
|
|
375
214
|
}
|
|
@@ -502,6 +341,7 @@ export const dev = async (options: HadarsRuntimeOptions) => {
|
|
|
502
341
|
swcPlugins: options.swcPlugins,
|
|
503
342
|
define: options.define,
|
|
504
343
|
moduleRules: options.moduleRules,
|
|
344
|
+
plugins: options.plugins,
|
|
505
345
|
reactMode: options.reactMode,
|
|
506
346
|
htmlTemplate: resolvedHtmlTemplate,
|
|
507
347
|
});
|
|
@@ -757,6 +597,7 @@ export const build = async (options: HadarsRuntimeOptions) => {
|
|
|
757
597
|
swcPlugins: options.swcPlugins,
|
|
758
598
|
define: options.define,
|
|
759
599
|
moduleRules: options.moduleRules,
|
|
600
|
+
plugins: options.plugins,
|
|
760
601
|
optimization: options.optimization,
|
|
761
602
|
reactMode: options.reactMode,
|
|
762
603
|
htmlTemplate: resolvedHtmlTemplate,
|
|
@@ -775,6 +616,7 @@ export const build = async (options: HadarsRuntimeOptions) => {
|
|
|
775
616
|
swcPlugins: options.swcPlugins,
|
|
776
617
|
define: options.define,
|
|
777
618
|
moduleRules: options.moduleRules,
|
|
619
|
+
plugins: options.plugins,
|
|
778
620
|
}),
|
|
779
621
|
]);
|
|
780
622
|
await fs.rm(tmpFilePath);
|