hadars 0.3.1 → 0.3.2

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.
@@ -218,7 +218,16 @@ function componentCalledUseId() {
218
218
  function snapshotContext() {
219
219
  const st = s();
220
220
  const ctx = st.currentTreeContext;
221
- return { tree: { id: ctx.id, overflow: ctx.overflow }, localId: st.localIdCounter, treeDepth: _treeDepth };
221
+ const depth = _treeDepth;
222
+ return {
223
+ tree: { id: ctx.id, overflow: ctx.overflow },
224
+ localId: st.localIdCounter,
225
+ treeDepth: depth,
226
+ // Snapshot the live stack so that popTreeContext reads correct saved values
227
+ // even if another concurrent render's resetRenderState stomped the arrays.
228
+ idStack: _treeIdStack.slice(0, depth),
229
+ ovStack: _treeOvStack.slice(0, depth)
230
+ };
222
231
  }
223
232
  function restoreContext(snap) {
224
233
  const st = s();
@@ -227,6 +236,10 @@ function restoreContext(snap) {
227
236
  ctx.overflow = snap.tree.overflow;
228
237
  st.localIdCounter = snap.localId;
229
238
  _treeDepth = snap.treeDepth;
239
+ for (let i = 0; i < snap.treeDepth; i++) {
240
+ _treeIdStack[i] = snap.idStack[i];
241
+ _treeOvStack[i] = snap.ovStack[i];
242
+ }
230
243
  }
231
244
  function getTreeId() {
232
245
  const { id, overflow } = s().currentTreeContext;
@@ -372,6 +385,14 @@ function restoreDispatcher(prev) {
372
385
  }
373
386
 
374
387
  // src/slim-react/render.ts
388
+ function captureRenderCtx() {
389
+ return { m: captureMap(), u: captureUnsuspend(), t: snapshotContext() };
390
+ }
391
+ function restoreRenderCtx(ctx) {
392
+ swapContextMap(ctx.m);
393
+ restoreUnsuspend(ctx.u);
394
+ restoreContext(ctx.t);
395
+ }
375
396
  var VOID_ELEMENTS = /* @__PURE__ */ new Set([
376
397
  "area",
377
398
  "base",
@@ -815,11 +836,9 @@ function renderComponent(type, props, writer, isSvg, _suspenseRetries = 0) {
815
836
  if (e && typeof e.then === "function") {
816
837
  if (_suspenseRetries + 1 >= MAX_COMPONENT_SUSPENSE_RETRIES) throw SUSPENSE_RETRY_LIMIT;
817
838
  patchPromiseStatus(e);
818
- const m = captureMap();
819
- const u = captureUnsuspend();
839
+ const rctx = captureRenderCtx();
820
840
  return e.then(() => {
821
- swapContextMap(m);
822
- restoreUnsuspend(u);
841
+ restoreRenderCtx(rctx);
823
842
  return renderComponent(type, props, writer, isSvg, _suspenseRetries + 1);
824
843
  });
825
844
  }
@@ -856,17 +875,14 @@ function renderComponent(type, props, writer, isSvg, _suspenseRetries = 0) {
856
875
  };
857
876
  const r2 = renderChildren(props.children, writer, isSvg);
858
877
  if (r2 && typeof r2.then === "function") {
859
- const m = captureMap();
860
- const u = captureUnsuspend();
878
+ const rctx = captureRenderCtx();
861
879
  return r2.then(
862
880
  () => {
863
- swapContextMap(m);
864
- restoreUnsuspend(u);
881
+ restoreRenderCtx(rctx);
865
882
  finish();
866
883
  },
867
884
  (e) => {
868
- swapContextMap(m);
869
- restoreUnsuspend(u);
885
+ restoreRenderCtx(rctx);
870
886
  finish();
871
887
  throw e;
872
888
  }
@@ -895,11 +911,9 @@ function renderComponent(type, props, writer, isSvg, _suspenseRetries = 0) {
895
911
  if (e && typeof e.then === "function") {
896
912
  if (_suspenseRetries + 1 >= MAX_COMPONENT_SUSPENSE_RETRIES) throw SUSPENSE_RETRY_LIMIT;
897
913
  patchPromiseStatus(e);
898
- const m = captureMap();
899
- const u = captureUnsuspend();
914
+ const rctx = captureRenderCtx();
900
915
  return e.then(() => {
901
- swapContextMap(m);
902
- restoreUnsuspend(u);
916
+ restoreRenderCtx(rctx);
903
917
  return renderComponent(type, props, writer, isSvg, _suspenseRetries + 1);
904
918
  });
905
919
  }
@@ -911,30 +925,25 @@ function renderComponent(type, props, writer, isSvg, _suspenseRetries = 0) {
911
925
  savedIdTree = pushTreeContext(1, 0);
912
926
  }
913
927
  if (result instanceof Promise) {
914
- const m = captureMap();
915
- const u = captureUnsuspend();
928
+ const rctx = captureRenderCtx();
916
929
  return result.then((resolved) => {
917
- swapContextMap(m);
918
- restoreUnsuspend(u);
930
+ restoreRenderCtx(rctx);
919
931
  let asyncSavedIdTree;
920
932
  if (componentCalledUseId()) {
921
933
  asyncSavedIdTree = pushTreeContext(1, 0);
922
934
  }
923
935
  const r2 = renderNode(resolved, writer, isSvg);
924
936
  if (r2 && typeof r2.then === "function") {
925
- const m2 = captureMap();
926
- const u2 = captureUnsuspend();
937
+ const rctx2 = captureRenderCtx();
927
938
  return r2.then(
928
939
  () => {
929
- swapContextMap(m2);
930
- restoreUnsuspend(u2);
940
+ restoreRenderCtx(rctx2);
931
941
  if (asyncSavedIdTree !== void 0) popTreeContext(asyncSavedIdTree);
932
942
  popComponentScope(savedScope);
933
943
  if (isProvider) popContextValue(ctx, prevCtxValue);
934
944
  },
935
945
  (e) => {
936
- swapContextMap(m2);
937
- restoreUnsuspend(u2);
946
+ restoreRenderCtx(rctx2);
938
947
  if (asyncSavedIdTree !== void 0) popTreeContext(asyncSavedIdTree);
939
948
  popComponentScope(savedScope);
940
949
  if (isProvider) popContextValue(ctx, prevCtxValue);
@@ -946,8 +955,7 @@ function renderComponent(type, props, writer, isSvg, _suspenseRetries = 0) {
946
955
  popComponentScope(savedScope);
947
956
  if (isProvider) popContextValue(ctx, prevCtxValue);
948
957
  }, (e) => {
949
- swapContextMap(m);
950
- restoreUnsuspend(u);
958
+ restoreRenderCtx(rctx);
951
959
  popComponentScope(savedScope);
952
960
  if (isProvider) popContextValue(ctx, prevCtxValue);
953
961
  throw e;
@@ -955,19 +963,16 @@ function renderComponent(type, props, writer, isSvg, _suspenseRetries = 0) {
955
963
  }
956
964
  const r = renderNode(result, writer, isSvg);
957
965
  if (r && typeof r.then === "function") {
958
- const m = captureMap();
959
- const u = captureUnsuspend();
966
+ const rctx = captureRenderCtx();
960
967
  return r.then(
961
968
  () => {
962
- swapContextMap(m);
963
- restoreUnsuspend(u);
969
+ restoreRenderCtx(rctx);
964
970
  if (savedIdTree !== void 0) popTreeContext(savedIdTree);
965
971
  popComponentScope(savedScope);
966
972
  if (isProvider) popContextValue(ctx, prevCtxValue);
967
973
  },
968
974
  (e) => {
969
- swapContextMap(m);
970
- restoreUnsuspend(u);
975
+ restoreRenderCtx(rctx);
971
976
  if (savedIdTree !== void 0) popTreeContext(savedIdTree);
972
977
  popComponentScope(savedScope);
973
978
  if (isProvider) popContextValue(ctx, prevCtxValue);
@@ -992,11 +997,9 @@ function renderChildArrayFrom(children, startIndex, writer, isSvg) {
992
997
  const savedTree = pushTreeContext(totalChildren, i);
993
998
  const r = renderNode(child, writer, isSvg);
994
999
  if (r && typeof r.then === "function") {
995
- const m = captureMap();
996
- const u = captureUnsuspend();
1000
+ const rctx = captureRenderCtx();
997
1001
  return r.then(() => {
998
- swapContextMap(m);
999
- restoreUnsuspend(u);
1002
+ restoreRenderCtx(rctx);
1000
1003
  popTreeContext(savedTree);
1001
1004
  return renderChildArrayFrom(children, i + 1, writer, isSvg);
1002
1005
  });
@@ -1020,11 +1023,9 @@ async function renderSuspense(props, writer, isSvg = false) {
1020
1023
  try {
1021
1024
  const r = renderNode(children, buffer, isSvg);
1022
1025
  if (r && typeof r.then === "function") {
1023
- const m = captureMap();
1024
- const u = captureUnsuspend();
1026
+ const rctx = captureRenderCtx();
1025
1027
  await r;
1026
- swapContextMap(m);
1027
- restoreUnsuspend(u);
1028
+ restoreRenderCtx(rctx);
1028
1029
  }
1029
1030
  writer.write("<!--$-->");
1030
1031
  buffer.flushTo(writer);
@@ -1038,11 +1039,9 @@ async function renderSuspense(props, writer, isSvg = false) {
1038
1039
  if (fallback) {
1039
1040
  const r = renderNode(fallback, writer, isSvg);
1040
1041
  if (r && typeof r.then === "function") {
1041
- const m = captureMap();
1042
- const u = captureUnsuspend();
1042
+ const rctx = captureRenderCtx();
1043
1043
  await r;
1044
- swapContextMap(m);
1045
- restoreUnsuspend(u);
1044
+ restoreRenderCtx(rctx);
1046
1045
  }
1047
1046
  }
1048
1047
  writer.write("<!--/$-->");
@@ -1079,9 +1078,9 @@ function renderToStream(element, options) {
1079
1078
  try {
1080
1079
  const r = renderNode(element, writer);
1081
1080
  if (r && typeof r.then === "function") {
1082
- const m = captureMap();
1081
+ const rctx = captureRenderCtx();
1083
1082
  await r;
1084
- swapContextMap(m);
1083
+ restoreRenderCtx(rctx);
1085
1084
  }
1086
1085
  writer.flush();
1087
1086
  controller.close();
@@ -1108,9 +1107,9 @@ async function renderPreflight(element, options) {
1108
1107
  NULL_WRITER.lastWasText = false;
1109
1108
  const r = renderNode(element, NULL_WRITER);
1110
1109
  if (r && typeof r.then === "function") {
1111
- const m = captureMap();
1110
+ const rctx = captureRenderCtx();
1112
1111
  await r;
1113
- swapContextMap(m);
1112
+ restoreRenderCtx(rctx);
1114
1113
  }
1115
1114
  } finally {
1116
1115
  swapContextMap(prev);
@@ -1135,9 +1134,9 @@ async function renderToString(element, options) {
1135
1134
  resetRenderState(idPrefix);
1136
1135
  const r = renderNode(element, writer);
1137
1136
  if (r && typeof r.then === "function") {
1138
- const m = captureMap();
1137
+ const rctx = captureRenderCtx();
1139
1138
  await r;
1140
- swapContextMap(m);
1139
+ restoreRenderCtx(rctx);
1141
1140
  }
1142
1141
  return output;
1143
1142
  } finally {
@@ -34,7 +34,7 @@ import {
34
34
  useSyncExternalStore,
35
35
  useTransition,
36
36
  version
37
- } from "../chunk-LY5MTHFV.js";
37
+ } from "../chunk-TV37IMRB.js";
38
38
  import {
39
39
  FRAGMENT_TYPE,
40
40
  Fragment,
@@ -134,7 +134,16 @@ function componentCalledUseId() {
134
134
  function snapshotContext() {
135
135
  const st = s();
136
136
  const ctx = st.currentTreeContext;
137
- return { tree: { id: ctx.id, overflow: ctx.overflow }, localId: st.localIdCounter, treeDepth: _treeDepth };
137
+ const depth = _treeDepth;
138
+ return {
139
+ tree: { id: ctx.id, overflow: ctx.overflow },
140
+ localId: st.localIdCounter,
141
+ treeDepth: depth,
142
+ // Snapshot the live stack so that popTreeContext reads correct saved values
143
+ // even if another concurrent render's resetRenderState stomped the arrays.
144
+ idStack: _treeIdStack.slice(0, depth),
145
+ ovStack: _treeOvStack.slice(0, depth)
146
+ };
138
147
  }
139
148
  function restoreContext(snap) {
140
149
  const st = s();
@@ -143,6 +152,10 @@ function restoreContext(snap) {
143
152
  ctx.overflow = snap.tree.overflow;
144
153
  st.localIdCounter = snap.localId;
145
154
  _treeDepth = snap.treeDepth;
155
+ for (let i = 0; i < snap.treeDepth; i++) {
156
+ _treeIdStack[i] = snap.idStack[i];
157
+ _treeOvStack[i] = snap.ovStack[i];
158
+ }
146
159
  }
147
160
  function getTreeId() {
148
161
  const { id, overflow } = s().currentTreeContext;
@@ -256,6 +269,14 @@ function restoreDispatcher(prev) {
256
269
  }
257
270
 
258
271
  // src/slim-react/render.ts
272
+ function captureRenderCtx() {
273
+ return { m: captureMap(), u: captureUnsuspend(), t: snapshotContext() };
274
+ }
275
+ function restoreRenderCtx(ctx) {
276
+ swapContextMap(ctx.m);
277
+ restoreUnsuspend(ctx.u);
278
+ restoreContext(ctx.t);
279
+ }
259
280
  var VOID_ELEMENTS = /* @__PURE__ */ new Set([
260
281
  "area",
261
282
  "base",
@@ -699,11 +720,9 @@ function renderComponent(type, props, writer, isSvg, _suspenseRetries = 0) {
699
720
  if (e && typeof e.then === "function") {
700
721
  if (_suspenseRetries + 1 >= MAX_COMPONENT_SUSPENSE_RETRIES) throw SUSPENSE_RETRY_LIMIT;
701
722
  patchPromiseStatus(e);
702
- const m = captureMap();
703
- const u = captureUnsuspend();
723
+ const rctx = captureRenderCtx();
704
724
  return e.then(() => {
705
- swapContextMap(m);
706
- restoreUnsuspend(u);
725
+ restoreRenderCtx(rctx);
707
726
  return renderComponent(type, props, writer, isSvg, _suspenseRetries + 1);
708
727
  });
709
728
  }
@@ -740,17 +759,14 @@ function renderComponent(type, props, writer, isSvg, _suspenseRetries = 0) {
740
759
  };
741
760
  const r2 = renderChildren(props.children, writer, isSvg);
742
761
  if (r2 && typeof r2.then === "function") {
743
- const m = captureMap();
744
- const u = captureUnsuspend();
762
+ const rctx = captureRenderCtx();
745
763
  return r2.then(
746
764
  () => {
747
- swapContextMap(m);
748
- restoreUnsuspend(u);
765
+ restoreRenderCtx(rctx);
749
766
  finish();
750
767
  },
751
768
  (e) => {
752
- swapContextMap(m);
753
- restoreUnsuspend(u);
769
+ restoreRenderCtx(rctx);
754
770
  finish();
755
771
  throw e;
756
772
  }
@@ -779,11 +795,9 @@ function renderComponent(type, props, writer, isSvg, _suspenseRetries = 0) {
779
795
  if (e && typeof e.then === "function") {
780
796
  if (_suspenseRetries + 1 >= MAX_COMPONENT_SUSPENSE_RETRIES) throw SUSPENSE_RETRY_LIMIT;
781
797
  patchPromiseStatus(e);
782
- const m = captureMap();
783
- const u = captureUnsuspend();
798
+ const rctx = captureRenderCtx();
784
799
  return e.then(() => {
785
- swapContextMap(m);
786
- restoreUnsuspend(u);
800
+ restoreRenderCtx(rctx);
787
801
  return renderComponent(type, props, writer, isSvg, _suspenseRetries + 1);
788
802
  });
789
803
  }
@@ -795,30 +809,25 @@ function renderComponent(type, props, writer, isSvg, _suspenseRetries = 0) {
795
809
  savedIdTree = pushTreeContext(1, 0);
796
810
  }
797
811
  if (result instanceof Promise) {
798
- const m = captureMap();
799
- const u = captureUnsuspend();
812
+ const rctx = captureRenderCtx();
800
813
  return result.then((resolved) => {
801
- swapContextMap(m);
802
- restoreUnsuspend(u);
814
+ restoreRenderCtx(rctx);
803
815
  let asyncSavedIdTree;
804
816
  if (componentCalledUseId()) {
805
817
  asyncSavedIdTree = pushTreeContext(1, 0);
806
818
  }
807
819
  const r2 = renderNode(resolved, writer, isSvg);
808
820
  if (r2 && typeof r2.then === "function") {
809
- const m2 = captureMap();
810
- const u2 = captureUnsuspend();
821
+ const rctx2 = captureRenderCtx();
811
822
  return r2.then(
812
823
  () => {
813
- swapContextMap(m2);
814
- restoreUnsuspend(u2);
824
+ restoreRenderCtx(rctx2);
815
825
  if (asyncSavedIdTree !== void 0) popTreeContext(asyncSavedIdTree);
816
826
  popComponentScope(savedScope);
817
827
  if (isProvider) popContextValue(ctx, prevCtxValue);
818
828
  },
819
829
  (e) => {
820
- swapContextMap(m2);
821
- restoreUnsuspend(u2);
830
+ restoreRenderCtx(rctx2);
822
831
  if (asyncSavedIdTree !== void 0) popTreeContext(asyncSavedIdTree);
823
832
  popComponentScope(savedScope);
824
833
  if (isProvider) popContextValue(ctx, prevCtxValue);
@@ -830,8 +839,7 @@ function renderComponent(type, props, writer, isSvg, _suspenseRetries = 0) {
830
839
  popComponentScope(savedScope);
831
840
  if (isProvider) popContextValue(ctx, prevCtxValue);
832
841
  }, (e) => {
833
- swapContextMap(m);
834
- restoreUnsuspend(u);
842
+ restoreRenderCtx(rctx);
835
843
  popComponentScope(savedScope);
836
844
  if (isProvider) popContextValue(ctx, prevCtxValue);
837
845
  throw e;
@@ -839,19 +847,16 @@ function renderComponent(type, props, writer, isSvg, _suspenseRetries = 0) {
839
847
  }
840
848
  const r = renderNode(result, writer, isSvg);
841
849
  if (r && typeof r.then === "function") {
842
- const m = captureMap();
843
- const u = captureUnsuspend();
850
+ const rctx = captureRenderCtx();
844
851
  return r.then(
845
852
  () => {
846
- swapContextMap(m);
847
- restoreUnsuspend(u);
853
+ restoreRenderCtx(rctx);
848
854
  if (savedIdTree !== void 0) popTreeContext(savedIdTree);
849
855
  popComponentScope(savedScope);
850
856
  if (isProvider) popContextValue(ctx, prevCtxValue);
851
857
  },
852
858
  (e) => {
853
- swapContextMap(m);
854
- restoreUnsuspend(u);
859
+ restoreRenderCtx(rctx);
855
860
  if (savedIdTree !== void 0) popTreeContext(savedIdTree);
856
861
  popComponentScope(savedScope);
857
862
  if (isProvider) popContextValue(ctx, prevCtxValue);
@@ -876,11 +881,9 @@ function renderChildArrayFrom(children, startIndex, writer, isSvg) {
876
881
  const savedTree = pushTreeContext(totalChildren, i);
877
882
  const r = renderNode(child, writer, isSvg);
878
883
  if (r && typeof r.then === "function") {
879
- const m = captureMap();
880
- const u = captureUnsuspend();
884
+ const rctx = captureRenderCtx();
881
885
  return r.then(() => {
882
- swapContextMap(m);
883
- restoreUnsuspend(u);
886
+ restoreRenderCtx(rctx);
884
887
  popTreeContext(savedTree);
885
888
  return renderChildArrayFrom(children, i + 1, writer, isSvg);
886
889
  });
@@ -904,11 +907,9 @@ async function renderSuspense(props, writer, isSvg = false) {
904
907
  try {
905
908
  const r = renderNode(children, buffer, isSvg);
906
909
  if (r && typeof r.then === "function") {
907
- const m = captureMap();
908
- const u = captureUnsuspend();
910
+ const rctx = captureRenderCtx();
909
911
  await r;
910
- swapContextMap(m);
911
- restoreUnsuspend(u);
912
+ restoreRenderCtx(rctx);
912
913
  }
913
914
  writer.write("<!--$-->");
914
915
  buffer.flushTo(writer);
@@ -922,11 +923,9 @@ async function renderSuspense(props, writer, isSvg = false) {
922
923
  if (fallback) {
923
924
  const r = renderNode(fallback, writer, isSvg);
924
925
  if (r && typeof r.then === "function") {
925
- const m = captureMap();
926
- const u = captureUnsuspend();
926
+ const rctx = captureRenderCtx();
927
927
  await r;
928
- swapContextMap(m);
929
- restoreUnsuspend(u);
928
+ restoreRenderCtx(rctx);
930
929
  }
931
930
  }
932
931
  writer.write("<!--/$-->");
@@ -955,9 +954,9 @@ async function renderToString(element, options) {
955
954
  resetRenderState(idPrefix);
956
955
  const r = renderNode(element, writer);
957
956
  if (r && typeof r.then === "function") {
958
- const m = captureMap();
957
+ const rctx = captureRenderCtx();
959
958
  await r;
960
- swapContextMap(m);
959
+ restoreRenderCtx(rctx);
961
960
  }
962
961
  return output;
963
962
  } finally {
package/dist/ssr-watch.js CHANGED
@@ -265,6 +265,12 @@ var buildCompilerConfig = (entry2, opts, includeHotPlugin) => {
265
265
  // changed files, making repeat dev starts significantly faster.
266
266
  cache: true,
267
267
  externals,
268
+ // externalsPresets.node externalises ALL Node.js built-ins (bare names
269
+ // and the node: prefix) for both static and dynamic imports. This
270
+ // complements the explicit `externals` array: the preset handles the
271
+ // node: URI scheme that rspack cannot resolve as a file, while the
272
+ // array keeps '@emotion/server' as an explicit external.
273
+ ...isServerBuild ? { externalsPresets: { node: true } } : {},
268
274
  ...optimization !== void 0 ? { optimization } : {},
269
275
  plugins: [
270
276
  !isServerBuild && new rspack.HtmlRspackPlugin({
@@ -317,7 +323,7 @@ var buildCompilerConfig = (entry2, opts, includeHotPlugin) => {
317
323
  // SSR watcher writing .hadars/index.ssr.js triggers the client compiler
318
324
  // and vice versa, causing an infinite rebuild loop.
319
325
  watchOptions: {
320
- ignored: ["**/node_modules/**", "**/.hadars/**"]
326
+ ignored: ["**/node_modules/**", "**/.hadars/**", "/tmp/**"]
321
327
  }
322
328
  };
323
329
  };
@@ -326,7 +332,7 @@ var compileEntry = async (entry2, opts) => {
326
332
  if (opts.watch) {
327
333
  await new Promise((resolve, reject) => {
328
334
  let first = true;
329
- compiler.watch({ ignored: ["**/node_modules/**", "**/.hadars/**"] }, (err, stats) => {
335
+ compiler.watch({ ignored: ["**/node_modules/**", "**/.hadars/**", "/tmp/**"] }, (err, stats) => {
330
336
  if (err) {
331
337
  if (first) {
332
338
  first = false;
@@ -359,10 +365,7 @@ var compileEntry = async (entry2, opts) => {
359
365
  }
360
366
  console.log(stats?.toString({
361
367
  colors: true,
362
- modules: true,
363
- children: true,
364
- chunks: true,
365
- chunkModules: true
368
+ preset: "minimal"
366
369
  }));
367
370
  resolve(stats);
368
371
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hadars",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "Minimal SSR framework for React — rspack, HMR, TypeScript, Bun/Node/Deno",
5
5
  "module": "./dist/index.js",
6
6
  "type": "module",
@@ -41,7 +41,7 @@
41
41
  "scripts": {
42
42
  "build:lib": "tsup src/index.tsx src/lambda.ts src/cloudflare.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'",
43
43
  "build:cli": "node build-scripts/build-cli.mjs",
44
- "build:all": "npm run build:lib && npm run build:cli",
44
+ "build:all": "node build-scripts/build-all.mjs",
45
45
  "test": "bun test test/render-compare.test.tsx && bun test test/ssr.test.ts",
46
46
  "prepare": "npm run build:all",
47
47
  "prepublishOnly": "npm run build:all"
package/src/build.ts CHANGED
@@ -701,6 +701,14 @@ export const run = async (options: HadarsRuntimeOptions) => {
701
701
  fs.readFile(pathMod.join(__dirname, StaticPath, 'out.html'), 'utf-8')
702
702
  );
703
703
 
704
+ // Hoist and pre-import the SSR module at startup so the first request does
705
+ // not pay the module parse/eval cost. The file: URL is stable for the life
706
+ // of the process (no cache-busting needed in run mode).
707
+ const componentPath = pathToFileURL(
708
+ pathMod.resolve(__dirname, HadarsFolder, SSR_FILENAME)
709
+ ).href;
710
+ const ssrModulePromise = import(componentPath) as Promise<HadarsEntryModule<any>>;
711
+
704
712
  const runHandler: CacheFetchHandler = async (req, ctx) => {
705
713
  const request = parseRequest(req);
706
714
  if (handler) {
@@ -733,16 +741,12 @@ export const run = async (options: HadarsRuntimeOptions) => {
733
741
  if (routeRes) return routeRes;
734
742
  }
735
743
 
736
- const componentPath = pathToFileURL(
737
- pathMod.resolve(__dirname, HadarsFolder, SSR_FILENAME)
738
- ).href;
739
-
740
744
  try {
741
745
  const {
742
746
  default: Component,
743
747
  getInitProps,
744
748
  getFinalProps,
745
- } = (await import(componentPath)) as HadarsEntryModule<any>;
749
+ } = await ssrModulePromise;
746
750
 
747
751
  if (renderPool && request.headers.get('Accept') !== 'application/json') {
748
752
  // Worker runs the full lifecycle — no non-serializable objects cross the thread boundary.
package/src/lambda.ts CHANGED
@@ -193,20 +193,13 @@ export function createLambdaHandler(options: HadarsOptions, bundled?: LambdaBund
193
193
  ? makePrecontentHtmlGetter(Promise.resolve(bundled.outHtml))
194
194
  : makePrecontentHtmlGetter(fs.readFile(pathMod.join(cwd, StaticPath, 'out.html'), 'utf-8'));
195
195
 
196
- // Hoist the SSR module reference so it is resolved once, not on every
197
- // request. In bundled mode the module is already in-memory; in file-based
198
- // mode we lazily import it and cache the promise so Node's module cache is
199
- // only consulted once.
200
- let ssrModulePromise: Promise<HadarsEntryModule<any>> | null = null;
201
- const getSsrModule = (): Promise<HadarsEntryModule<any>> => {
202
- if (bundled) return Promise.resolve(bundled.ssrModule);
203
- if (!ssrModulePromise) {
204
- ssrModulePromise = import(
205
- pathToFileURL(pathMod.resolve(cwd, HadarsFolder, SSR_FILENAME)).href
206
- ) as Promise<HadarsEntryModule<any>>;
207
- }
208
- return ssrModulePromise;
209
- };
196
+ // Start loading the SSR module immediately during Lambda's init phase this
197
+ // is "free" time not billed against request latency. Lazy loading would
198
+ // push the module parse cost onto the first request.
199
+ const ssrModulePromise: Promise<HadarsEntryModule<any>> = bundled
200
+ ? Promise.resolve(bundled.ssrModule)
201
+ : import(pathToFileURL(pathMod.resolve(cwd, HadarsFolder, SSR_FILENAME)).href) as Promise<HadarsEntryModule<any>>;
202
+ const getSsrModule = () => ssrModulePromise;
210
203
 
211
204
  const runHandler = async (req: Request): Promise<Response> => {
212
205
  const request = parseRequest(req);