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.
- package/README.md +2 -0
- package/dist/{chunk-2KJRDPCN.js → chunk-2J2L2H3H.js} +9 -9
- package/dist/{chunk-LY5MTHFV.js → chunk-TV37IMRB.js} +50 -51
- package/dist/cli.js +70 -67
- package/dist/cloudflare.cjs +56 -57
- package/dist/cloudflare.js +2 -2
- package/dist/lambda.cjs +58 -65
- package/dist/lambda.js +4 -10
- package/dist/slim-react/index.cjs +50 -51
- package/dist/slim-react/index.js +1 -1
- package/dist/ssr-render-worker.js +46 -47
- package/dist/ssr-watch.js +9 -6
- package/package.json +2 -2
- package/src/build.ts +9 -5
- package/src/lambda.ts +7 -14
- package/src/slim-react/render.ts +39 -25
- package/src/slim-react/renderContext.ts +34 -11
- package/src/utils/rspack.ts +9 -6
- package/src/utils/ssrHandler.ts +13 -8
package/src/slim-react/render.ts
CHANGED
|
@@ -38,7 +38,21 @@ import {
|
|
|
38
38
|
captureMap,
|
|
39
39
|
captureUnsuspend,
|
|
40
40
|
restoreUnsuspend,
|
|
41
|
+
type ContextSnapshot,
|
|
41
42
|
} from "./renderContext";
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Capture all three concurrent-render globals in one call.
|
|
46
|
+
* Must be called immediately before every `await` and the returned token
|
|
47
|
+
* passed to restoreRenderCtx immediately after resuming — just like the
|
|
48
|
+
* individual captureMap / captureUnsuspend calls they replace.
|
|
49
|
+
*/
|
|
50
|
+
function captureRenderCtx(): { m: ReturnType<typeof captureMap>; u: unknown; t: ContextSnapshot } {
|
|
51
|
+
return { m: captureMap(), u: captureUnsuspend(), t: snapshotContext() };
|
|
52
|
+
}
|
|
53
|
+
function restoreRenderCtx(ctx: ReturnType<typeof captureRenderCtx>): void {
|
|
54
|
+
swapContextMap(ctx.m); restoreUnsuspend(ctx.u); restoreContext(ctx.t);
|
|
55
|
+
}
|
|
42
56
|
import { installDispatcher, restoreDispatcher } from "./dispatcher";
|
|
43
57
|
|
|
44
58
|
// ---------------------------------------------------------------------------
|
|
@@ -627,9 +641,9 @@ function renderComponent(
|
|
|
627
641
|
if (e && typeof (e as any).then === "function") {
|
|
628
642
|
if (_suspenseRetries + 1 >= MAX_COMPONENT_SUSPENSE_RETRIES) throw SUSPENSE_RETRY_LIMIT;
|
|
629
643
|
patchPromiseStatus(e as Promise<unknown>);
|
|
630
|
-
const
|
|
644
|
+
const rctx = captureRenderCtx();
|
|
631
645
|
return (e as Promise<unknown>).then(() => {
|
|
632
|
-
|
|
646
|
+
restoreRenderCtx(rctx);
|
|
633
647
|
return renderComponent(type, props, writer, isSvg, _suspenseRetries + 1);
|
|
634
648
|
});
|
|
635
649
|
}
|
|
@@ -686,10 +700,10 @@ function renderComponent(
|
|
|
686
700
|
};
|
|
687
701
|
const r = renderChildren(props.children, writer, isSvg);
|
|
688
702
|
if (r && typeof (r as any).then === "function") {
|
|
689
|
-
const
|
|
703
|
+
const rctx = captureRenderCtx();
|
|
690
704
|
return (r as Promise<void>).then(
|
|
691
|
-
() => {
|
|
692
|
-
(e) => {
|
|
705
|
+
() => { restoreRenderCtx(rctx); finish(); },
|
|
706
|
+
(e) => { restoreRenderCtx(rctx); finish(); throw e; },
|
|
693
707
|
);
|
|
694
708
|
}
|
|
695
709
|
finish();
|
|
@@ -722,9 +736,9 @@ function renderComponent(
|
|
|
722
736
|
if (e && typeof (e as any).then === "function") {
|
|
723
737
|
if (_suspenseRetries + 1 >= MAX_COMPONENT_SUSPENSE_RETRIES) throw SUSPENSE_RETRY_LIMIT;
|
|
724
738
|
patchPromiseStatus(e as Promise<unknown>);
|
|
725
|
-
const
|
|
739
|
+
const rctx = captureRenderCtx();
|
|
726
740
|
return (e as Promise<unknown>).then(() => {
|
|
727
|
-
|
|
741
|
+
restoreRenderCtx(rctx);
|
|
728
742
|
return renderComponent(type, props, writer, isSvg, _suspenseRetries + 1);
|
|
729
743
|
});
|
|
730
744
|
}
|
|
@@ -744,9 +758,9 @@ function renderComponent(
|
|
|
744
758
|
|
|
745
759
|
// Async component
|
|
746
760
|
if (result instanceof Promise) {
|
|
747
|
-
const
|
|
761
|
+
const rctx = captureRenderCtx();
|
|
748
762
|
return result.then((resolved) => {
|
|
749
|
-
|
|
763
|
+
restoreRenderCtx(rctx);
|
|
750
764
|
// Check useId after the async body has finished executing.
|
|
751
765
|
let asyncSavedIdTree: number | undefined;
|
|
752
766
|
if (componentCalledUseId()) {
|
|
@@ -754,17 +768,17 @@ function renderComponent(
|
|
|
754
768
|
}
|
|
755
769
|
const r = renderNode(resolved, writer, isSvg);
|
|
756
770
|
if (r && typeof (r as any).then === "function") {
|
|
757
|
-
const
|
|
771
|
+
const rctx2 = captureRenderCtx();
|
|
758
772
|
// Only allocate cleanup closures when actually going async.
|
|
759
773
|
return (r as Promise<void>).then(
|
|
760
774
|
() => {
|
|
761
|
-
|
|
775
|
+
restoreRenderCtx(rctx2);
|
|
762
776
|
if (asyncSavedIdTree !== undefined) popTreeContext(asyncSavedIdTree);
|
|
763
777
|
popComponentScope(savedScope);
|
|
764
778
|
if (isProvider) popContextValue(ctx, prevCtxValue);
|
|
765
779
|
},
|
|
766
780
|
(e) => {
|
|
767
|
-
|
|
781
|
+
restoreRenderCtx(rctx2);
|
|
768
782
|
if (asyncSavedIdTree !== undefined) popTreeContext(asyncSavedIdTree);
|
|
769
783
|
popComponentScope(savedScope);
|
|
770
784
|
if (isProvider) popContextValue(ctx, prevCtxValue);
|
|
@@ -777,7 +791,7 @@ function renderComponent(
|
|
|
777
791
|
popComponentScope(savedScope);
|
|
778
792
|
if (isProvider) popContextValue(ctx, prevCtxValue);
|
|
779
793
|
}, (e) => {
|
|
780
|
-
|
|
794
|
+
restoreRenderCtx(rctx);
|
|
781
795
|
// savedIdTree is always undefined here (async component skips the push).
|
|
782
796
|
popComponentScope(savedScope);
|
|
783
797
|
if (isProvider) popContextValue(ctx, prevCtxValue);
|
|
@@ -788,17 +802,17 @@ function renderComponent(
|
|
|
788
802
|
const r = renderNode(result, writer, isSvg);
|
|
789
803
|
|
|
790
804
|
if (r && typeof (r as any).then === "function") {
|
|
791
|
-
const
|
|
805
|
+
const rctx = captureRenderCtx();
|
|
792
806
|
// Only allocate cleanup closures when actually going async.
|
|
793
807
|
return (r as Promise<void>).then(
|
|
794
808
|
() => {
|
|
795
|
-
|
|
809
|
+
restoreRenderCtx(rctx);
|
|
796
810
|
if (savedIdTree !== undefined) popTreeContext(savedIdTree);
|
|
797
811
|
popComponentScope(savedScope);
|
|
798
812
|
if (isProvider) popContextValue(ctx, prevCtxValue);
|
|
799
813
|
},
|
|
800
814
|
(e) => {
|
|
801
|
-
|
|
815
|
+
restoreRenderCtx(rctx);
|
|
802
816
|
if (savedIdTree !== undefined) popTreeContext(savedIdTree);
|
|
803
817
|
popComponentScope(savedScope);
|
|
804
818
|
if (isProvider) popContextValue(ctx, prevCtxValue);
|
|
@@ -839,9 +853,9 @@ function renderChildArrayFrom(
|
|
|
839
853
|
const savedTree = pushTreeContext(totalChildren, i);
|
|
840
854
|
const r = renderNode(child, writer, isSvg);
|
|
841
855
|
if (r && typeof (r as any).then === "function") {
|
|
842
|
-
const
|
|
856
|
+
const rctx = captureRenderCtx();
|
|
843
857
|
return (r as Promise<void>).then(() => {
|
|
844
|
-
|
|
858
|
+
restoreRenderCtx(rctx);
|
|
845
859
|
popTreeContext(savedTree);
|
|
846
860
|
return renderChildArrayFrom(children, i + 1, writer, isSvg);
|
|
847
861
|
});
|
|
@@ -895,9 +909,9 @@ async function renderSuspense(
|
|
|
895
909
|
try {
|
|
896
910
|
const r = renderNode(children, buffer, isSvg);
|
|
897
911
|
if (r && typeof (r as any).then === "function") {
|
|
898
|
-
const
|
|
912
|
+
const rctx = captureRenderCtx();
|
|
899
913
|
await r;
|
|
900
|
-
|
|
914
|
+
restoreRenderCtx(rctx);
|
|
901
915
|
}
|
|
902
916
|
// Success – wrap with React's Suspense boundary markers so hydrateRoot
|
|
903
917
|
// can locate the boundary in the DOM (<!--$--> … <!--/$-->).
|
|
@@ -918,9 +932,9 @@ async function renderSuspense(
|
|
|
918
932
|
if (fallback) {
|
|
919
933
|
const r = renderNode(fallback, writer, isSvg);
|
|
920
934
|
if (r && typeof (r as any).then === "function") {
|
|
921
|
-
const
|
|
935
|
+
const rctx = captureRenderCtx();
|
|
922
936
|
await r;
|
|
923
|
-
|
|
937
|
+
restoreRenderCtx(rctx);
|
|
924
938
|
}
|
|
925
939
|
}
|
|
926
940
|
writer.write("<!--/$-->");
|
|
@@ -984,7 +998,7 @@ export function renderToStream(
|
|
|
984
998
|
try {
|
|
985
999
|
const r = renderNode(element, writer);
|
|
986
1000
|
if (r && typeof (r as any).then === "function") {
|
|
987
|
-
const
|
|
1001
|
+
const rctx = captureRenderCtx(); await r; restoreRenderCtx(rctx);
|
|
988
1002
|
}
|
|
989
1003
|
writer.flush!(); // encode everything accumulated (sync renders: the whole page)
|
|
990
1004
|
controller.close();
|
|
@@ -1037,7 +1051,7 @@ export async function renderPreflight(
|
|
|
1037
1051
|
// so a single pass is guaranteed to complete with all promises resolved.
|
|
1038
1052
|
const r = renderNode(element, NULL_WRITER);
|
|
1039
1053
|
if (r && typeof (r as any).then === "function") {
|
|
1040
|
-
const
|
|
1054
|
+
const rctx = captureRenderCtx(); await r; restoreRenderCtx(rctx);
|
|
1041
1055
|
}
|
|
1042
1056
|
} finally {
|
|
1043
1057
|
swapContextMap(prev);
|
|
@@ -1075,7 +1089,7 @@ export async function renderToString(
|
|
|
1075
1089
|
// so a single pass is guaranteed to complete with all promises resolved.
|
|
1076
1090
|
const r = renderNode(element, writer);
|
|
1077
1091
|
if (r && typeof (r as any).then === "function") {
|
|
1078
|
-
const
|
|
1092
|
+
const rctx = captureRenderCtx(); await r; restoreRenderCtx(rctx);
|
|
1079
1093
|
}
|
|
1080
1094
|
return output;
|
|
1081
1095
|
} finally {
|
|
@@ -220,21 +220,44 @@ export function componentCalledUseId(): boolean {
|
|
|
220
220
|
return s().localIdCounter > 0;
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
-
export
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
223
|
+
export interface ContextSnapshot {
|
|
224
|
+
tree: TreeContext;
|
|
225
|
+
localId: number;
|
|
226
|
+
treeDepth: number;
|
|
227
|
+
/** Saved parent-context id values for the stack slots 0..treeDepth-1.
|
|
228
|
+
* Required so that concurrent renders cannot corrupt popTreeContext calls
|
|
229
|
+
* that run after an await continuation. */
|
|
230
|
+
idStack: number[];
|
|
231
|
+
ovStack: string[];
|
|
228
232
|
}
|
|
229
233
|
|
|
230
|
-
export function
|
|
234
|
+
export function snapshotContext(): ContextSnapshot {
|
|
235
|
+
const st = s();
|
|
236
|
+
const ctx = st.currentTreeContext;
|
|
237
|
+
const depth = _treeDepth;
|
|
238
|
+
return {
|
|
239
|
+
tree: { id: ctx.id, overflow: ctx.overflow },
|
|
240
|
+
localId: st.localIdCounter,
|
|
241
|
+
treeDepth: depth,
|
|
242
|
+
// Snapshot the live stack so that popTreeContext reads correct saved values
|
|
243
|
+
// even if another concurrent render's resetRenderState stomped the arrays.
|
|
244
|
+
idStack: _treeIdStack.slice(0, depth),
|
|
245
|
+
ovStack: _treeOvStack.slice(0, depth),
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export function restoreContext(snap: ContextSnapshot): void {
|
|
231
250
|
const st = s();
|
|
232
251
|
const ctx = st.currentTreeContext;
|
|
233
|
-
|
|
234
|
-
ctx.
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
252
|
+
ctx.id = snap.tree.id;
|
|
253
|
+
ctx.overflow = snap.tree.overflow;
|
|
254
|
+
st.localIdCounter = snap.localId;
|
|
255
|
+
_treeDepth = snap.treeDepth;
|
|
256
|
+
// Restore the stack so subsequent popTreeContext calls see the right values.
|
|
257
|
+
for (let i = 0; i < snap.treeDepth; i++) {
|
|
258
|
+
_treeIdStack[i] = snap.idStack[i]!;
|
|
259
|
+
_treeOvStack[i] = snap.ovStack[i]!;
|
|
260
|
+
}
|
|
238
261
|
}
|
|
239
262
|
|
|
240
263
|
/**
|
package/src/utils/rspack.ts
CHANGED
|
@@ -338,6 +338,12 @@ const buildCompilerConfig = (
|
|
|
338
338
|
// changed files, making repeat dev starts significantly faster.
|
|
339
339
|
cache: true,
|
|
340
340
|
externals,
|
|
341
|
+
// externalsPresets.node externalises ALL Node.js built-ins (bare names
|
|
342
|
+
// and the node: prefix) for both static and dynamic imports. This
|
|
343
|
+
// complements the explicit `externals` array: the preset handles the
|
|
344
|
+
// node: URI scheme that rspack cannot resolve as a file, while the
|
|
345
|
+
// array keeps '@emotion/server' as an explicit external.
|
|
346
|
+
...(isServerBuild ? { externalsPresets: { node: true } } : {}),
|
|
341
347
|
...(optimization !== undefined ? { optimization } : {}),
|
|
342
348
|
plugins: [
|
|
343
349
|
!isServerBuild && new rspack.HtmlRspackPlugin({
|
|
@@ -393,7 +399,7 @@ const buildCompilerConfig = (
|
|
|
393
399
|
// SSR watcher writing .hadars/index.ssr.js triggers the client compiler
|
|
394
400
|
// and vice versa, causing an infinite rebuild loop.
|
|
395
401
|
watchOptions: {
|
|
396
|
-
ignored: ['**/node_modules/**', '**/.hadars/**'],
|
|
402
|
+
ignored: ['**/node_modules/**', '**/.hadars/**', '/tmp/**'],
|
|
397
403
|
},
|
|
398
404
|
};
|
|
399
405
|
};
|
|
@@ -418,7 +424,7 @@ export const compileEntry = async (entry: string, opts: EntryOptions & { watch?:
|
|
|
418
424
|
let first = true;
|
|
419
425
|
// Pass ignored patterns directly — compiler.watch(watchOptions) replaces
|
|
420
426
|
// the config-level watchOptions, so we must repeat them here.
|
|
421
|
-
compiler.watch({ ignored: ['**/node_modules/**', '**/.hadars/**'] }, (err: any, stats: any) => {
|
|
427
|
+
compiler.watch({ ignored: ['**/node_modules/**', '**/.hadars/**', '/tmp/**'] }, (err: any, stats: any) => {
|
|
422
428
|
if (err) {
|
|
423
429
|
if (first) { first = false; reject(err); }
|
|
424
430
|
else { console.error('rspack watch error', err); }
|
|
@@ -452,10 +458,7 @@ export const compileEntry = async (entry: string, opts: EntryOptions & { watch?:
|
|
|
452
458
|
|
|
453
459
|
console.log(stats?.toString({
|
|
454
460
|
colors: true,
|
|
455
|
-
|
|
456
|
-
children: true,
|
|
457
|
-
chunks: true,
|
|
458
|
-
chunkModules: true,
|
|
461
|
+
preset: 'minimal',
|
|
459
462
|
}));
|
|
460
463
|
|
|
461
464
|
resolve(stats);
|
package/src/utils/ssrHandler.ts
CHANGED
|
@@ -89,6 +89,18 @@ export const makePrecontentHtmlGetter = (htmlFilePromise: Promise<string>) => {
|
|
|
89
89
|
let preHead: string | null = null;
|
|
90
90
|
let postHead: string | null = null;
|
|
91
91
|
let postContent: string | null = null;
|
|
92
|
+
|
|
93
|
+
// Parse the template as soon as the file read completes — during the
|
|
94
|
+
// process's idle time before the first request arrives. This way the
|
|
95
|
+
// first real request finds preHead !== null and takes the sync path.
|
|
96
|
+
const primed = htmlFilePromise.then(html => {
|
|
97
|
+
const headEnd = html.indexOf(HEAD_MARKER);
|
|
98
|
+
const contentStart = html.indexOf(BODY_MARKER);
|
|
99
|
+
preHead = html.slice(0, headEnd);
|
|
100
|
+
postHead = html.slice(headEnd + HEAD_MARKER.length, contentStart);
|
|
101
|
+
postContent = html.slice(contentStart + BODY_MARKER.length);
|
|
102
|
+
});
|
|
103
|
+
|
|
92
104
|
// Returns synchronously once the template has been loaded and parsed
|
|
93
105
|
// (every request after the first). Callers can check `instanceof Promise`
|
|
94
106
|
// to take a zero-await hot path.
|
|
@@ -97,14 +109,7 @@ export const makePrecontentHtmlGetter = (htmlFilePromise: Promise<string>) => {
|
|
|
97
109
|
// Hot path — sync return, no Promise allocation.
|
|
98
110
|
return [preHead + headHtml + postHead!, postContent!];
|
|
99
111
|
}
|
|
100
|
-
return
|
|
101
|
-
const headEnd = html.indexOf(HEAD_MARKER);
|
|
102
|
-
const contentStart = html.indexOf(BODY_MARKER);
|
|
103
|
-
preHead = html.slice(0, headEnd);
|
|
104
|
-
postHead = html.slice(headEnd + HEAD_MARKER.length, contentStart);
|
|
105
|
-
postContent = html.slice(contentStart + BODY_MARKER.length);
|
|
106
|
-
return [preHead + headHtml + postHead, postContent];
|
|
107
|
-
});
|
|
112
|
+
return primed.then(() => [preHead! + headHtml + postHead!, postContent!]);
|
|
108
113
|
};
|
|
109
114
|
};
|
|
110
115
|
|