hadars 0.1.40 → 0.2.1

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.
Files changed (42) hide show
  1. package/README.md +85 -70
  2. package/cli-lib.ts +89 -12
  3. package/dist/chunk-HWOLYLPF.js +332 -0
  4. package/dist/{chunk-2ENP7IAW.js → chunk-LY5MTHFV.js} +360 -203
  5. package/dist/cli.js +506 -274
  6. package/dist/cloudflare.cjs +1394 -0
  7. package/dist/cloudflare.d.cts +64 -0
  8. package/dist/cloudflare.d.ts +64 -0
  9. package/dist/cloudflare.js +68 -0
  10. package/dist/{hadars-Bh-V5YXg.d.cts → hadars-DEBSYAQl.d.cts} +1 -36
  11. package/dist/{hadars-Bh-V5YXg.d.ts → hadars-DEBSYAQl.d.ts} +1 -36
  12. package/dist/index.cjs +129 -156
  13. package/dist/index.d.cts +5 -11
  14. package/dist/index.d.ts +5 -11
  15. package/dist/index.js +129 -155
  16. package/dist/lambda.cjs +391 -229
  17. package/dist/lambda.d.cts +1 -2
  18. package/dist/lambda.d.ts +1 -2
  19. package/dist/lambda.js +18 -307
  20. package/dist/slim-react/index.cjs +361 -203
  21. package/dist/slim-react/index.d.cts +24 -8
  22. package/dist/slim-react/index.d.ts +24 -8
  23. package/dist/slim-react/index.js +3 -1
  24. package/dist/ssr-render-worker.js +352 -221
  25. package/dist/utils/Head.tsx +132 -187
  26. package/package.json +7 -2
  27. package/src/build.ts +7 -6
  28. package/src/cloudflare.ts +139 -0
  29. package/src/index.tsx +0 -3
  30. package/src/lambda.ts +6 -2
  31. package/src/slim-react/context.ts +2 -1
  32. package/src/slim-react/index.ts +21 -18
  33. package/src/slim-react/render.ts +379 -240
  34. package/src/slim-react/renderContext.ts +105 -45
  35. package/src/ssr-render-worker.ts +14 -44
  36. package/src/types/hadars.ts +0 -1
  37. package/src/utils/Head.tsx +132 -187
  38. package/src/utils/cookies.ts +1 -1
  39. package/src/utils/response.tsx +68 -33
  40. package/src/utils/serve.ts +29 -27
  41. package/src/utils/ssrHandler.ts +54 -25
  42. package/src/utils/staticFile.ts +2 -7
package/dist/cli.js CHANGED
@@ -107,7 +107,11 @@ var parseCookies = (cookieString) => {
107
107
  if (index > -1) {
108
108
  const key = pair.slice(0, index).trim();
109
109
  const value = pair.slice(index + 1).trim();
110
- cookies[key] = decodeURIComponent(value);
110
+ try {
111
+ cookies[key] = decodeURIComponent(value);
112
+ } catch {
113
+ cookies[key] = value;
114
+ }
111
115
  }
112
116
  }
113
117
  return cookies;
@@ -171,6 +175,13 @@ function swapContextMap(map) {
171
175
  function captureMap() {
172
176
  return _g[MAP_KEY];
173
177
  }
178
+ var UNSUSPEND_KEY = "__hadarsUnsuspend";
179
+ function captureUnsuspend() {
180
+ return _g[UNSUSPEND_KEY];
181
+ }
182
+ function restoreUnsuspend(u) {
183
+ _g[UNSUSPEND_KEY] = u;
184
+ }
174
185
  function getContextValue(context) {
175
186
  const map = _g[MAP_KEY];
176
187
  if (map && map.has(context)) return map.get(context);
@@ -178,10 +189,14 @@ function getContextValue(context) {
178
189
  return "_defaultValue" in c ? c._defaultValue : c._currentValue;
179
190
  }
180
191
  function pushContextValue(context, value) {
181
- const map = _g[MAP_KEY];
192
+ let map = _g[MAP_KEY];
193
+ if (map === null) {
194
+ map = /* @__PURE__ */ new Map();
195
+ _g[MAP_KEY] = map;
196
+ }
182
197
  const c = context;
183
- const prev = map && map.has(context) ? map.get(context) : "_defaultValue" in c ? c._defaultValue : c._currentValue;
184
- map?.set(context, value);
198
+ const prev = map.has(context) ? map.get(context) : "_defaultValue" in c ? c._defaultValue : c._currentValue;
199
+ map.set(context, value);
185
200
  return prev;
186
201
  }
187
202
  function popContextValue(context, prev) {
@@ -189,48 +204,57 @@ function popContextValue(context, prev) {
189
204
  }
190
205
  var GLOBAL_KEY = "__slimReactRenderState";
191
206
  var EMPTY = { id: 1, overflow: "" };
207
+ var _stateCache = null;
192
208
  function s() {
193
- const g = globalThis;
194
- if (!g[GLOBAL_KEY]) {
195
- g[GLOBAL_KEY] = { currentTreeContext: { ...EMPTY }, localIdCounter: 0, idPrefix: "" };
209
+ if (_stateCache !== null) return _stateCache;
210
+ if (!_g[GLOBAL_KEY]) {
211
+ _g[GLOBAL_KEY] = { currentTreeContext: { ...EMPTY }, localIdCounter: 0, idPrefix: "" };
196
212
  }
197
- return g[GLOBAL_KEY];
213
+ _stateCache = _g[GLOBAL_KEY];
214
+ return _stateCache;
198
215
  }
216
+ var _treeIdStack = [];
217
+ var _treeOvStack = [];
218
+ var _treeDepth = 0;
199
219
  function resetRenderState(idPrefix = "") {
200
220
  const st = s();
201
- st.currentTreeContext = { ...EMPTY };
221
+ st.currentTreeContext.id = EMPTY.id;
222
+ st.currentTreeContext.overflow = EMPTY.overflow;
202
223
  st.localIdCounter = 0;
203
224
  st.idPrefix = idPrefix;
225
+ _treeDepth = 0;
204
226
  }
205
227
  function pushTreeContext(totalChildren, index) {
206
228
  const st = s();
207
- const saved = { ...st.currentTreeContext };
208
- const baseIdWithLeadingBit = st.currentTreeContext.id;
209
- const baseOverflow = st.currentTreeContext.overflow;
229
+ const ctx = st.currentTreeContext;
230
+ const depth = _treeDepth++;
231
+ _treeIdStack[depth] = ctx.id;
232
+ _treeOvStack[depth] = ctx.overflow;
233
+ const baseIdWithLeadingBit = ctx.id;
234
+ const baseOverflow = ctx.overflow;
210
235
  const baseLength = 31 - Math.clz32(baseIdWithLeadingBit);
211
236
  let baseId = baseIdWithLeadingBit & ~(1 << baseLength);
212
237
  const slot = index + 1;
213
238
  const newBits = 32 - Math.clz32(totalChildren);
214
239
  const length = newBits + baseLength;
215
240
  if (30 < length) {
216
- const numberOfOverflowBits = baseLength - baseLength % 5;
217
- const overflowStr = (baseId & (1 << numberOfOverflowBits) - 1).toString(32);
218
- baseId >>= numberOfOverflowBits;
219
- const newBaseLength = baseLength - numberOfOverflowBits;
220
- st.currentTreeContext = {
221
- id: 1 << newBits + newBaseLength | slot << newBaseLength | baseId,
222
- overflow: overflowStr + baseOverflow
223
- };
241
+ const overflowBits = baseLength - baseLength % 5;
242
+ const overflowStr = (baseId & (1 << overflowBits) - 1).toString(32);
243
+ baseId >>= overflowBits;
244
+ const newBaseLength = baseLength - overflowBits;
245
+ ctx.id = 1 << newBits + newBaseLength | slot << newBaseLength | baseId;
246
+ ctx.overflow = overflowStr + baseOverflow;
224
247
  } else {
225
- st.currentTreeContext = {
226
- id: 1 << length | slot << baseLength | baseId,
227
- overflow: baseOverflow
228
- };
248
+ ctx.id = 1 << length | slot << baseLength | baseId;
249
+ ctx.overflow = baseOverflow;
229
250
  }
230
- return saved;
251
+ return depth;
231
252
  }
232
- function popTreeContext(saved) {
233
- s().currentTreeContext = saved;
253
+ function popTreeContext(depth) {
254
+ const ctx = s().currentTreeContext;
255
+ ctx.id = _treeIdStack[depth];
256
+ ctx.overflow = _treeOvStack[depth];
257
+ _treeDepth = depth;
234
258
  }
235
259
  function pushComponentScope() {
236
260
  const st = s();
@@ -246,12 +270,16 @@ function componentCalledUseId() {
246
270
  }
247
271
  function snapshotContext() {
248
272
  const st = s();
249
- return { tree: { ...st.currentTreeContext }, localId: st.localIdCounter };
273
+ const ctx = st.currentTreeContext;
274
+ return { tree: { id: ctx.id, overflow: ctx.overflow }, localId: st.localIdCounter, treeDepth: _treeDepth };
250
275
  }
251
276
  function restoreContext(snap) {
252
277
  const st = s();
253
- st.currentTreeContext = { ...snap.tree };
278
+ const ctx = st.currentTreeContext;
279
+ ctx.id = snap.tree.id;
280
+ ctx.overflow = snap.tree.overflow;
254
281
  st.localIdCounter = snap.localId;
282
+ _treeDepth = snap.treeDepth;
255
283
  }
256
284
  function getTreeId() {
257
285
  const { id, overflow } = s().currentTreeContext;
@@ -382,19 +410,80 @@ var VOID_ELEMENTS = /* @__PURE__ */ new Set([
382
410
  "wbr"
383
411
  ]);
384
412
  var HTML_ESC = { "&": "&amp;", "<": "&lt;", ">": "&gt;", "'": "&#x27;" };
413
+ var HTML_ESC_RE = /[&<>']/;
385
414
  function escapeHtml(str) {
415
+ if (!HTML_ESC_RE.test(str)) return str;
386
416
  return str.replace(/[&<>']/g, (c) => HTML_ESC[c]);
387
417
  }
388
418
  var ATTR_ESC = { "&": "&amp;", '"': "&quot;", "<": "&lt;", ">": "&gt;" };
419
+ var ATTR_ESC_RE = /[&"<>]/;
389
420
  function escapeAttr(str) {
421
+ if (!ATTR_ESC_RE.test(str)) return str;
390
422
  return str.replace(/[&"<>]/g, (c) => ATTR_ESC[c]);
391
423
  }
424
+ var UNITLESS_CSS = /* @__PURE__ */ new Set([
425
+ "animationIterationCount",
426
+ "aspectRatio",
427
+ "borderImageOutset",
428
+ "borderImageSlice",
429
+ "borderImageWidth",
430
+ "boxFlex",
431
+ "boxFlexGroup",
432
+ "boxOrdinalGroup",
433
+ "columnCount",
434
+ "columns",
435
+ "flex",
436
+ "flexGrow",
437
+ "flexPositive",
438
+ "flexShrink",
439
+ "flexNegative",
440
+ "flexOrder",
441
+ "gridArea",
442
+ "gridRow",
443
+ "gridRowEnd",
444
+ "gridRowSpan",
445
+ "gridRowStart",
446
+ "gridColumn",
447
+ "gridColumnEnd",
448
+ "gridColumnSpan",
449
+ "gridColumnStart",
450
+ "fontWeight",
451
+ "lineClamp",
452
+ "lineHeight",
453
+ "opacity",
454
+ "order",
455
+ "orphans",
456
+ "scale",
457
+ "tabSize",
458
+ "widows",
459
+ "zIndex",
460
+ "zoom",
461
+ "fillOpacity",
462
+ "floodOpacity",
463
+ "stopOpacity",
464
+ "strokeDasharray",
465
+ "strokeDashoffset",
466
+ "strokeMiterlimit",
467
+ "strokeOpacity",
468
+ "strokeWidth"
469
+ ]);
470
+ var _cssKeyCache = /* @__PURE__ */ new Map();
392
471
  function styleObjectToString(style) {
393
472
  let result = "";
394
473
  for (const key in style) {
474
+ const value = style[key];
475
+ if (value == null || typeof value === "boolean") continue;
395
476
  if (result) result += ";";
396
- const cssKey = key.replace(/[A-Z]/g, (m) => "-" + m.toLowerCase());
397
- result += cssKey + ":" + style[key];
477
+ let cssKey = _cssKeyCache.get(key);
478
+ if (cssKey === void 0) {
479
+ cssKey = key.replace(/[A-Z]/g, (m) => "-" + m.toLowerCase());
480
+ _cssKeyCache.set(key, cssKey);
481
+ }
482
+ if (typeof value === "number" && value !== 0 && !UNITLESS_CSS.has(key)) {
483
+ result += cssKey + ":" + value + "px";
484
+ } else {
485
+ result += cssKey + ":" + value;
486
+ }
398
487
  }
399
488
  return result;
400
489
  }
@@ -529,14 +618,22 @@ var SVG_ATTR_MAP = {
529
618
  xChannelSelector: "xChannelSelector",
530
619
  yChannelSelector: "yChannelSelector"
531
620
  };
532
- function renderAttributes(props, isSvg) {
533
- let attrs = "";
621
+ var TEXTAREA_SKIP_PROPS = /* @__PURE__ */ new Set(["value", "defaultValue", "children"]);
622
+ var SELECT_SKIP_PROPS = /* @__PURE__ */ new Set(["value", "defaultValue"]);
623
+ var INTERNAL_PROPS = /* @__PURE__ */ new Set([
624
+ "children",
625
+ "key",
626
+ "ref",
627
+ "dangerouslySetInnerHTML",
628
+ "suppressHydrationWarning",
629
+ "suppressContentEditableWarning"
630
+ ]);
631
+ function writeAttributes(writer, props, isSvg, skip) {
534
632
  for (const key in props) {
633
+ if (skip !== void 0 && skip.has(key)) continue;
535
634
  const value = props[key];
536
- if (key === "children" || key === "key" || key === "ref" || key === "dangerouslySetInnerHTML" || key === "suppressHydrationWarning" || key === "suppressContentEditableWarning")
537
- continue;
538
- if (key.startsWith("on") && key.length > 2 && key[2] === key[2].toUpperCase())
539
- continue;
635
+ if (INTERNAL_PROPS.has(key)) continue;
636
+ if (key.length > 2 && key.charCodeAt(0) === 111 && key.charCodeAt(1) === 110 && key.charCodeAt(2) >= 65 && key.charCodeAt(2) <= 90) continue;
540
637
  let attrName;
541
638
  if (isSvg && key in SVG_ATTR_MAP) {
542
639
  attrName = SVG_ATTR_MAP[key];
@@ -544,41 +641,46 @@ function renderAttributes(props, isSvg) {
544
641
  attrName = key === "className" ? "class" : key === "htmlFor" ? "for" : key === "tabIndex" ? "tabindex" : key === "defaultValue" ? "value" : key === "defaultChecked" ? "checked" : key;
545
642
  }
546
643
  if (value === false || value == null) {
547
- if (value === false && (attrName.startsWith("aria-") || attrName.startsWith("data-"))) {
548
- attrs += ` ${attrName}="false"`;
644
+ if (value === false && (attrName.charCodeAt(0) === 97 && attrName.startsWith("aria-") || attrName.charCodeAt(0) === 100 && attrName.startsWith("data-"))) {
645
+ writer.write(` ${attrName}="false"`);
549
646
  }
550
647
  continue;
551
648
  }
552
649
  if (value === true) {
553
- if (attrName.startsWith("aria-") || attrName.startsWith("data-")) {
554
- attrs += ` ${attrName}="true"`;
650
+ if (attrName.charCodeAt(0) === 97 && attrName.startsWith("aria-") || attrName.charCodeAt(0) === 100 && attrName.startsWith("data-")) {
651
+ writer.write(` ${attrName}="true"`);
555
652
  } else {
556
- attrs += ` ${attrName}=""`;
653
+ writer.write(` ${attrName}=""`);
557
654
  }
558
655
  continue;
559
656
  }
560
657
  if (key === "style" && typeof value === "object") {
561
658
  const styleStr = styleObjectToString(value);
562
- if (styleStr) attrs += ` style="${escapeAttr(styleStr)}"`;
659
+ if (styleStr) writer.write(` style="${escapeAttr(styleStr)}"`);
563
660
  continue;
564
661
  }
565
- attrs += ` ${attrName}="${escapeAttr(String(value))}"`;
662
+ writer.write(` ${attrName}="${escapeAttr(typeof value === "string" ? value : String(value))}"`);
566
663
  }
567
- return attrs;
568
664
  }
569
- var BufferWriter = class {
570
- chunks = [];
665
+ var BufferWriter = class _BufferWriter {
666
+ data = "";
571
667
  lastWasText = false;
572
668
  write(chunk) {
573
- this.chunks.push(chunk);
669
+ this.data += chunk;
574
670
  this.lastWasText = false;
575
671
  }
576
672
  text(s2) {
577
- this.chunks.push(s2);
673
+ this.data += s2;
578
674
  this.lastWasText = true;
579
675
  }
580
- flush(target) {
581
- for (const c of this.chunks) target.write(c);
676
+ /** Flush accumulated output into a parent writer and reset. */
677
+ flushTo(target) {
678
+ if (!this.data) return;
679
+ if (target instanceof _BufferWriter) {
680
+ target.data += this.data;
681
+ } else {
682
+ target.write(this.data);
683
+ }
582
684
  target.lastWasText = this.lastWasText;
583
685
  }
584
686
  };
@@ -595,15 +697,12 @@ function renderNode(node, writer, isSvg = false) {
595
697
  if (Array.isArray(node)) {
596
698
  return renderChildArray(node, writer, isSvg);
597
699
  }
598
- if (typeof node === "object" && node !== null && Symbol.iterator in node && !("$$typeof" in node)) {
599
- return renderChildArray(
600
- Array.from(node),
601
- writer,
602
- isSvg
603
- );
700
+ const obj = node;
701
+ if (Symbol.iterator in obj && !("$$typeof" in obj)) {
702
+ return renderChildArray(Array.from(obj), writer, isSvg);
604
703
  }
605
- if (typeof node === "object" && node !== null && "$$typeof" in node) {
606
- const elType = node["$$typeof"];
704
+ if ("$$typeof" in obj) {
705
+ const elType = obj["$$typeof"];
607
706
  if (elType !== SLIM_ELEMENT && elType !== REACT19_ELEMENT) return;
608
707
  const element = node;
609
708
  const { type, props } = element;
@@ -613,15 +712,15 @@ function renderNode(node, writer, isSvg = false) {
613
712
  if (type === SUSPENSE_TYPE) {
614
713
  return renderSuspense(props, writer, isSvg);
615
714
  }
715
+ if (typeof type === "string") {
716
+ return renderHostElement(type, props, writer, isSvg);
717
+ }
616
718
  if (typeof type === "function") {
617
719
  return renderComponent(type, props, writer, isSvg);
618
720
  }
619
721
  if (typeof type === "object" && type !== null) {
620
722
  return renderComponent(type, props, writer, isSvg);
621
723
  }
622
- if (typeof type === "string") {
623
- return renderHostElement(type, props, writer, isSvg);
624
- }
625
724
  }
626
725
  }
627
726
  function markSelectedOptionsMulti(children, selectedValues) {
@@ -647,26 +746,21 @@ function markSelectedOptionsMulti(children, selectedValues) {
647
746
  return children;
648
747
  }
649
748
  function renderHostElement(tag, props, writer, isSvg) {
650
- const enteringSvg = tag === "svg";
651
- const childSvg = isSvg || enteringSvg;
749
+ const childSvg = isSvg || tag === "svg";
652
750
  if (tag === "textarea") {
653
751
  const textContent = props.value ?? props.defaultValue ?? props.children ?? "";
654
- const filteredProps = {};
655
- for (const k of Object.keys(props)) {
656
- if (k !== "value" && k !== "defaultValue" && k !== "children") filteredProps[k] = props[k];
657
- }
658
- writer.write(`<textarea${renderAttributes(filteredProps, false)}>`);
752
+ writer.write("<textarea");
753
+ writeAttributes(writer, props, false, TEXTAREA_SKIP_PROPS);
754
+ writer.write(">");
659
755
  writer.text(escapeHtml(String(textContent)));
660
756
  writer.write("</textarea>");
661
757
  return;
662
758
  }
663
759
  if (tag === "select") {
664
760
  const selectedValue = props.value ?? props.defaultValue;
665
- const filteredProps = {};
666
- for (const k of Object.keys(props)) {
667
- if (k !== "value" && k !== "defaultValue") filteredProps[k] = props[k];
668
- }
669
- writer.write(`<select${renderAttributes(filteredProps, false)}>`);
761
+ writer.write("<select");
762
+ writeAttributes(writer, props, false, SELECT_SKIP_PROPS);
763
+ writer.write(">");
670
764
  const selectedSet = selectedValue == null ? null : Array.isArray(selectedValue) ? new Set(selectedValue.map(String)) : /* @__PURE__ */ new Set([String(selectedValue)]);
671
765
  const patchedChildren = selectedSet != null ? markSelectedOptionsMulti(props.children, selectedSet) : props.children;
672
766
  const inner2 = renderChildren(patchedChildren, writer, false);
@@ -678,7 +772,8 @@ function renderHostElement(tag, props, writer, isSvg) {
678
772
  writer.write("</select>");
679
773
  return;
680
774
  }
681
- writer.write(`<${tag}${renderAttributes(props, childSvg)}`);
775
+ writer.write(`<${tag}`);
776
+ writeAttributes(writer, props, childSvg);
682
777
  if (VOID_ELEMENTS.has(tag)) {
683
778
  writer.write("/>");
684
779
  return;
@@ -704,8 +799,25 @@ var REACT_PROVIDER = /* @__PURE__ */ Symbol.for("react.provider");
704
799
  var REACT_CONTEXT = /* @__PURE__ */ Symbol.for("react.context");
705
800
  var REACT_CONSUMER = /* @__PURE__ */ Symbol.for("react.consumer");
706
801
  var REACT_LAZY = /* @__PURE__ */ Symbol.for("react.lazy");
707
- function renderComponent(type, props, writer, isSvg) {
708
- const typeOf = type?.$$typeof;
802
+ var SUSPENSE_RETRY_LIMIT = /* @__PURE__ */ Symbol("SuspenseRetryLimit");
803
+ var MAX_COMPONENT_SUSPENSE_RETRIES = 25;
804
+ function patchPromiseStatus(p) {
805
+ const w = p;
806
+ if (w.status) return;
807
+ w.status = "pending";
808
+ w.then(
809
+ (v) => {
810
+ w.status = "fulfilled";
811
+ w.value = v;
812
+ },
813
+ (r) => {
814
+ w.status = "rejected";
815
+ w.reason = r;
816
+ }
817
+ );
818
+ }
819
+ function renderComponent(type, props, writer, isSvg, _suspenseRetries = 0) {
820
+ const typeOf = type.$$typeof;
709
821
  if (typeOf === REACT_MEMO) {
710
822
  return renderNode(
711
823
  { $$typeof: SLIM_ELEMENT, type: type.type, props, key: null },
@@ -717,7 +829,23 @@ function renderComponent(type, props, writer, isSvg) {
717
829
  return renderComponent(type.render, props, writer, isSvg);
718
830
  }
719
831
  if (typeOf === REACT_LAZY) {
720
- const resolved = type._init(type._payload);
832
+ let resolved;
833
+ try {
834
+ resolved = type._init(type._payload);
835
+ } catch (e) {
836
+ if (e && typeof e.then === "function") {
837
+ if (_suspenseRetries + 1 >= MAX_COMPONENT_SUSPENSE_RETRIES) throw SUSPENSE_RETRY_LIMIT;
838
+ patchPromiseStatus(e);
839
+ const m = captureMap();
840
+ const u = captureUnsuspend();
841
+ return e.then(() => {
842
+ swapContextMap(m);
843
+ restoreUnsuspend(u);
844
+ return renderComponent(type, props, writer, isSvg, _suspenseRetries + 1);
845
+ });
846
+ }
847
+ throw e;
848
+ }
721
849
  const LazyComp = resolved?.default ?? resolved;
722
850
  return renderComponent(LazyComp, props, writer, isSvg);
723
851
  }
@@ -726,12 +854,12 @@ function renderComponent(type, props, writer, isSvg) {
726
854
  const value = ctx2 ? getContextValue(ctx2) : void 0;
727
855
  const result2 = typeof props.children === "function" ? props.children(value) : null;
728
856
  const savedScope2 = pushComponentScope();
729
- const finish2 = () => popComponentScope(savedScope2);
857
+ const finish = () => popComponentScope(savedScope2);
730
858
  const r2 = renderNode(result2, writer, isSvg);
731
859
  if (r2 && typeof r2.then === "function") {
732
- return r2.then(finish2);
860
+ return r2.then(finish);
733
861
  }
734
- finish2();
862
+ finish();
735
863
  return;
736
864
  }
737
865
  const isProvider = "_context" in type || typeOf === REACT_PROVIDER || typeOf === REACT_CONTEXT && "value" in props;
@@ -743,26 +871,29 @@ function renderComponent(type, props, writer, isSvg) {
743
871
  }
744
872
  const savedScope = pushComponentScope();
745
873
  if (isProvider && typeof type !== "function") {
746
- const finish2 = () => {
874
+ const finish = () => {
747
875
  popComponentScope(savedScope);
748
876
  popContextValue(ctx, prevCtxValue);
749
877
  };
750
878
  const r2 = renderChildren(props.children, writer, isSvg);
751
879
  if (r2 && typeof r2.then === "function") {
752
880
  const m = captureMap();
881
+ const u = captureUnsuspend();
753
882
  return r2.then(
754
883
  () => {
755
884
  swapContextMap(m);
756
- finish2();
885
+ restoreUnsuspend(u);
886
+ finish();
757
887
  },
758
888
  (e) => {
759
889
  swapContextMap(m);
760
- finish2();
890
+ restoreUnsuspend(u);
891
+ finish();
761
892
  throw e;
762
893
  }
763
894
  );
764
895
  }
765
- finish2();
896
+ finish();
766
897
  return;
767
898
  }
768
899
  let result;
@@ -782,6 +913,17 @@ function renderComponent(type, props, writer, isSvg) {
782
913
  restoreDispatcher(prevDispatcher);
783
914
  popComponentScope(savedScope);
784
915
  if (isProvider) popContextValue(ctx, prevCtxValue);
916
+ if (e && typeof e.then === "function") {
917
+ if (_suspenseRetries + 1 >= MAX_COMPONENT_SUSPENSE_RETRIES) throw SUSPENSE_RETRY_LIMIT;
918
+ patchPromiseStatus(e);
919
+ const m = captureMap();
920
+ const u = captureUnsuspend();
921
+ return e.then(() => {
922
+ swapContextMap(m);
923
+ restoreUnsuspend(u);
924
+ return renderComponent(type, props, writer, isSvg, _suspenseRetries + 1);
925
+ });
926
+ }
785
927
  throw e;
786
928
  }
787
929
  restoreDispatcher(prevDispatcher);
@@ -789,97 +931,93 @@ function renderComponent(type, props, writer, isSvg) {
789
931
  if (!(result instanceof Promise) && componentCalledUseId()) {
790
932
  savedIdTree = pushTreeContext(1, 0);
791
933
  }
792
- const finish = () => {
793
- if (savedIdTree !== void 0) popTreeContext(savedIdTree);
794
- popComponentScope(savedScope);
795
- if (isProvider) popContextValue(ctx, prevCtxValue);
796
- };
797
934
  if (result instanceof Promise) {
798
935
  const m = captureMap();
936
+ const u = captureUnsuspend();
799
937
  return result.then((resolved) => {
800
938
  swapContextMap(m);
939
+ restoreUnsuspend(u);
801
940
  let asyncSavedIdTree;
802
941
  if (componentCalledUseId()) {
803
942
  asyncSavedIdTree = pushTreeContext(1, 0);
804
943
  }
805
- const asyncFinish = () => {
806
- if (asyncSavedIdTree !== void 0) popTreeContext(asyncSavedIdTree);
807
- popComponentScope(savedScope);
808
- if (isProvider) popContextValue(ctx, prevCtxValue);
809
- };
810
944
  const r2 = renderNode(resolved, writer, isSvg);
811
945
  if (r2 && typeof r2.then === "function") {
812
946
  const m2 = captureMap();
947
+ const u2 = captureUnsuspend();
813
948
  return r2.then(
814
949
  () => {
815
950
  swapContextMap(m2);
816
- asyncFinish();
951
+ restoreUnsuspend(u2);
952
+ if (asyncSavedIdTree !== void 0) popTreeContext(asyncSavedIdTree);
953
+ popComponentScope(savedScope);
954
+ if (isProvider) popContextValue(ctx, prevCtxValue);
817
955
  },
818
956
  (e) => {
819
957
  swapContextMap(m2);
820
- asyncFinish();
958
+ restoreUnsuspend(u2);
959
+ if (asyncSavedIdTree !== void 0) popTreeContext(asyncSavedIdTree);
960
+ popComponentScope(savedScope);
961
+ if (isProvider) popContextValue(ctx, prevCtxValue);
821
962
  throw e;
822
963
  }
823
964
  );
824
965
  }
825
- asyncFinish();
966
+ if (asyncSavedIdTree !== void 0) popTreeContext(asyncSavedIdTree);
967
+ popComponentScope(savedScope);
968
+ if (isProvider) popContextValue(ctx, prevCtxValue);
826
969
  }, (e) => {
827
970
  swapContextMap(m);
828
- finish();
971
+ restoreUnsuspend(u);
972
+ popComponentScope(savedScope);
973
+ if (isProvider) popContextValue(ctx, prevCtxValue);
829
974
  throw e;
830
975
  });
831
976
  }
832
977
  const r = renderNode(result, writer, isSvg);
833
978
  if (r && typeof r.then === "function") {
834
979
  const m = captureMap();
980
+ const u = captureUnsuspend();
835
981
  return r.then(
836
982
  () => {
837
983
  swapContextMap(m);
838
- finish();
984
+ restoreUnsuspend(u);
985
+ if (savedIdTree !== void 0) popTreeContext(savedIdTree);
986
+ popComponentScope(savedScope);
987
+ if (isProvider) popContextValue(ctx, prevCtxValue);
839
988
  },
840
989
  (e) => {
841
990
  swapContextMap(m);
842
- finish();
991
+ restoreUnsuspend(u);
992
+ if (savedIdTree !== void 0) popTreeContext(savedIdTree);
993
+ popComponentScope(savedScope);
994
+ if (isProvider) popContextValue(ctx, prevCtxValue);
843
995
  throw e;
844
996
  }
845
997
  );
846
998
  }
847
- finish();
848
- }
849
- function isTextLike(node) {
850
- return typeof node === "string" || typeof node === "number";
999
+ if (savedIdTree !== void 0) popTreeContext(savedIdTree);
1000
+ popComponentScope(savedScope);
1001
+ if (isProvider) popContextValue(ctx, prevCtxValue);
851
1002
  }
852
1003
  function renderChildArray(children, writer, isSvg) {
853
- const totalChildren = children.length;
854
- for (let i = 0; i < totalChildren; i++) {
855
- if (isTextLike(children[i]) && writer.lastWasText) {
856
- writer.write("<!-- -->");
857
- }
858
- const savedTree = pushTreeContext(totalChildren, i);
859
- const r = renderNode(children[i], writer, isSvg);
860
- if (r && typeof r.then === "function") {
861
- const m = captureMap();
862
- return r.then(() => {
863
- swapContextMap(m);
864
- popTreeContext(savedTree);
865
- return renderChildArrayFrom(children, i + 1, writer, isSvg);
866
- });
867
- }
868
- popTreeContext(savedTree);
869
- }
1004
+ return renderChildArrayFrom(children, 0, writer, isSvg);
870
1005
  }
871
1006
  function renderChildArrayFrom(children, startIndex, writer, isSvg) {
872
1007
  const totalChildren = children.length;
873
1008
  for (let i = startIndex; i < totalChildren; i++) {
874
- if (isTextLike(children[i]) && writer.lastWasText) {
1009
+ const child = children[i];
1010
+ if ((typeof child === "string" || typeof child === "number") && writer.lastWasText) {
875
1011
  writer.write("<!-- -->");
876
1012
  }
877
1013
  const savedTree = pushTreeContext(totalChildren, i);
878
- const r = renderNode(children[i], writer, isSvg);
1014
+ const r = renderNode(child, writer, isSvg);
879
1015
  if (r && typeof r.then === "function") {
880
1016
  const m = captureMap();
1017
+ const u = captureUnsuspend();
881
1018
  return r.then(() => {
882
1019
  swapContextMap(m);
1020
+ restoreUnsuspend(u);
883
1021
  popTreeContext(savedTree);
884
1022
  return renderChildArrayFrom(children, i + 1, writer, isSvg);
885
1023
  });
@@ -894,87 +1032,94 @@ function renderChildren(children, writer, isSvg = false) {
894
1032
  }
895
1033
  return renderNode(children, writer, isSvg);
896
1034
  }
897
- var MAX_SUSPENSE_RETRIES = 25;
898
1035
  async function renderSuspense(props, writer, isSvg = false) {
899
1036
  const { children, fallback } = props;
900
- let attempts = 0;
901
1037
  const snap = snapshotContext();
902
- while (attempts < MAX_SUSPENSE_RETRIES) {
903
- restoreContext(snap);
904
- let buffer = new BufferWriter();
905
- try {
906
- const r = renderNode(children, buffer, isSvg);
907
- if (r && typeof r.then === "function") {
908
- const m = captureMap();
909
- await r;
910
- swapContextMap(m);
1038
+ const savedMap = captureMap();
1039
+ const savedMapClone = savedMap ? new Map(savedMap) : null;
1040
+ const buffer = new BufferWriter();
1041
+ try {
1042
+ const r = renderNode(children, buffer, isSvg);
1043
+ if (r && typeof r.then === "function") {
1044
+ const m = captureMap();
1045
+ const u = captureUnsuspend();
1046
+ await r;
1047
+ swapContextMap(m);
1048
+ restoreUnsuspend(u);
1049
+ }
1050
+ writer.write("<!--$-->");
1051
+ buffer.flushTo(writer);
1052
+ writer.write("<!--/$-->");
1053
+ writer.flush?.();
1054
+ } catch (error) {
1055
+ if (error === SUSPENSE_RETRY_LIMIT) {
1056
+ restoreContext(snap);
1057
+ swapContextMap(savedMapClone);
1058
+ writer.write("<!--$?-->");
1059
+ if (fallback) {
1060
+ const r = renderNode(fallback, writer, isSvg);
1061
+ if (r && typeof r.then === "function") {
1062
+ const m = captureMap();
1063
+ const u = captureUnsuspend();
1064
+ await r;
1065
+ swapContextMap(m);
1066
+ restoreUnsuspend(u);
1067
+ }
911
1068
  }
912
- writer.write("<!--$-->");
913
- buffer.flush(writer);
914
1069
  writer.write("<!--/$-->");
915
- return;
916
- } catch (error) {
917
- if (error && typeof error.then === "function") {
918
- const m = captureMap();
919
- await error;
920
- swapContextMap(m);
921
- attempts++;
922
- } else {
923
- throw error;
924
- }
1070
+ } else {
1071
+ throw error;
925
1072
  }
926
1073
  }
927
- restoreContext(snap);
928
- writer.write("<!--$?-->");
929
- if (fallback) {
930
- const r = renderNode(fallback, writer, isSvg);
1074
+ }
1075
+ var _streamEncoder = new TextEncoder();
1076
+ var NULL_WRITER = {
1077
+ lastWasText: false,
1078
+ write(_c) {
1079
+ },
1080
+ text(_s) {
1081
+ }
1082
+ };
1083
+ async function renderPreflight(element, options) {
1084
+ const idPrefix = options?.identifierPrefix ?? "";
1085
+ const prev = swapContextMap(null);
1086
+ try {
1087
+ resetRenderState(idPrefix);
1088
+ NULL_WRITER.lastWasText = false;
1089
+ const r = renderNode(element, NULL_WRITER);
931
1090
  if (r && typeof r.then === "function") {
932
1091
  const m = captureMap();
933
1092
  await r;
934
1093
  swapContextMap(m);
935
1094
  }
1095
+ } finally {
1096
+ swapContextMap(prev);
936
1097
  }
937
- writer.write("<!--/$-->");
938
1098
  }
939
1099
  async function renderToString(element, options) {
940
1100
  const idPrefix = options?.identifierPrefix ?? "";
941
- const contextMap = /* @__PURE__ */ new Map();
942
- const prev = swapContextMap(contextMap);
1101
+ const prev = swapContextMap(null);
1102
+ let output = "";
1103
+ const writer = {
1104
+ lastWasText: false,
1105
+ write(c) {
1106
+ output += c;
1107
+ this.lastWasText = false;
1108
+ },
1109
+ text(s2) {
1110
+ output += s2;
1111
+ this.lastWasText = true;
1112
+ }
1113
+ };
943
1114
  try {
944
- for (let attempt = 0; attempt < MAX_SUSPENSE_RETRIES; attempt++) {
945
- resetRenderState(idPrefix);
946
- swapContextMap(contextMap);
947
- const chunks = [];
948
- const writer = {
949
- lastWasText: false,
950
- write(c) {
951
- chunks.push(c);
952
- this.lastWasText = false;
953
- },
954
- text(s2) {
955
- chunks.push(s2);
956
- this.lastWasText = true;
957
- }
958
- };
959
- try {
960
- const r = renderNode(element, writer);
961
- if (r && typeof r.then === "function") {
962
- const m = captureMap();
963
- await r;
964
- swapContextMap(m);
965
- }
966
- return chunks.join("");
967
- } catch (error) {
968
- if (error && typeof error.then === "function") {
969
- const m = captureMap();
970
- await error;
971
- swapContextMap(m);
972
- continue;
973
- }
974
- throw error;
975
- }
1115
+ resetRenderState(idPrefix);
1116
+ const r = renderNode(element, writer);
1117
+ if (r && typeof r.then === "function") {
1118
+ const m = captureMap();
1119
+ await r;
1120
+ swapContextMap(m);
976
1121
  }
977
- throw new Error("[slim-react] renderToString exceeded maximum retries");
1122
+ return output;
978
1123
  } finally {
979
1124
  swapContextMap(prev);
980
1125
  }
@@ -995,8 +1140,8 @@ var ATTR = {
995
1140
  fetchPriority: "fetchpriority",
996
1141
  hrefLang: "hreflang"
997
1142
  };
998
- function renderHeadTag(tag, opts, selfClose = false) {
999
- let attrs = "";
1143
+ function renderHeadTag(tag, id, opts, selfClose = false) {
1144
+ let attrs = ` id="${escAttr(id)}"`;
1000
1145
  let inner = "";
1001
1146
  for (const [k, v] of Object.entries(opts)) {
1002
1147
  if (k === "key" || k === "children") continue;
@@ -1010,18 +1155,18 @@ function renderHeadTag(tag, opts, selfClose = false) {
1010
1155
  }
1011
1156
  return selfClose ? `<${tag}${attrs}>` : `<${tag}${attrs}>${inner}</${tag}>`;
1012
1157
  }
1013
- var getHeadHtml = (seoData) => {
1158
+ function buildHeadHtml(seoData) {
1014
1159
  let html = `<title>${escText(seoData.title ?? "")}</title>`;
1015
- for (const opts of Object.values(seoData.meta))
1016
- html += renderHeadTag("meta", opts, true);
1017
- for (const opts of Object.values(seoData.link))
1018
- html += renderHeadTag("link", opts, true);
1019
- for (const opts of Object.values(seoData.style))
1020
- html += renderHeadTag("style", opts);
1021
- for (const opts of Object.values(seoData.script))
1022
- html += renderHeadTag("script", opts);
1160
+ for (const [id, opts] of Object.entries(seoData.meta))
1161
+ html += renderHeadTag("meta", id, opts, true);
1162
+ for (const [id, opts] of Object.entries(seoData.link))
1163
+ html += renderHeadTag("link", id, opts, true);
1164
+ for (const [id, opts] of Object.entries(seoData.style))
1165
+ html += renderHeadTag("style", id, opts);
1166
+ for (const [id, opts] of Object.entries(seoData.script))
1167
+ html += renderHeadTag("script", id, opts);
1023
1168
  return html;
1024
- };
1169
+ }
1025
1170
  var getReactResponse = async (req, opts) => {
1026
1171
  const App = opts.document.body;
1027
1172
  const { getInitProps, getFinalProps } = opts.document;
@@ -1035,28 +1180,44 @@ var getReactResponse = async (req, opts) => {
1035
1180
  };
1036
1181
  const unsuspend = { cache: /* @__PURE__ */ new Map() };
1037
1182
  globalThis.__hadarsUnsuspend = unsuspend;
1038
- let bodyHtml;
1183
+ globalThis.__hadarsContext = context;
1184
+ const element = createElement(App, props);
1039
1185
  try {
1040
- bodyHtml = await renderToString(createElement(App, props));
1186
+ await renderPreflight(element);
1041
1187
  } finally {
1042
1188
  globalThis.__hadarsUnsuspend = null;
1189
+ globalThis.__hadarsContext = null;
1043
1190
  }
1044
- const { context: _, ...restProps } = getFinalProps ? await getFinalProps(props) : props;
1045
- const serverData = {};
1046
- for (const [key, entry] of unsuspend.cache) {
1047
- if (entry.status === "fulfilled") serverData[key] = entry.value;
1048
- }
1049
- const clientProps = {
1050
- ...restProps,
1051
- location: req.location,
1052
- ...Object.keys(serverData).length > 0 ? { __serverData: serverData } : {}
1191
+ const status = context.head.status;
1192
+ const getAppBody = async () => {
1193
+ globalThis.__hadarsUnsuspend = unsuspend;
1194
+ globalThis.__hadarsContext = context;
1195
+ try {
1196
+ return await renderToString(element);
1197
+ } finally {
1198
+ globalThis.__hadarsUnsuspend = null;
1199
+ globalThis.__hadarsContext = null;
1200
+ }
1053
1201
  };
1054
- return {
1055
- bodyHtml,
1056
- clientProps,
1057
- status: context.head.status,
1058
- headHtml: getHeadHtml(context.head)
1202
+ const finalize = async () => {
1203
+ const { context: _, ...restProps } = getFinalProps ? await getFinalProps(props) : props;
1204
+ const serverData = {};
1205
+ let hasServerData = false;
1206
+ for (const [key, entry] of unsuspend.cache) {
1207
+ if (entry.status === "fulfilled") {
1208
+ serverData[key] = entry.value;
1209
+ hasServerData = true;
1210
+ }
1211
+ }
1212
+ return {
1213
+ clientProps: {
1214
+ ...restProps,
1215
+ location: req.location,
1216
+ ...hasServerData ? { __serverData: serverData } : {}
1217
+ }
1218
+ };
1059
1219
  };
1220
+ return { head: context.head, status, getAppBody, finalize };
1060
1221
  };
1061
1222
 
1062
1223
  // src/utils/rspack.ts
@@ -1454,27 +1615,6 @@ function nodeReadableToWebStream(readable) {
1454
1615
  });
1455
1616
  }
1456
1617
  var noopCtx = { upgrade: () => false };
1457
- var COMPRESSIBLE_RE = /\b(?:text\/|application\/(?:json|javascript|xml)|image\/svg\+xml)/;
1458
- function withCompression(handler) {
1459
- return async (req, ctx) => {
1460
- const res = await handler(req, ctx);
1461
- if (!res?.body) return res;
1462
- if (!COMPRESSIBLE_RE.test(res.headers.get("Content-Type") ?? "")) return res;
1463
- if (res.headers.has("Content-Encoding")) return res;
1464
- const accept = req.headers.get("Accept-Encoding") ?? "";
1465
- const encoding = accept.includes("br") ? "br" : accept.includes("gzip") ? "gzip" : null;
1466
- if (!encoding) return res;
1467
- try {
1468
- const compressed = res.body.pipeThrough(new globalThis.CompressionStream(encoding));
1469
- const headers = new Headers(res.headers);
1470
- headers.set("Content-Encoding", encoding);
1471
- headers.delete("Content-Length");
1472
- return new Response(compressed, { status: res.status, statusText: res.statusText, headers });
1473
- } catch {
1474
- return res;
1475
- }
1476
- };
1477
- }
1478
1618
  function withRequestLogging(handler) {
1479
1619
  return async (req, ctx) => {
1480
1620
  const start = performance.now();
@@ -1486,9 +1626,27 @@ function withRequestLogging(handler) {
1486
1626
  return res;
1487
1627
  };
1488
1628
  }
1629
+ var COMPRESSIBLE_RE = /^text\/|\/json|\/javascript|\/xml|\/wasm/;
1630
+ function withCompression(handler) {
1631
+ return async (req, ctx) => {
1632
+ const res = await handler(req, ctx);
1633
+ if (!res || !res.body) return res;
1634
+ const accept = req.headers.get("Accept-Encoding") ?? "";
1635
+ if (!accept.includes("gzip")) return res;
1636
+ if (res.headers.has("content-encoding")) return res;
1637
+ const ct = res.headers.get("content-type") ?? "";
1638
+ if (!COMPRESSIBLE_RE.test(ct)) return res;
1639
+ const compressed = res.body.pipeThrough(
1640
+ new globalThis.CompressionStream("gzip")
1641
+ );
1642
+ const headers = new Headers(res.headers);
1643
+ headers.set("content-encoding", "gzip");
1644
+ headers.delete("content-length");
1645
+ return new Response(compressed, { status: res.status, headers });
1646
+ };
1647
+ }
1489
1648
  async function serve(port, fetchHandler, websocket) {
1490
- fetchHandler = withCompression(fetchHandler);
1491
- fetchHandler = withRequestLogging(fetchHandler);
1649
+ fetchHandler = withCompression(withRequestLogging(fetchHandler));
1492
1650
  if (isBun) {
1493
1651
  globalThis.Bun.serve({
1494
1652
  port,
@@ -1561,7 +1719,7 @@ async function serve(port, fetchHandler, websocket) {
1561
1719
  }
1562
1720
 
1563
1721
  // src/utils/staticFile.ts
1564
- import { readFile, stat } from "node:fs/promises";
1722
+ import { readFile } from "node:fs/promises";
1565
1723
  var MIME = {
1566
1724
  html: "text/html; charset=utf-8",
1567
1725
  htm: "text/html; charset=utf-8",
@@ -1587,16 +1745,11 @@ var MIME = {
1587
1745
  pdf: "application/pdf"
1588
1746
  };
1589
1747
  async function tryServeFile(filePath) {
1590
- try {
1591
- await stat(filePath);
1592
- } catch {
1593
- return null;
1594
- }
1595
1748
  try {
1596
1749
  const data = await readFile(filePath);
1597
1750
  const ext = filePath.split(".").pop()?.toLowerCase() ?? "";
1598
1751
  const contentType = MIME[ext] ?? "application/octet-stream";
1599
- return new Response(data.buffer, { headers: { "Content-Type": contentType } });
1752
+ return new Response(data, { headers: { "Content-Type": contentType } });
1600
1753
  } catch {
1601
1754
  return null;
1602
1755
  }
@@ -1617,16 +1770,25 @@ import cluster from "node:cluster";
1617
1770
  var HEAD_MARKER = '<meta name="HADARS_HEAD">';
1618
1771
  var BODY_MARKER = '<meta name="HADARS_BODY">';
1619
1772
  var encoder = new TextEncoder();
1620
- async function buildSsrResponse(bodyHtml, clientProps, headHtml, status, getPrecontentHtml) {
1773
+ function buildSsrResponse(head, status, getAppBody, finalize, getPrecontentHtml) {
1774
+ const headHtml = buildHeadHtml(head);
1775
+ const precontentResult = getPrecontentHtml(headHtml);
1621
1776
  const responseStream = new ReadableStream({
1622
1777
  async start(controller) {
1623
- const [precontentHtml, postContent] = await getPrecontentHtml(headHtml);
1624
- controller.enqueue(encoder.encode(precontentHtml));
1625
- const scriptContent = JSON.stringify({ hadars: { props: clientProps } }).replace(/</g, "\\u003c");
1626
- controller.enqueue(encoder.encode(
1627
- `<div id="app">${bodyHtml}</div><script id="hadars" type="application/json">${scriptContent}</script>` + postContent
1628
- ));
1629
- controller.close();
1778
+ try {
1779
+ const [precontentHtml, postContent] = precontentResult instanceof Promise ? await precontentResult : precontentResult;
1780
+ controller.enqueue(encoder.encode(precontentHtml));
1781
+ const bodyHtml = await getAppBody();
1782
+ controller.enqueue(encoder.encode(`<div id="app">${bodyHtml}</div>`));
1783
+ const { clientProps } = await finalize();
1784
+ const scriptContent = JSON.stringify({ hadars: { props: clientProps } }).replace(/</g, "\\u003c");
1785
+ controller.enqueue(encoder.encode(
1786
+ `<script id="hadars" type="application/json">${scriptContent}</script>` + postContent
1787
+ ));
1788
+ controller.close();
1789
+ } catch (err) {
1790
+ controller.error(err);
1791
+ }
1630
1792
  }
1631
1793
  });
1632
1794
  return new Response(responseStream, {
@@ -1638,16 +1800,18 @@ var makePrecontentHtmlGetter = (htmlFilePromise) => {
1638
1800
  let preHead = null;
1639
1801
  let postHead = null;
1640
1802
  let postContent = null;
1641
- return async (headHtml) => {
1642
- if (preHead === null || postHead === null || postContent === null) {
1643
- const html = await htmlFilePromise;
1803
+ return (headHtml) => {
1804
+ if (preHead !== null) {
1805
+ return [preHead + headHtml + postHead, postContent];
1806
+ }
1807
+ return htmlFilePromise.then((html) => {
1644
1808
  const headEnd = html.indexOf(HEAD_MARKER);
1645
1809
  const contentStart = html.indexOf(BODY_MARKER);
1646
1810
  preHead = html.slice(0, headEnd);
1647
1811
  postHead = html.slice(headEnd + HEAD_MARKER.length, contentStart);
1648
1812
  postContent = html.slice(contentStart + BODY_MARKER.length);
1649
- }
1650
- return [preHead + headHtml + postHead, postContent];
1813
+ return [preHead + headHtml + postHead, postContent];
1814
+ });
1651
1815
  };
1652
1816
  };
1653
1817
  async function transformStream(data, stream) {
@@ -2117,7 +2281,7 @@ var dev = async (options) => {
2117
2281
  getInitProps,
2118
2282
  getFinalProps
2119
2283
  } = await import(importPath);
2120
- const { bodyHtml, clientProps, status, headHtml } = await getReactResponse(request, {
2284
+ const { head, status, getAppBody, finalize } = await getReactResponse(request, {
2121
2285
  document: {
2122
2286
  body: Component,
2123
2287
  lang: "en",
@@ -2126,13 +2290,14 @@ var dev = async (options) => {
2126
2290
  }
2127
2291
  });
2128
2292
  if (request.headers.get("Accept") === "application/json") {
2293
+ const { clientProps } = await finalize();
2129
2294
  const serverData = clientProps.__serverData ?? {};
2130
2295
  return new Response(JSON.stringify({ serverData }), {
2131
2296
  status,
2132
2297
  headers: { "Content-Type": "application/json; charset=utf-8" }
2133
2298
  });
2134
2299
  }
2135
- return buildSsrResponse(bodyHtml, clientProps, headHtml, status, getPrecontentHtml);
2300
+ return buildSsrResponse(head, status, getAppBody, finalize, getPrecontentHtml);
2136
2301
  } catch (err) {
2137
2302
  console.error("[hadars] SSR render error:", err);
2138
2303
  const msg = (err?.stack ?? err?.message ?? String(err)).replace(/</g, "&lt;");
@@ -2274,7 +2439,7 @@ var run = async (options) => {
2274
2439
  status: wStatus
2275
2440
  });
2276
2441
  }
2277
- const { bodyHtml, clientProps, status, headHtml } = await getReactResponse(request, {
2442
+ const { head, status, getAppBody, finalize } = await getReactResponse(request, {
2278
2443
  document: {
2279
2444
  body: Component,
2280
2445
  lang: "en",
@@ -2283,13 +2448,14 @@ var run = async (options) => {
2283
2448
  }
2284
2449
  });
2285
2450
  if (request.headers.get("Accept") === "application/json") {
2451
+ const { clientProps } = await finalize();
2286
2452
  const serverData = clientProps.__serverData ?? {};
2287
2453
  return new Response(JSON.stringify({ serverData }), {
2288
2454
  status,
2289
2455
  headers: { "Content-Type": "application/json; charset=utf-8" }
2290
2456
  });
2291
2457
  }
2292
- return buildSsrResponse(bodyHtml, clientProps, headHtml, status, getPrecontentHtml);
2458
+ return buildSsrResponse(head, status, getAppBody, finalize, getPrecontentHtml);
2293
2459
  } catch (err) {
2294
2460
  console.error("[hadars] SSR render error:", err);
2295
2461
  return new Response("Internal Server Error", { status: 500 });
@@ -2335,6 +2501,67 @@ async function loadConfig(configPath) {
2335
2501
  const mod = await import(url);
2336
2502
  return mod && (mod.default ?? mod);
2337
2503
  }
2504
+ async function bundleCloudflare(config, configPath, outputFile, cwd) {
2505
+ console.log("Building hadars project...");
2506
+ await build({ ...config, mode: "production" });
2507
+ const ssrBundle = resolve(cwd, ".hadars", "index.ssr.js");
2508
+ const outHtml = resolve(cwd, ".hadars", "static", "out.html");
2509
+ if (!existsSync3(ssrBundle)) {
2510
+ console.error(`SSR bundle not found: ${ssrBundle}`);
2511
+ process.exit(1);
2512
+ }
2513
+ if (!existsSync3(outHtml)) {
2514
+ console.error(`HTML template not found: ${outHtml}`);
2515
+ process.exit(1);
2516
+ }
2517
+ const cloudflareModule = resolve(dirname(fileURLToPath3(import.meta.url)), "cloudflare.js");
2518
+ const shimPath = join(cwd, `.hadars-cloudflare-shim-${Date.now()}.ts`);
2519
+ const shim = [
2520
+ `import * as ssrModule from ${JSON.stringify(ssrBundle)};`,
2521
+ `import outHtml from ${JSON.stringify(outHtml)};`,
2522
+ `import { createCloudflareHandler } from ${JSON.stringify(cloudflareModule)};`,
2523
+ `import config from ${JSON.stringify(configPath)};`,
2524
+ `export default createCloudflareHandler(config as any, { ssrModule: ssrModule as any, outHtml });`
2525
+ ].join("\n") + "\n";
2526
+ await writeFile(shimPath, shim, "utf-8");
2527
+ try {
2528
+ const { build: esbuild } = await import("esbuild");
2529
+ console.log(`Bundling Cloudflare Worker \u2192 ${outputFile}`);
2530
+ await esbuild({
2531
+ entryPoints: [shimPath],
2532
+ bundle: true,
2533
+ // 'browser' avoids Node.js built-in shims; CF Workers uses Web APIs.
2534
+ // If you use node:* APIs in your app code, add nodejs_compat to wrangler.toml.
2535
+ platform: "browser",
2536
+ format: "esm",
2537
+ target: ["es2022"],
2538
+ outfile: outputFile,
2539
+ sourcemap: false,
2540
+ loader: { ".html": "text", ".tsx": "tsx", ".ts": "ts" },
2541
+ // @rspack/* is build-time only — never imported at Worker runtime.
2542
+ external: ["@rspack/*"],
2543
+ // Cloudflare Workers supports the Web Crypto API natively; suppress
2544
+ // esbuild's attempt to polyfill node:crypto.
2545
+ define: { "global": "globalThis" }
2546
+ });
2547
+ console.log(`Cloudflare Worker bundle written to ${outputFile}`);
2548
+ console.log(`
2549
+ Deploy instructions:`);
2550
+ console.log(` 1. Ensure wrangler.toml points to the output file:`);
2551
+ console.log(` name = "my-app"`);
2552
+ console.log(` main = "${outputFile}"`);
2553
+ console.log(` compatibility_date = "2024-09-23"`);
2554
+ console.log(` compatibility_flags = ["nodejs_compat"]`);
2555
+ console.log(` 2. Upload .hadars/static/ assets to R2 (or another CDN):`);
2556
+ console.log(` wrangler r2 object put my-bucket/assets/ --file .hadars/static/ --recursive`);
2557
+ console.log(` 3. Add a route rule in wrangler.toml to send *.js / *.css to R2`);
2558
+ console.log(` and all other requests to the Worker.`);
2559
+ console.log(` 4. Deploy: wrangler deploy`);
2560
+ } finally {
2561
+ await unlink(shimPath).catch(() => {
2562
+ });
2563
+ }
2564
+ }
2338
2565
  async function bundleLambda(config, configPath, outputFile, cwd) {
2339
2566
  console.log("Building hadars project...");
2340
2567
  await build({ ...config, mode: "production" });
@@ -2436,7 +2663,7 @@ export default config;
2436
2663
  dist/
2437
2664
  `,
2438
2665
  "src/App.tsx": () => `import React from 'react';
2439
- import { HadarsContext, HadarsHead, type HadarsApp } from 'hadars';
2666
+ import { HadarsHead, type HadarsApp } from 'hadars';
2440
2667
 
2441
2668
  const css = \`
2442
2669
  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
@@ -2548,15 +2775,15 @@ const css = \`
2548
2775
 
2549
2776
  \`;
2550
2777
 
2551
- const App: HadarsApp<{}> = ({ context }) => {
2778
+ const App: HadarsApp<{}> = () => {
2552
2779
  const [count, setCount] = React.useState(0);
2553
2780
 
2554
2781
  return (
2555
- <HadarsContext context={context}>
2782
+ <>
2556
2783
  <HadarsHead status={200}>
2557
2784
  <title>My App</title>
2558
- <meta id="viewport" name="viewport" content="width=device-width, initial-scale=1" />
2559
- <style id="app-styles" dangerouslySetInnerHTML={{ __html: css }} />
2785
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
2786
+ <style data-id="app-styles" dangerouslySetInnerHTML={{ __html: css }} />
2560
2787
  </HadarsHead>
2561
2788
 
2562
2789
  <nav className="nav">
@@ -2614,7 +2841,7 @@ const App: HadarsApp<{}> = ({ context }) => {
2614
2841
  </div>
2615
2842
  </div>
2616
2843
 
2617
- </HadarsContext>
2844
+ </>
2618
2845
  );
2619
2846
  };
2620
2847
 
@@ -2643,7 +2870,7 @@ Done! Next steps:
2643
2870
  `);
2644
2871
  }
2645
2872
  function usage() {
2646
- console.log("Usage: hadars <new <name> | dev | build | run | export lambda [output.mjs]>");
2873
+ console.log("Usage: hadars <new <name> | dev | build | run | export lambda [output.mjs] | export cloudflare [output.mjs]>");
2647
2874
  }
2648
2875
  async function runCli(argv, cwd = process.cwd()) {
2649
2876
  const cmd = argv[2];
@@ -2686,13 +2913,18 @@ async function runCli(argv, cwd = process.cwd()) {
2686
2913
  process.exit(0);
2687
2914
  case "export": {
2688
2915
  const subCmd = argv[3];
2689
- if (subCmd !== "lambda") {
2690
- console.error(`Unknown export target: ${subCmd ?? "(none)"}. Did you mean: hadars export lambda`);
2916
+ if (subCmd === "lambda") {
2917
+ const outputFile = resolve(cwd, argv[4] ?? "lambda.mjs");
2918
+ await bundleLambda(cfg, configPath, outputFile, cwd);
2919
+ process.exit(0);
2920
+ } else if (subCmd === "cloudflare") {
2921
+ const outputFile = resolve(cwd, argv[4] ?? "cloudflare.mjs");
2922
+ await bundleCloudflare(cfg, configPath, outputFile, cwd);
2923
+ process.exit(0);
2924
+ } else {
2925
+ console.error(`Unknown export target: ${subCmd ?? "(none)"}. Supported: lambda, cloudflare`);
2691
2926
  process.exit(1);
2692
2927
  }
2693
- const outputFile = resolve(cwd, argv[4] ?? "lambda.mjs");
2694
- await bundleLambda(cfg, configPath, outputFile, cwd);
2695
- process.exit(0);
2696
2928
  }
2697
2929
  case "run":
2698
2930
  console.log("Running project...");