hadars 0.1.27 → 0.1.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +164 -15
- package/dist/slim-react/index.cjs +108 -23
- package/dist/slim-react/index.js +98 -23
- package/dist/ssr-render-worker.js +164 -15
- package/package.json +1 -1
- package/src/slim-react/dispatcher.ts +69 -0
- package/src/slim-react/render.ts +37 -5
- package/src/slim-react/renderContext.ts +72 -21
package/dist/cli.js
CHANGED
|
@@ -190,7 +190,7 @@ function popContextValue(context, prev) {
|
|
|
190
190
|
_g[MAP_KEY]?.set(context, prev);
|
|
191
191
|
}
|
|
192
192
|
var GLOBAL_KEY = "__slimReactRenderState";
|
|
193
|
-
var EMPTY = { id:
|
|
193
|
+
var EMPTY = { id: 1, overflow: "" };
|
|
194
194
|
function s() {
|
|
195
195
|
const g = globalThis;
|
|
196
196
|
if (!g[GLOBAL_KEY]) {
|
|
@@ -206,20 +206,27 @@ function resetRenderState() {
|
|
|
206
206
|
function pushTreeContext(totalChildren, index) {
|
|
207
207
|
const st = s();
|
|
208
208
|
const saved = { ...st.currentTreeContext };
|
|
209
|
-
const
|
|
209
|
+
const baseIdWithLeadingBit = st.currentTreeContext.id;
|
|
210
|
+
const baseOverflow = st.currentTreeContext.overflow;
|
|
211
|
+
const baseLength = 31 - Math.clz32(baseIdWithLeadingBit);
|
|
212
|
+
let baseId = baseIdWithLeadingBit & ~(1 << baseLength);
|
|
210
213
|
const slot = index + 1;
|
|
211
|
-
const
|
|
212
|
-
|
|
214
|
+
const newBits = 32 - Math.clz32(totalChildren);
|
|
215
|
+
const length = newBits + baseLength;
|
|
216
|
+
if (30 < length) {
|
|
217
|
+
const numberOfOverflowBits = baseLength - baseLength % 5;
|
|
218
|
+
const overflowStr = (baseId & (1 << numberOfOverflowBits) - 1).toString(32);
|
|
219
|
+
baseId >>= numberOfOverflowBits;
|
|
220
|
+
const newBaseLength = baseLength - numberOfOverflowBits;
|
|
213
221
|
st.currentTreeContext = {
|
|
214
|
-
id:
|
|
215
|
-
overflow:
|
|
216
|
-
bits: totalBits
|
|
222
|
+
id: 1 << newBits + newBaseLength | slot << newBaseLength | baseId,
|
|
223
|
+
overflow: overflowStr + baseOverflow
|
|
217
224
|
};
|
|
218
225
|
} else {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
226
|
+
st.currentTreeContext = {
|
|
227
|
+
id: 1 << length | slot << baseLength | baseId,
|
|
228
|
+
overflow: baseOverflow
|
|
229
|
+
};
|
|
223
230
|
}
|
|
224
231
|
return saved;
|
|
225
232
|
}
|
|
@@ -235,6 +242,9 @@ function pushComponentScope() {
|
|
|
235
242
|
function popComponentScope(saved) {
|
|
236
243
|
s().localIdCounter = saved;
|
|
237
244
|
}
|
|
245
|
+
function componentCalledUseId() {
|
|
246
|
+
return s().localIdCounter > 0;
|
|
247
|
+
}
|
|
238
248
|
function snapshotContext() {
|
|
239
249
|
const st = s();
|
|
240
250
|
return { tree: { ...st.currentTreeContext }, localId: st.localIdCounter };
|
|
@@ -244,6 +254,121 @@ function restoreContext(snap) {
|
|
|
244
254
|
st.currentTreeContext = { ...snap.tree };
|
|
245
255
|
st.localIdCounter = snap.localId;
|
|
246
256
|
}
|
|
257
|
+
function getTreeId() {
|
|
258
|
+
const { id, overflow } = s().currentTreeContext;
|
|
259
|
+
if (id === 1)
|
|
260
|
+
return overflow;
|
|
261
|
+
const stripped = (id & ~(1 << 31 - Math.clz32(id))).toString(32);
|
|
262
|
+
return stripped + overflow;
|
|
263
|
+
}
|
|
264
|
+
function makeId() {
|
|
265
|
+
const st = s();
|
|
266
|
+
const treeId = getTreeId();
|
|
267
|
+
const n = st.localIdCounter++;
|
|
268
|
+
let id = "\xAB" + st.idPrefix + "R" + treeId;
|
|
269
|
+
if (n > 0)
|
|
270
|
+
id += "H" + n.toString(32);
|
|
271
|
+
return id + "\xBB";
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// src/slim-react/hooks.ts
|
|
275
|
+
function useState(initialState) {
|
|
276
|
+
const value = typeof initialState === "function" ? initialState() : initialState;
|
|
277
|
+
return [value, () => {
|
|
278
|
+
}];
|
|
279
|
+
}
|
|
280
|
+
function useReducer(_reducer, initialState) {
|
|
281
|
+
return [initialState, () => {
|
|
282
|
+
}];
|
|
283
|
+
}
|
|
284
|
+
function useEffect(_effect, _deps) {
|
|
285
|
+
}
|
|
286
|
+
function useLayoutEffect(_effect, _deps) {
|
|
287
|
+
}
|
|
288
|
+
function useInsertionEffect(_effect, _deps) {
|
|
289
|
+
}
|
|
290
|
+
function useRef(initialValue) {
|
|
291
|
+
return { current: initialValue };
|
|
292
|
+
}
|
|
293
|
+
function useMemo(factory, _deps) {
|
|
294
|
+
return factory();
|
|
295
|
+
}
|
|
296
|
+
function useCallback(callback, _deps) {
|
|
297
|
+
return callback;
|
|
298
|
+
}
|
|
299
|
+
function useDebugValue(_value, _format) {
|
|
300
|
+
}
|
|
301
|
+
function useImperativeHandle(_ref, _createHandle, _deps) {
|
|
302
|
+
}
|
|
303
|
+
function useSyncExternalStore(_subscribe, getSnapshot, getServerSnapshot) {
|
|
304
|
+
return (getServerSnapshot || getSnapshot)();
|
|
305
|
+
}
|
|
306
|
+
function useTransition() {
|
|
307
|
+
return [false, (cb) => cb()];
|
|
308
|
+
}
|
|
309
|
+
function useDeferredValue(value) {
|
|
310
|
+
return value;
|
|
311
|
+
}
|
|
312
|
+
function useOptimistic(passthrough) {
|
|
313
|
+
return [passthrough, () => {
|
|
314
|
+
}];
|
|
315
|
+
}
|
|
316
|
+
function useActionState(_action, initialState, _permalink) {
|
|
317
|
+
return [initialState, () => {
|
|
318
|
+
}, false];
|
|
319
|
+
}
|
|
320
|
+
function use(usable) {
|
|
321
|
+
if (typeof usable === "object" && usable !== null && ("_currentValue" in usable || "_defaultValue" in usable)) {
|
|
322
|
+
return getContextValue(usable);
|
|
323
|
+
}
|
|
324
|
+
const promise = usable;
|
|
325
|
+
if (promise.status === "fulfilled")
|
|
326
|
+
return promise.value;
|
|
327
|
+
if (promise.status === "rejected")
|
|
328
|
+
throw promise.reason;
|
|
329
|
+
throw promise;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// src/slim-react/dispatcher.ts
|
|
333
|
+
import ReactPkg from "react";
|
|
334
|
+
var _internals = ReactPkg.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;
|
|
335
|
+
var slimDispatcher = {
|
|
336
|
+
useId: makeId,
|
|
337
|
+
readContext: (ctx) => getContextValue(ctx),
|
|
338
|
+
useContext: (ctx) => getContextValue(ctx),
|
|
339
|
+
useState,
|
|
340
|
+
useReducer,
|
|
341
|
+
useEffect,
|
|
342
|
+
useLayoutEffect,
|
|
343
|
+
useInsertionEffect,
|
|
344
|
+
useRef,
|
|
345
|
+
useMemo,
|
|
346
|
+
useCallback,
|
|
347
|
+
useDebugValue,
|
|
348
|
+
useImperativeHandle,
|
|
349
|
+
useSyncExternalStore,
|
|
350
|
+
useTransition,
|
|
351
|
+
useDeferredValue,
|
|
352
|
+
useOptimistic,
|
|
353
|
+
useActionState,
|
|
354
|
+
use,
|
|
355
|
+
// React internals that compiled output may call
|
|
356
|
+
useMemoCache: (size) => new Array(size).fill(void 0),
|
|
357
|
+
useCacheRefresh: () => () => {
|
|
358
|
+
},
|
|
359
|
+
useHostTransitionStatus: () => false
|
|
360
|
+
};
|
|
361
|
+
function installDispatcher() {
|
|
362
|
+
if (!_internals)
|
|
363
|
+
return null;
|
|
364
|
+
const prev = _internals.H;
|
|
365
|
+
_internals.H = slimDispatcher;
|
|
366
|
+
return prev;
|
|
367
|
+
}
|
|
368
|
+
function restoreDispatcher(prev) {
|
|
369
|
+
if (_internals)
|
|
370
|
+
_internals.H = prev;
|
|
371
|
+
}
|
|
247
372
|
|
|
248
373
|
// src/slim-react/render.ts
|
|
249
374
|
var VOID_ELEMENTS = /* @__PURE__ */ new Set([
|
|
@@ -425,7 +550,11 @@ function renderAttributes(props, isSvg) {
|
|
|
425
550
|
continue;
|
|
426
551
|
}
|
|
427
552
|
if (value === true) {
|
|
428
|
-
|
|
553
|
+
if (attrName.startsWith("aria-") || attrName.startsWith("data-")) {
|
|
554
|
+
attrs += ` ${attrName}="true"`;
|
|
555
|
+
} else {
|
|
556
|
+
attrs += ` ${attrName}=""`;
|
|
557
|
+
}
|
|
429
558
|
continue;
|
|
430
559
|
}
|
|
431
560
|
if (key === "style" && typeof value === "object") {
|
|
@@ -644,6 +773,7 @@ function renderComponent(type, props, writer, isSvg) {
|
|
|
644
773
|
return;
|
|
645
774
|
}
|
|
646
775
|
let result;
|
|
776
|
+
const prevDispatcher = installDispatcher();
|
|
647
777
|
try {
|
|
648
778
|
if (type.prototype && typeof type.prototype.render === "function") {
|
|
649
779
|
const instance = new type(props);
|
|
@@ -657,12 +787,20 @@ function renderComponent(type, props, writer, isSvg) {
|
|
|
657
787
|
result = type(props);
|
|
658
788
|
}
|
|
659
789
|
} catch (e) {
|
|
790
|
+
restoreDispatcher(prevDispatcher);
|
|
660
791
|
popComponentScope(savedScope);
|
|
661
792
|
if (isProvider)
|
|
662
793
|
popContextValue(ctx, prevCtxValue);
|
|
663
794
|
throw e;
|
|
664
795
|
}
|
|
796
|
+
restoreDispatcher(prevDispatcher);
|
|
797
|
+
let savedIdTree;
|
|
798
|
+
if (!(result instanceof Promise) && componentCalledUseId()) {
|
|
799
|
+
savedIdTree = pushTreeContext(1, 0);
|
|
800
|
+
}
|
|
665
801
|
const finish = () => {
|
|
802
|
+
if (savedIdTree !== void 0)
|
|
803
|
+
popTreeContext(savedIdTree);
|
|
666
804
|
popComponentScope(savedScope);
|
|
667
805
|
if (isProvider)
|
|
668
806
|
popContextValue(ctx, prevCtxValue);
|
|
@@ -671,22 +809,33 @@ function renderComponent(type, props, writer, isSvg) {
|
|
|
671
809
|
const m = captureMap();
|
|
672
810
|
return result.then((resolved) => {
|
|
673
811
|
swapContextMap(m);
|
|
812
|
+
let asyncSavedIdTree;
|
|
813
|
+
if (componentCalledUseId()) {
|
|
814
|
+
asyncSavedIdTree = pushTreeContext(1, 0);
|
|
815
|
+
}
|
|
816
|
+
const asyncFinish = () => {
|
|
817
|
+
if (asyncSavedIdTree !== void 0)
|
|
818
|
+
popTreeContext(asyncSavedIdTree);
|
|
819
|
+
popComponentScope(savedScope);
|
|
820
|
+
if (isProvider)
|
|
821
|
+
popContextValue(ctx, prevCtxValue);
|
|
822
|
+
};
|
|
674
823
|
const r2 = renderNode(resolved, writer, isSvg);
|
|
675
824
|
if (r2 && typeof r2.then === "function") {
|
|
676
825
|
const m2 = captureMap();
|
|
677
826
|
return r2.then(
|
|
678
827
|
() => {
|
|
679
828
|
swapContextMap(m2);
|
|
680
|
-
|
|
829
|
+
asyncFinish();
|
|
681
830
|
},
|
|
682
831
|
(e) => {
|
|
683
832
|
swapContextMap(m2);
|
|
684
|
-
|
|
833
|
+
asyncFinish();
|
|
685
834
|
throw e;
|
|
686
835
|
}
|
|
687
836
|
);
|
|
688
837
|
}
|
|
689
|
-
|
|
838
|
+
asyncFinish();
|
|
690
839
|
}, (e) => {
|
|
691
840
|
swapContextMap(m);
|
|
692
841
|
finish();
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/slim-react/index.ts
|
|
@@ -130,7 +140,7 @@ function popContextValue(context, prev) {
|
|
|
130
140
|
_g[MAP_KEY]?.set(context, prev);
|
|
131
141
|
}
|
|
132
142
|
var GLOBAL_KEY = "__slimReactRenderState";
|
|
133
|
-
var EMPTY = { id:
|
|
143
|
+
var EMPTY = { id: 1, overflow: "" };
|
|
134
144
|
function s() {
|
|
135
145
|
const g = globalThis;
|
|
136
146
|
if (!g[GLOBAL_KEY]) {
|
|
@@ -146,20 +156,27 @@ function resetRenderState() {
|
|
|
146
156
|
function pushTreeContext(totalChildren, index) {
|
|
147
157
|
const st = s();
|
|
148
158
|
const saved = { ...st.currentTreeContext };
|
|
149
|
-
const
|
|
159
|
+
const baseIdWithLeadingBit = st.currentTreeContext.id;
|
|
160
|
+
const baseOverflow = st.currentTreeContext.overflow;
|
|
161
|
+
const baseLength = 31 - Math.clz32(baseIdWithLeadingBit);
|
|
162
|
+
let baseId = baseIdWithLeadingBit & ~(1 << baseLength);
|
|
150
163
|
const slot = index + 1;
|
|
151
|
-
const
|
|
152
|
-
|
|
164
|
+
const newBits = 32 - Math.clz32(totalChildren);
|
|
165
|
+
const length = newBits + baseLength;
|
|
166
|
+
if (30 < length) {
|
|
167
|
+
const numberOfOverflowBits = baseLength - baseLength % 5;
|
|
168
|
+
const overflowStr = (baseId & (1 << numberOfOverflowBits) - 1).toString(32);
|
|
169
|
+
baseId >>= numberOfOverflowBits;
|
|
170
|
+
const newBaseLength = baseLength - numberOfOverflowBits;
|
|
153
171
|
st.currentTreeContext = {
|
|
154
|
-
id:
|
|
155
|
-
overflow:
|
|
156
|
-
bits: totalBits
|
|
172
|
+
id: 1 << newBits + newBaseLength | slot << newBaseLength | baseId,
|
|
173
|
+
overflow: overflowStr + baseOverflow
|
|
157
174
|
};
|
|
158
175
|
} else {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
176
|
+
st.currentTreeContext = {
|
|
177
|
+
id: 1 << length | slot << baseLength | baseId,
|
|
178
|
+
overflow: baseOverflow
|
|
179
|
+
};
|
|
163
180
|
}
|
|
164
181
|
return saved;
|
|
165
182
|
}
|
|
@@ -175,6 +192,9 @@ function pushComponentScope() {
|
|
|
175
192
|
function popComponentScope(saved) {
|
|
176
193
|
s().localIdCounter = saved;
|
|
177
194
|
}
|
|
195
|
+
function componentCalledUseId() {
|
|
196
|
+
return s().localIdCounter > 0;
|
|
197
|
+
}
|
|
178
198
|
function snapshotContext() {
|
|
179
199
|
const st = s();
|
|
180
200
|
return { tree: { ...st.currentTreeContext }, localId: st.localIdCounter };
|
|
@@ -185,20 +205,20 @@ function restoreContext(snap) {
|
|
|
185
205
|
st.localIdCounter = snap.localId;
|
|
186
206
|
}
|
|
187
207
|
function getTreeId() {
|
|
188
|
-
const { id, overflow
|
|
189
|
-
|
|
208
|
+
const { id, overflow } = s().currentTreeContext;
|
|
209
|
+
if (id === 1)
|
|
210
|
+
return overflow;
|
|
211
|
+
const stripped = (id & ~(1 << 31 - Math.clz32(id))).toString(32);
|
|
212
|
+
return stripped + overflow;
|
|
190
213
|
}
|
|
191
214
|
function makeId() {
|
|
192
215
|
const st = s();
|
|
193
216
|
const treeId = getTreeId();
|
|
194
217
|
const n = st.localIdCounter++;
|
|
195
|
-
let id = "
|
|
196
|
-
if (treeId.length > 0)
|
|
197
|
-
id += treeId;
|
|
198
|
-
id += ":";
|
|
218
|
+
let id = "\xAB" + st.idPrefix + "R" + treeId;
|
|
199
219
|
if (n > 0)
|
|
200
|
-
id += "H" + n.toString(32)
|
|
201
|
-
return id;
|
|
220
|
+
id += "H" + n.toString(32);
|
|
221
|
+
return id + "\xBB";
|
|
202
222
|
}
|
|
203
223
|
|
|
204
224
|
// src/slim-react/hooks.ts
|
|
@@ -291,6 +311,47 @@ function createContext(defaultValue) {
|
|
|
291
311
|
return context;
|
|
292
312
|
}
|
|
293
313
|
|
|
314
|
+
// src/slim-react/dispatcher.ts
|
|
315
|
+
var import_react = __toESM(require("react"), 1);
|
|
316
|
+
var _internals = import_react.default.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;
|
|
317
|
+
var slimDispatcher = {
|
|
318
|
+
useId: makeId,
|
|
319
|
+
readContext: (ctx) => getContextValue(ctx),
|
|
320
|
+
useContext: (ctx) => getContextValue(ctx),
|
|
321
|
+
useState,
|
|
322
|
+
useReducer,
|
|
323
|
+
useEffect,
|
|
324
|
+
useLayoutEffect,
|
|
325
|
+
useInsertionEffect,
|
|
326
|
+
useRef,
|
|
327
|
+
useMemo,
|
|
328
|
+
useCallback,
|
|
329
|
+
useDebugValue,
|
|
330
|
+
useImperativeHandle,
|
|
331
|
+
useSyncExternalStore,
|
|
332
|
+
useTransition,
|
|
333
|
+
useDeferredValue,
|
|
334
|
+
useOptimistic,
|
|
335
|
+
useActionState,
|
|
336
|
+
use,
|
|
337
|
+
// React internals that compiled output may call
|
|
338
|
+
useMemoCache: (size) => new Array(size).fill(void 0),
|
|
339
|
+
useCacheRefresh: () => () => {
|
|
340
|
+
},
|
|
341
|
+
useHostTransitionStatus: () => false
|
|
342
|
+
};
|
|
343
|
+
function installDispatcher() {
|
|
344
|
+
if (!_internals)
|
|
345
|
+
return null;
|
|
346
|
+
const prev = _internals.H;
|
|
347
|
+
_internals.H = slimDispatcher;
|
|
348
|
+
return prev;
|
|
349
|
+
}
|
|
350
|
+
function restoreDispatcher(prev) {
|
|
351
|
+
if (_internals)
|
|
352
|
+
_internals.H = prev;
|
|
353
|
+
}
|
|
354
|
+
|
|
294
355
|
// src/slim-react/render.ts
|
|
295
356
|
var VOID_ELEMENTS = /* @__PURE__ */ new Set([
|
|
296
357
|
"area",
|
|
@@ -471,7 +532,11 @@ function renderAttributes(props, isSvg) {
|
|
|
471
532
|
continue;
|
|
472
533
|
}
|
|
473
534
|
if (value === true) {
|
|
474
|
-
|
|
535
|
+
if (attrName.startsWith("aria-") || attrName.startsWith("data-")) {
|
|
536
|
+
attrs += ` ${attrName}="true"`;
|
|
537
|
+
} else {
|
|
538
|
+
attrs += ` ${attrName}=""`;
|
|
539
|
+
}
|
|
475
540
|
continue;
|
|
476
541
|
}
|
|
477
542
|
if (key === "style" && typeof value === "object") {
|
|
@@ -690,6 +755,7 @@ function renderComponent(type, props, writer, isSvg) {
|
|
|
690
755
|
return;
|
|
691
756
|
}
|
|
692
757
|
let result;
|
|
758
|
+
const prevDispatcher = installDispatcher();
|
|
693
759
|
try {
|
|
694
760
|
if (type.prototype && typeof type.prototype.render === "function") {
|
|
695
761
|
const instance = new type(props);
|
|
@@ -703,12 +769,20 @@ function renderComponent(type, props, writer, isSvg) {
|
|
|
703
769
|
result = type(props);
|
|
704
770
|
}
|
|
705
771
|
} catch (e) {
|
|
772
|
+
restoreDispatcher(prevDispatcher);
|
|
706
773
|
popComponentScope(savedScope);
|
|
707
774
|
if (isProvider)
|
|
708
775
|
popContextValue(ctx, prevCtxValue);
|
|
709
776
|
throw e;
|
|
710
777
|
}
|
|
778
|
+
restoreDispatcher(prevDispatcher);
|
|
779
|
+
let savedIdTree;
|
|
780
|
+
if (!(result instanceof Promise) && componentCalledUseId()) {
|
|
781
|
+
savedIdTree = pushTreeContext(1, 0);
|
|
782
|
+
}
|
|
711
783
|
const finish = () => {
|
|
784
|
+
if (savedIdTree !== void 0)
|
|
785
|
+
popTreeContext(savedIdTree);
|
|
712
786
|
popComponentScope(savedScope);
|
|
713
787
|
if (isProvider)
|
|
714
788
|
popContextValue(ctx, prevCtxValue);
|
|
@@ -717,22 +791,33 @@ function renderComponent(type, props, writer, isSvg) {
|
|
|
717
791
|
const m = captureMap();
|
|
718
792
|
return result.then((resolved) => {
|
|
719
793
|
swapContextMap(m);
|
|
794
|
+
let asyncSavedIdTree;
|
|
795
|
+
if (componentCalledUseId()) {
|
|
796
|
+
asyncSavedIdTree = pushTreeContext(1, 0);
|
|
797
|
+
}
|
|
798
|
+
const asyncFinish = () => {
|
|
799
|
+
if (asyncSavedIdTree !== void 0)
|
|
800
|
+
popTreeContext(asyncSavedIdTree);
|
|
801
|
+
popComponentScope(savedScope);
|
|
802
|
+
if (isProvider)
|
|
803
|
+
popContextValue(ctx, prevCtxValue);
|
|
804
|
+
};
|
|
720
805
|
const r2 = renderNode(resolved, writer, isSvg);
|
|
721
806
|
if (r2 && typeof r2.then === "function") {
|
|
722
807
|
const m2 = captureMap();
|
|
723
808
|
return r2.then(
|
|
724
809
|
() => {
|
|
725
810
|
swapContextMap(m2);
|
|
726
|
-
|
|
811
|
+
asyncFinish();
|
|
727
812
|
},
|
|
728
813
|
(e) => {
|
|
729
814
|
swapContextMap(m2);
|
|
730
|
-
|
|
815
|
+
asyncFinish();
|
|
731
816
|
throw e;
|
|
732
817
|
}
|
|
733
818
|
);
|
|
734
819
|
}
|
|
735
|
-
|
|
820
|
+
asyncFinish();
|
|
736
821
|
}, (e) => {
|
|
737
822
|
swapContextMap(m);
|
|
738
823
|
finish();
|
package/dist/slim-react/index.js
CHANGED
|
@@ -39,7 +39,7 @@ function popContextValue(context, prev) {
|
|
|
39
39
|
_g[MAP_KEY]?.set(context, prev);
|
|
40
40
|
}
|
|
41
41
|
var GLOBAL_KEY = "__slimReactRenderState";
|
|
42
|
-
var EMPTY = { id:
|
|
42
|
+
var EMPTY = { id: 1, overflow: "" };
|
|
43
43
|
function s() {
|
|
44
44
|
const g = globalThis;
|
|
45
45
|
if (!g[GLOBAL_KEY]) {
|
|
@@ -55,20 +55,27 @@ function resetRenderState() {
|
|
|
55
55
|
function pushTreeContext(totalChildren, index) {
|
|
56
56
|
const st = s();
|
|
57
57
|
const saved = { ...st.currentTreeContext };
|
|
58
|
-
const
|
|
58
|
+
const baseIdWithLeadingBit = st.currentTreeContext.id;
|
|
59
|
+
const baseOverflow = st.currentTreeContext.overflow;
|
|
60
|
+
const baseLength = 31 - Math.clz32(baseIdWithLeadingBit);
|
|
61
|
+
let baseId = baseIdWithLeadingBit & ~(1 << baseLength);
|
|
59
62
|
const slot = index + 1;
|
|
60
|
-
const
|
|
61
|
-
|
|
63
|
+
const newBits = 32 - Math.clz32(totalChildren);
|
|
64
|
+
const length = newBits + baseLength;
|
|
65
|
+
if (30 < length) {
|
|
66
|
+
const numberOfOverflowBits = baseLength - baseLength % 5;
|
|
67
|
+
const overflowStr = (baseId & (1 << numberOfOverflowBits) - 1).toString(32);
|
|
68
|
+
baseId >>= numberOfOverflowBits;
|
|
69
|
+
const newBaseLength = baseLength - numberOfOverflowBits;
|
|
62
70
|
st.currentTreeContext = {
|
|
63
|
-
id:
|
|
64
|
-
overflow:
|
|
65
|
-
bits: totalBits
|
|
71
|
+
id: 1 << newBits + newBaseLength | slot << newBaseLength | baseId,
|
|
72
|
+
overflow: overflowStr + baseOverflow
|
|
66
73
|
};
|
|
67
74
|
} else {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
75
|
+
st.currentTreeContext = {
|
|
76
|
+
id: 1 << length | slot << baseLength | baseId,
|
|
77
|
+
overflow: baseOverflow
|
|
78
|
+
};
|
|
72
79
|
}
|
|
73
80
|
return saved;
|
|
74
81
|
}
|
|
@@ -84,6 +91,9 @@ function pushComponentScope() {
|
|
|
84
91
|
function popComponentScope(saved) {
|
|
85
92
|
s().localIdCounter = saved;
|
|
86
93
|
}
|
|
94
|
+
function componentCalledUseId() {
|
|
95
|
+
return s().localIdCounter > 0;
|
|
96
|
+
}
|
|
87
97
|
function snapshotContext() {
|
|
88
98
|
const st = s();
|
|
89
99
|
return { tree: { ...st.currentTreeContext }, localId: st.localIdCounter };
|
|
@@ -94,20 +104,20 @@ function restoreContext(snap) {
|
|
|
94
104
|
st.localIdCounter = snap.localId;
|
|
95
105
|
}
|
|
96
106
|
function getTreeId() {
|
|
97
|
-
const { id, overflow
|
|
98
|
-
|
|
107
|
+
const { id, overflow } = s().currentTreeContext;
|
|
108
|
+
if (id === 1)
|
|
109
|
+
return overflow;
|
|
110
|
+
const stripped = (id & ~(1 << 31 - Math.clz32(id))).toString(32);
|
|
111
|
+
return stripped + overflow;
|
|
99
112
|
}
|
|
100
113
|
function makeId() {
|
|
101
114
|
const st = s();
|
|
102
115
|
const treeId = getTreeId();
|
|
103
116
|
const n = st.localIdCounter++;
|
|
104
|
-
let id = "
|
|
105
|
-
if (treeId.length > 0)
|
|
106
|
-
id += treeId;
|
|
107
|
-
id += ":";
|
|
117
|
+
let id = "\xAB" + st.idPrefix + "R" + treeId;
|
|
108
118
|
if (n > 0)
|
|
109
|
-
id += "H" + n.toString(32)
|
|
110
|
-
return id;
|
|
119
|
+
id += "H" + n.toString(32);
|
|
120
|
+
return id + "\xBB";
|
|
111
121
|
}
|
|
112
122
|
|
|
113
123
|
// src/slim-react/hooks.ts
|
|
@@ -200,6 +210,47 @@ function createContext(defaultValue) {
|
|
|
200
210
|
return context;
|
|
201
211
|
}
|
|
202
212
|
|
|
213
|
+
// src/slim-react/dispatcher.ts
|
|
214
|
+
import ReactPkg from "react";
|
|
215
|
+
var _internals = ReactPkg.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;
|
|
216
|
+
var slimDispatcher = {
|
|
217
|
+
useId: makeId,
|
|
218
|
+
readContext: (ctx) => getContextValue(ctx),
|
|
219
|
+
useContext: (ctx) => getContextValue(ctx),
|
|
220
|
+
useState,
|
|
221
|
+
useReducer,
|
|
222
|
+
useEffect,
|
|
223
|
+
useLayoutEffect,
|
|
224
|
+
useInsertionEffect,
|
|
225
|
+
useRef,
|
|
226
|
+
useMemo,
|
|
227
|
+
useCallback,
|
|
228
|
+
useDebugValue,
|
|
229
|
+
useImperativeHandle,
|
|
230
|
+
useSyncExternalStore,
|
|
231
|
+
useTransition,
|
|
232
|
+
useDeferredValue,
|
|
233
|
+
useOptimistic,
|
|
234
|
+
useActionState,
|
|
235
|
+
use,
|
|
236
|
+
// React internals that compiled output may call
|
|
237
|
+
useMemoCache: (size) => new Array(size).fill(void 0),
|
|
238
|
+
useCacheRefresh: () => () => {
|
|
239
|
+
},
|
|
240
|
+
useHostTransitionStatus: () => false
|
|
241
|
+
};
|
|
242
|
+
function installDispatcher() {
|
|
243
|
+
if (!_internals)
|
|
244
|
+
return null;
|
|
245
|
+
const prev = _internals.H;
|
|
246
|
+
_internals.H = slimDispatcher;
|
|
247
|
+
return prev;
|
|
248
|
+
}
|
|
249
|
+
function restoreDispatcher(prev) {
|
|
250
|
+
if (_internals)
|
|
251
|
+
_internals.H = prev;
|
|
252
|
+
}
|
|
253
|
+
|
|
203
254
|
// src/slim-react/render.ts
|
|
204
255
|
var VOID_ELEMENTS = /* @__PURE__ */ new Set([
|
|
205
256
|
"area",
|
|
@@ -380,7 +431,11 @@ function renderAttributes(props, isSvg) {
|
|
|
380
431
|
continue;
|
|
381
432
|
}
|
|
382
433
|
if (value === true) {
|
|
383
|
-
|
|
434
|
+
if (attrName.startsWith("aria-") || attrName.startsWith("data-")) {
|
|
435
|
+
attrs += ` ${attrName}="true"`;
|
|
436
|
+
} else {
|
|
437
|
+
attrs += ` ${attrName}=""`;
|
|
438
|
+
}
|
|
384
439
|
continue;
|
|
385
440
|
}
|
|
386
441
|
if (key === "style" && typeof value === "object") {
|
|
@@ -599,6 +654,7 @@ function renderComponent(type, props, writer, isSvg) {
|
|
|
599
654
|
return;
|
|
600
655
|
}
|
|
601
656
|
let result;
|
|
657
|
+
const prevDispatcher = installDispatcher();
|
|
602
658
|
try {
|
|
603
659
|
if (type.prototype && typeof type.prototype.render === "function") {
|
|
604
660
|
const instance = new type(props);
|
|
@@ -612,12 +668,20 @@ function renderComponent(type, props, writer, isSvg) {
|
|
|
612
668
|
result = type(props);
|
|
613
669
|
}
|
|
614
670
|
} catch (e) {
|
|
671
|
+
restoreDispatcher(prevDispatcher);
|
|
615
672
|
popComponentScope(savedScope);
|
|
616
673
|
if (isProvider)
|
|
617
674
|
popContextValue(ctx, prevCtxValue);
|
|
618
675
|
throw e;
|
|
619
676
|
}
|
|
677
|
+
restoreDispatcher(prevDispatcher);
|
|
678
|
+
let savedIdTree;
|
|
679
|
+
if (!(result instanceof Promise) && componentCalledUseId()) {
|
|
680
|
+
savedIdTree = pushTreeContext(1, 0);
|
|
681
|
+
}
|
|
620
682
|
const finish = () => {
|
|
683
|
+
if (savedIdTree !== void 0)
|
|
684
|
+
popTreeContext(savedIdTree);
|
|
621
685
|
popComponentScope(savedScope);
|
|
622
686
|
if (isProvider)
|
|
623
687
|
popContextValue(ctx, prevCtxValue);
|
|
@@ -626,22 +690,33 @@ function renderComponent(type, props, writer, isSvg) {
|
|
|
626
690
|
const m = captureMap();
|
|
627
691
|
return result.then((resolved) => {
|
|
628
692
|
swapContextMap(m);
|
|
693
|
+
let asyncSavedIdTree;
|
|
694
|
+
if (componentCalledUseId()) {
|
|
695
|
+
asyncSavedIdTree = pushTreeContext(1, 0);
|
|
696
|
+
}
|
|
697
|
+
const asyncFinish = () => {
|
|
698
|
+
if (asyncSavedIdTree !== void 0)
|
|
699
|
+
popTreeContext(asyncSavedIdTree);
|
|
700
|
+
popComponentScope(savedScope);
|
|
701
|
+
if (isProvider)
|
|
702
|
+
popContextValue(ctx, prevCtxValue);
|
|
703
|
+
};
|
|
629
704
|
const r2 = renderNode(resolved, writer, isSvg);
|
|
630
705
|
if (r2 && typeof r2.then === "function") {
|
|
631
706
|
const m2 = captureMap();
|
|
632
707
|
return r2.then(
|
|
633
708
|
() => {
|
|
634
709
|
swapContextMap(m2);
|
|
635
|
-
|
|
710
|
+
asyncFinish();
|
|
636
711
|
},
|
|
637
712
|
(e) => {
|
|
638
713
|
swapContextMap(m2);
|
|
639
|
-
|
|
714
|
+
asyncFinish();
|
|
640
715
|
throw e;
|
|
641
716
|
}
|
|
642
717
|
);
|
|
643
718
|
}
|
|
644
|
-
|
|
719
|
+
asyncFinish();
|
|
645
720
|
}, (e) => {
|
|
646
721
|
swapContextMap(m);
|
|
647
722
|
finish();
|
|
@@ -97,7 +97,7 @@ function popContextValue(context, prev) {
|
|
|
97
97
|
_g[MAP_KEY]?.set(context, prev);
|
|
98
98
|
}
|
|
99
99
|
var GLOBAL_KEY = "__slimReactRenderState";
|
|
100
|
-
var EMPTY = { id:
|
|
100
|
+
var EMPTY = { id: 1, overflow: "" };
|
|
101
101
|
function s() {
|
|
102
102
|
const g = globalThis;
|
|
103
103
|
if (!g[GLOBAL_KEY]) {
|
|
@@ -113,20 +113,27 @@ function resetRenderState() {
|
|
|
113
113
|
function pushTreeContext(totalChildren, index) {
|
|
114
114
|
const st = s();
|
|
115
115
|
const saved = { ...st.currentTreeContext };
|
|
116
|
-
const
|
|
116
|
+
const baseIdWithLeadingBit = st.currentTreeContext.id;
|
|
117
|
+
const baseOverflow = st.currentTreeContext.overflow;
|
|
118
|
+
const baseLength = 31 - Math.clz32(baseIdWithLeadingBit);
|
|
119
|
+
let baseId = baseIdWithLeadingBit & ~(1 << baseLength);
|
|
117
120
|
const slot = index + 1;
|
|
118
|
-
const
|
|
119
|
-
|
|
121
|
+
const newBits = 32 - Math.clz32(totalChildren);
|
|
122
|
+
const length = newBits + baseLength;
|
|
123
|
+
if (30 < length) {
|
|
124
|
+
const numberOfOverflowBits = baseLength - baseLength % 5;
|
|
125
|
+
const overflowStr = (baseId & (1 << numberOfOverflowBits) - 1).toString(32);
|
|
126
|
+
baseId >>= numberOfOverflowBits;
|
|
127
|
+
const newBaseLength = baseLength - numberOfOverflowBits;
|
|
120
128
|
st.currentTreeContext = {
|
|
121
|
-
id:
|
|
122
|
-
overflow:
|
|
123
|
-
bits: totalBits
|
|
129
|
+
id: 1 << newBits + newBaseLength | slot << newBaseLength | baseId,
|
|
130
|
+
overflow: overflowStr + baseOverflow
|
|
124
131
|
};
|
|
125
132
|
} else {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
133
|
+
st.currentTreeContext = {
|
|
134
|
+
id: 1 << length | slot << baseLength | baseId,
|
|
135
|
+
overflow: baseOverflow
|
|
136
|
+
};
|
|
130
137
|
}
|
|
131
138
|
return saved;
|
|
132
139
|
}
|
|
@@ -142,6 +149,9 @@ function pushComponentScope() {
|
|
|
142
149
|
function popComponentScope(saved) {
|
|
143
150
|
s().localIdCounter = saved;
|
|
144
151
|
}
|
|
152
|
+
function componentCalledUseId() {
|
|
153
|
+
return s().localIdCounter > 0;
|
|
154
|
+
}
|
|
145
155
|
function snapshotContext() {
|
|
146
156
|
const st = s();
|
|
147
157
|
return { tree: { ...st.currentTreeContext }, localId: st.localIdCounter };
|
|
@@ -151,6 +161,121 @@ function restoreContext(snap) {
|
|
|
151
161
|
st.currentTreeContext = { ...snap.tree };
|
|
152
162
|
st.localIdCounter = snap.localId;
|
|
153
163
|
}
|
|
164
|
+
function getTreeId() {
|
|
165
|
+
const { id, overflow } = s().currentTreeContext;
|
|
166
|
+
if (id === 1)
|
|
167
|
+
return overflow;
|
|
168
|
+
const stripped = (id & ~(1 << 31 - Math.clz32(id))).toString(32);
|
|
169
|
+
return stripped + overflow;
|
|
170
|
+
}
|
|
171
|
+
function makeId() {
|
|
172
|
+
const st = s();
|
|
173
|
+
const treeId = getTreeId();
|
|
174
|
+
const n = st.localIdCounter++;
|
|
175
|
+
let id = "\xAB" + st.idPrefix + "R" + treeId;
|
|
176
|
+
if (n > 0)
|
|
177
|
+
id += "H" + n.toString(32);
|
|
178
|
+
return id + "\xBB";
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// src/slim-react/hooks.ts
|
|
182
|
+
function useState(initialState) {
|
|
183
|
+
const value = typeof initialState === "function" ? initialState() : initialState;
|
|
184
|
+
return [value, () => {
|
|
185
|
+
}];
|
|
186
|
+
}
|
|
187
|
+
function useReducer(_reducer, initialState) {
|
|
188
|
+
return [initialState, () => {
|
|
189
|
+
}];
|
|
190
|
+
}
|
|
191
|
+
function useEffect(_effect, _deps) {
|
|
192
|
+
}
|
|
193
|
+
function useLayoutEffect(_effect, _deps) {
|
|
194
|
+
}
|
|
195
|
+
function useInsertionEffect(_effect, _deps) {
|
|
196
|
+
}
|
|
197
|
+
function useRef(initialValue) {
|
|
198
|
+
return { current: initialValue };
|
|
199
|
+
}
|
|
200
|
+
function useMemo(factory, _deps) {
|
|
201
|
+
return factory();
|
|
202
|
+
}
|
|
203
|
+
function useCallback(callback, _deps) {
|
|
204
|
+
return callback;
|
|
205
|
+
}
|
|
206
|
+
function useDebugValue(_value, _format) {
|
|
207
|
+
}
|
|
208
|
+
function useImperativeHandle(_ref, _createHandle, _deps) {
|
|
209
|
+
}
|
|
210
|
+
function useSyncExternalStore(_subscribe, getSnapshot, getServerSnapshot) {
|
|
211
|
+
return (getServerSnapshot || getSnapshot)();
|
|
212
|
+
}
|
|
213
|
+
function useTransition() {
|
|
214
|
+
return [false, (cb) => cb()];
|
|
215
|
+
}
|
|
216
|
+
function useDeferredValue(value) {
|
|
217
|
+
return value;
|
|
218
|
+
}
|
|
219
|
+
function useOptimistic(passthrough) {
|
|
220
|
+
return [passthrough, () => {
|
|
221
|
+
}];
|
|
222
|
+
}
|
|
223
|
+
function useActionState(_action, initialState, _permalink) {
|
|
224
|
+
return [initialState, () => {
|
|
225
|
+
}, false];
|
|
226
|
+
}
|
|
227
|
+
function use(usable) {
|
|
228
|
+
if (typeof usable === "object" && usable !== null && ("_currentValue" in usable || "_defaultValue" in usable)) {
|
|
229
|
+
return getContextValue(usable);
|
|
230
|
+
}
|
|
231
|
+
const promise = usable;
|
|
232
|
+
if (promise.status === "fulfilled")
|
|
233
|
+
return promise.value;
|
|
234
|
+
if (promise.status === "rejected")
|
|
235
|
+
throw promise.reason;
|
|
236
|
+
throw promise;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// src/slim-react/dispatcher.ts
|
|
240
|
+
import ReactPkg from "react";
|
|
241
|
+
var _internals = ReactPkg.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;
|
|
242
|
+
var slimDispatcher = {
|
|
243
|
+
useId: makeId,
|
|
244
|
+
readContext: (ctx) => getContextValue(ctx),
|
|
245
|
+
useContext: (ctx) => getContextValue(ctx),
|
|
246
|
+
useState,
|
|
247
|
+
useReducer,
|
|
248
|
+
useEffect,
|
|
249
|
+
useLayoutEffect,
|
|
250
|
+
useInsertionEffect,
|
|
251
|
+
useRef,
|
|
252
|
+
useMemo,
|
|
253
|
+
useCallback,
|
|
254
|
+
useDebugValue,
|
|
255
|
+
useImperativeHandle,
|
|
256
|
+
useSyncExternalStore,
|
|
257
|
+
useTransition,
|
|
258
|
+
useDeferredValue,
|
|
259
|
+
useOptimistic,
|
|
260
|
+
useActionState,
|
|
261
|
+
use,
|
|
262
|
+
// React internals that compiled output may call
|
|
263
|
+
useMemoCache: (size) => new Array(size).fill(void 0),
|
|
264
|
+
useCacheRefresh: () => () => {
|
|
265
|
+
},
|
|
266
|
+
useHostTransitionStatus: () => false
|
|
267
|
+
};
|
|
268
|
+
function installDispatcher() {
|
|
269
|
+
if (!_internals)
|
|
270
|
+
return null;
|
|
271
|
+
const prev = _internals.H;
|
|
272
|
+
_internals.H = slimDispatcher;
|
|
273
|
+
return prev;
|
|
274
|
+
}
|
|
275
|
+
function restoreDispatcher(prev) {
|
|
276
|
+
if (_internals)
|
|
277
|
+
_internals.H = prev;
|
|
278
|
+
}
|
|
154
279
|
|
|
155
280
|
// src/slim-react/render.ts
|
|
156
281
|
var VOID_ELEMENTS = /* @__PURE__ */ new Set([
|
|
@@ -332,7 +457,11 @@ function renderAttributes(props, isSvg) {
|
|
|
332
457
|
continue;
|
|
333
458
|
}
|
|
334
459
|
if (value === true) {
|
|
335
|
-
|
|
460
|
+
if (attrName.startsWith("aria-") || attrName.startsWith("data-")) {
|
|
461
|
+
attrs += ` ${attrName}="true"`;
|
|
462
|
+
} else {
|
|
463
|
+
attrs += ` ${attrName}=""`;
|
|
464
|
+
}
|
|
336
465
|
continue;
|
|
337
466
|
}
|
|
338
467
|
if (key === "style" && typeof value === "object") {
|
|
@@ -551,6 +680,7 @@ function renderComponent(type, props, writer, isSvg) {
|
|
|
551
680
|
return;
|
|
552
681
|
}
|
|
553
682
|
let result;
|
|
683
|
+
const prevDispatcher = installDispatcher();
|
|
554
684
|
try {
|
|
555
685
|
if (type.prototype && typeof type.prototype.render === "function") {
|
|
556
686
|
const instance = new type(props);
|
|
@@ -564,12 +694,20 @@ function renderComponent(type, props, writer, isSvg) {
|
|
|
564
694
|
result = type(props);
|
|
565
695
|
}
|
|
566
696
|
} catch (e) {
|
|
697
|
+
restoreDispatcher(prevDispatcher);
|
|
567
698
|
popComponentScope(savedScope);
|
|
568
699
|
if (isProvider)
|
|
569
700
|
popContextValue(ctx, prevCtxValue);
|
|
570
701
|
throw e;
|
|
571
702
|
}
|
|
703
|
+
restoreDispatcher(prevDispatcher);
|
|
704
|
+
let savedIdTree;
|
|
705
|
+
if (!(result instanceof Promise) && componentCalledUseId()) {
|
|
706
|
+
savedIdTree = pushTreeContext(1, 0);
|
|
707
|
+
}
|
|
572
708
|
const finish = () => {
|
|
709
|
+
if (savedIdTree !== void 0)
|
|
710
|
+
popTreeContext(savedIdTree);
|
|
573
711
|
popComponentScope(savedScope);
|
|
574
712
|
if (isProvider)
|
|
575
713
|
popContextValue(ctx, prevCtxValue);
|
|
@@ -578,22 +716,33 @@ function renderComponent(type, props, writer, isSvg) {
|
|
|
578
716
|
const m = captureMap();
|
|
579
717
|
return result.then((resolved) => {
|
|
580
718
|
swapContextMap(m);
|
|
719
|
+
let asyncSavedIdTree;
|
|
720
|
+
if (componentCalledUseId()) {
|
|
721
|
+
asyncSavedIdTree = pushTreeContext(1, 0);
|
|
722
|
+
}
|
|
723
|
+
const asyncFinish = () => {
|
|
724
|
+
if (asyncSavedIdTree !== void 0)
|
|
725
|
+
popTreeContext(asyncSavedIdTree);
|
|
726
|
+
popComponentScope(savedScope);
|
|
727
|
+
if (isProvider)
|
|
728
|
+
popContextValue(ctx, prevCtxValue);
|
|
729
|
+
};
|
|
581
730
|
const r2 = renderNode(resolved, writer, isSvg);
|
|
582
731
|
if (r2 && typeof r2.then === "function") {
|
|
583
732
|
const m2 = captureMap();
|
|
584
733
|
return r2.then(
|
|
585
734
|
() => {
|
|
586
735
|
swapContextMap(m2);
|
|
587
|
-
|
|
736
|
+
asyncFinish();
|
|
588
737
|
},
|
|
589
738
|
(e) => {
|
|
590
739
|
swapContextMap(m2);
|
|
591
|
-
|
|
740
|
+
asyncFinish();
|
|
592
741
|
throw e;
|
|
593
742
|
}
|
|
594
743
|
);
|
|
595
744
|
}
|
|
596
|
-
|
|
745
|
+
asyncFinish();
|
|
597
746
|
}, (e) => {
|
|
598
747
|
swapContextMap(m);
|
|
599
748
|
finish();
|
package/package.json
CHANGED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React dispatcher shim for slim-react SSR.
|
|
3
|
+
*
|
|
4
|
+
* During a slim-react render, `React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE.H`
|
|
5
|
+
* is null, so any component that calls `React.useId()` (or another hook) via
|
|
6
|
+
* React's own package will hit `resolveDispatcher()` → null → error.
|
|
7
|
+
*
|
|
8
|
+
* We install a minimal dispatcher object for the duration of each component
|
|
9
|
+
* call so that `React.useId()` routes through slim-react's tree-aware
|
|
10
|
+
* `makeId()`. All other hooks already have working SSR stubs in hooks.ts;
|
|
11
|
+
* they are forwarded here so libraries that call them via `React.*` also work.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { makeId, getContextValue } from "./renderContext";
|
|
15
|
+
import {
|
|
16
|
+
useState, useReducer, useEffect, useLayoutEffect, useInsertionEffect,
|
|
17
|
+
useRef, useMemo, useCallback, useDebugValue, useImperativeHandle,
|
|
18
|
+
useSyncExternalStore, useTransition, useDeferredValue,
|
|
19
|
+
useOptimistic, useActionState, use,
|
|
20
|
+
} from "./hooks";
|
|
21
|
+
|
|
22
|
+
import ReactPkg from "react";
|
|
23
|
+
|
|
24
|
+
// React 19 exposes its shared internals under this key.
|
|
25
|
+
const _internals: { H: object | null } | undefined =
|
|
26
|
+
(ReactPkg as any).__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;
|
|
27
|
+
|
|
28
|
+
// The dispatcher object we install. We keep a stable reference so the same
|
|
29
|
+
// object is reused across every component call.
|
|
30
|
+
const slimDispatcher: Record<string, unknown> = {
|
|
31
|
+
useId: makeId,
|
|
32
|
+
readContext: (ctx: any) => getContextValue(ctx),
|
|
33
|
+
useContext: (ctx: any) => getContextValue(ctx),
|
|
34
|
+
useState,
|
|
35
|
+
useReducer,
|
|
36
|
+
useEffect,
|
|
37
|
+
useLayoutEffect,
|
|
38
|
+
useInsertionEffect,
|
|
39
|
+
useRef,
|
|
40
|
+
useMemo,
|
|
41
|
+
useCallback,
|
|
42
|
+
useDebugValue,
|
|
43
|
+
useImperativeHandle,
|
|
44
|
+
useSyncExternalStore,
|
|
45
|
+
useTransition,
|
|
46
|
+
useDeferredValue,
|
|
47
|
+
useOptimistic,
|
|
48
|
+
useActionState,
|
|
49
|
+
use,
|
|
50
|
+
// React internals that compiled output may call
|
|
51
|
+
useMemoCache: (size: number) => new Array(size).fill(undefined),
|
|
52
|
+
useCacheRefresh: () => () => {},
|
|
53
|
+
useHostTransitionStatus: () => false,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Install the slim dispatcher and return the previous value.
|
|
58
|
+
* Call `restoreDispatcher(prev)` when the component finishes.
|
|
59
|
+
*/
|
|
60
|
+
export function installDispatcher(): object | null {
|
|
61
|
+
if (!_internals) return null;
|
|
62
|
+
const prev = _internals.H;
|
|
63
|
+
_internals.H = slimDispatcher;
|
|
64
|
+
return prev;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function restoreDispatcher(prev: object | null): void {
|
|
68
|
+
if (_internals) _internals.H = prev;
|
|
69
|
+
}
|
package/src/slim-react/render.ts
CHANGED
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
popTreeContext,
|
|
29
29
|
pushComponentScope,
|
|
30
30
|
popComponentScope,
|
|
31
|
+
componentCalledUseId,
|
|
31
32
|
snapshotContext,
|
|
32
33
|
restoreContext,
|
|
33
34
|
pushContextValue,
|
|
@@ -35,7 +36,9 @@ import {
|
|
|
35
36
|
getContextValue,
|
|
36
37
|
swapContextMap,
|
|
37
38
|
captureMap,
|
|
39
|
+
type TreeContext,
|
|
38
40
|
} from "./renderContext";
|
|
41
|
+
import { installDispatcher, restoreDispatcher } from "./dispatcher";
|
|
39
42
|
|
|
40
43
|
// ---------------------------------------------------------------------------
|
|
41
44
|
// HTML helpers
|
|
@@ -287,8 +290,13 @@ function renderAttributes(props: Record<string, any>, isSvg: boolean): string {
|
|
|
287
290
|
continue;
|
|
288
291
|
}
|
|
289
292
|
if (value === true) {
|
|
290
|
-
//
|
|
291
|
-
|
|
293
|
+
// aria-* and data-* are string attributes: true must serialize to "true".
|
|
294
|
+
// HTML boolean attributes (disabled, hidden, checked, …) use attr="" (present-without-value).
|
|
295
|
+
if (attrName.startsWith("aria-") || attrName.startsWith("data-")) {
|
|
296
|
+
attrs += ` ${attrName}="true"`;
|
|
297
|
+
} else {
|
|
298
|
+
attrs += ` ${attrName}=""`;
|
|
299
|
+
}
|
|
292
300
|
continue;
|
|
293
301
|
}
|
|
294
302
|
if (key === "style" && typeof value === "object") {
|
|
@@ -625,6 +633,7 @@ function renderComponent(
|
|
|
625
633
|
}
|
|
626
634
|
|
|
627
635
|
let result: SlimNode;
|
|
636
|
+
const prevDispatcher = installDispatcher();
|
|
628
637
|
try {
|
|
629
638
|
if (type.prototype && typeof type.prototype.render === "function") {
|
|
630
639
|
const instance = new (type as any)(props);
|
|
@@ -638,12 +647,25 @@ function renderComponent(
|
|
|
638
647
|
result = type(props);
|
|
639
648
|
}
|
|
640
649
|
} catch (e) {
|
|
650
|
+
restoreDispatcher(prevDispatcher);
|
|
641
651
|
popComponentScope(savedScope);
|
|
642
652
|
if (isProvider) popContextValue(ctx, prevCtxValue);
|
|
643
653
|
throw e;
|
|
644
654
|
}
|
|
655
|
+
restoreDispatcher(prevDispatcher);
|
|
656
|
+
|
|
657
|
+
// React 19 finishFunctionComponent: if the component called useId, push a
|
|
658
|
+
// tree-context slot for the component's OUTPUT children — matching React 19's
|
|
659
|
+
// `pushTreeContext(keyPath, 1, 0)` call inside finishFunctionComponent.
|
|
660
|
+
// This ensures that useId IDs produced by child components of a useId-calling
|
|
661
|
+
// component are tree-positioned identically to React's own renderer.
|
|
662
|
+
let savedIdTree: TreeContext | undefined;
|
|
663
|
+
if (!(result instanceof Promise) && componentCalledUseId()) {
|
|
664
|
+
savedIdTree = pushTreeContext(1, 0);
|
|
665
|
+
}
|
|
645
666
|
|
|
646
667
|
const finish = () => {
|
|
668
|
+
if (savedIdTree !== undefined) popTreeContext(savedIdTree);
|
|
647
669
|
popComponentScope(savedScope);
|
|
648
670
|
if (isProvider) popContextValue(ctx, prevCtxValue);
|
|
649
671
|
};
|
|
@@ -653,15 +675,25 @@ function renderComponent(
|
|
|
653
675
|
const m = captureMap();
|
|
654
676
|
return result.then((resolved) => {
|
|
655
677
|
swapContextMap(m);
|
|
678
|
+
// Check useId after the async body has finished executing.
|
|
679
|
+
let asyncSavedIdTree: TreeContext | undefined;
|
|
680
|
+
if (componentCalledUseId()) {
|
|
681
|
+
asyncSavedIdTree = pushTreeContext(1, 0);
|
|
682
|
+
}
|
|
683
|
+
const asyncFinish = () => {
|
|
684
|
+
if (asyncSavedIdTree !== undefined) popTreeContext(asyncSavedIdTree);
|
|
685
|
+
popComponentScope(savedScope);
|
|
686
|
+
if (isProvider) popContextValue(ctx, prevCtxValue);
|
|
687
|
+
};
|
|
656
688
|
const r = renderNode(resolved, writer, isSvg);
|
|
657
689
|
if (r && typeof (r as any).then === "function") {
|
|
658
690
|
const m2 = captureMap();
|
|
659
691
|
return (r as Promise<void>).then(
|
|
660
|
-
() => { swapContextMap(m2);
|
|
661
|
-
(e) => { swapContextMap(m2);
|
|
692
|
+
() => { swapContextMap(m2); asyncFinish(); },
|
|
693
|
+
(e) => { swapContextMap(m2); asyncFinish(); throw e; },
|
|
662
694
|
);
|
|
663
695
|
}
|
|
664
|
-
|
|
696
|
+
asyncFinish();
|
|
665
697
|
}, (e) => { swapContextMap(m); finish(); throw e; });
|
|
666
698
|
}
|
|
667
699
|
|
|
@@ -63,10 +63,15 @@ export function popContextValue(context: object, prev: unknown): void {
|
|
|
63
63
|
(_g[MAP_KEY] as Map<object, unknown> | null)?.set(context, prev);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
// TreeContext matches React 19's representation exactly:
|
|
67
|
+
// `id` is a packed bitfield with a leading sentinel `1` bit followed by tree
|
|
68
|
+
// path slots. The most-recently-pushed slot occupies the HIGHEST non-sentinel
|
|
69
|
+
// bits, matching React 19's Fizz `pushTreeContext` bit-packing order.
|
|
70
|
+
// `overflow` accumulates segments that no longer fit in the 30-bit budget,
|
|
71
|
+
// prepended newest-first (same as React 19).
|
|
66
72
|
export interface TreeContext {
|
|
67
|
-
id: number;
|
|
68
|
-
overflow: string;
|
|
69
|
-
bits: number;
|
|
73
|
+
id: number; // bitfield with sentinel; 1 = empty (just sentinel, no data)
|
|
74
|
+
overflow: string; // base-32 partial path segments that overflowed
|
|
70
75
|
}
|
|
71
76
|
|
|
72
77
|
interface RenderState {
|
|
@@ -76,7 +81,8 @@ interface RenderState {
|
|
|
76
81
|
}
|
|
77
82
|
|
|
78
83
|
const GLOBAL_KEY = "__slimReactRenderState";
|
|
79
|
-
|
|
84
|
+
// React 19's initial context is { id: 1, overflow: "" } — sentinel bit only.
|
|
85
|
+
const EMPTY: TreeContext = { id: 1, overflow: "" };
|
|
80
86
|
|
|
81
87
|
function s(): RenderState {
|
|
82
88
|
const g = globalThis as any;
|
|
@@ -96,23 +102,45 @@ export function setIdPrefix(prefix: string) {
|
|
|
96
102
|
s().idPrefix = prefix;
|
|
97
103
|
}
|
|
98
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Push a new level onto the tree context — matches React 19's Fizz
|
|
107
|
+
* `pushTreeContext` exactly:
|
|
108
|
+
* - new slot occupies the HIGHER bit positions (above the old base data)
|
|
109
|
+
* - on overflow, the LOWEST bits of the old data move to the overflow string
|
|
110
|
+
* (rounded to a multiple of 5 so base-32 digits align on byte boundaries)
|
|
111
|
+
*/
|
|
99
112
|
export function pushTreeContext(totalChildren: number, index: number): TreeContext {
|
|
100
113
|
const st = s();
|
|
101
114
|
const saved: TreeContext = { ...st.currentTreeContext };
|
|
102
|
-
const pendingBits = 32 - Math.clz32(totalChildren);
|
|
103
|
-
const slot = index + 1;
|
|
104
|
-
const totalBits = st.currentTreeContext.bits + pendingBits;
|
|
105
115
|
|
|
106
|
-
|
|
116
|
+
const baseIdWithLeadingBit = st.currentTreeContext.id;
|
|
117
|
+
const baseOverflow = st.currentTreeContext.overflow;
|
|
118
|
+
// Number of data bits currently stored (excludes the sentinel bit).
|
|
119
|
+
const baseLength = 31 - Math.clz32(baseIdWithLeadingBit);
|
|
120
|
+
// Strip the sentinel to get the pure data portion.
|
|
121
|
+
let baseId = baseIdWithLeadingBit & ~(1 << baseLength);
|
|
122
|
+
|
|
123
|
+
const slot = index + 1; // 1-indexed
|
|
124
|
+
const newBits = 32 - Math.clz32(totalChildren); // bits required for the new slot
|
|
125
|
+
const length = newBits + baseLength; // total data bits after push
|
|
126
|
+
|
|
127
|
+
if (30 < length) {
|
|
128
|
+
// Overflow: flush the lowest bits of the old data to the overflow string.
|
|
129
|
+
// Round down to a multiple of 5 so each base-32 character covers exactly
|
|
130
|
+
// 5 bits (no fractional digits that would corrupt adjacent chars).
|
|
131
|
+
const numberOfOverflowBits = baseLength - (baseLength % 5);
|
|
132
|
+
const overflowStr = (baseId & ((1 << numberOfOverflowBits) - 1)).toString(32);
|
|
133
|
+
baseId >>= numberOfOverflowBits;
|
|
134
|
+
const newBaseLength = baseLength - numberOfOverflowBits;
|
|
107
135
|
st.currentTreeContext = {
|
|
108
|
-
id: (
|
|
109
|
-
overflow:
|
|
110
|
-
bits: totalBits,
|
|
136
|
+
id: (1 << (newBits + newBaseLength)) | (slot << newBaseLength) | baseId,
|
|
137
|
+
overflow: overflowStr + baseOverflow,
|
|
111
138
|
};
|
|
112
139
|
} else {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
140
|
+
st.currentTreeContext = {
|
|
141
|
+
id: (1 << length) | (slot << baseLength) | baseId,
|
|
142
|
+
overflow: baseOverflow,
|
|
143
|
+
};
|
|
116
144
|
}
|
|
117
145
|
return saved;
|
|
118
146
|
}
|
|
@@ -132,6 +160,11 @@ export function popComponentScope(saved: number) {
|
|
|
132
160
|
s().localIdCounter = saved;
|
|
133
161
|
}
|
|
134
162
|
|
|
163
|
+
/** True if the current component has called useId at least once. */
|
|
164
|
+
export function componentCalledUseId(): boolean {
|
|
165
|
+
return s().localIdCounter > 0;
|
|
166
|
+
}
|
|
167
|
+
|
|
135
168
|
export function snapshotContext(): { tree: TreeContext; localId: number } {
|
|
136
169
|
const st = s();
|
|
137
170
|
return { tree: { ...st.currentTreeContext }, localId: st.localIdCounter };
|
|
@@ -143,18 +176,36 @@ export function restoreContext(snap: { tree: TreeContext; localId: number }) {
|
|
|
143
176
|
st.localIdCounter = snap.localId;
|
|
144
177
|
}
|
|
145
178
|
|
|
179
|
+
/**
|
|
180
|
+
* Produce the base-32 tree path string from the current context.
|
|
181
|
+
* Strips the sentinel bit then concatenates stripped_id + overflow —
|
|
182
|
+
* the same formula React 19 uses in both its Fizz SSR renderer and
|
|
183
|
+
* the client-side `mountId`.
|
|
184
|
+
*/
|
|
146
185
|
function getTreeId(): string {
|
|
147
|
-
const { id, overflow
|
|
148
|
-
|
|
186
|
+
const { id, overflow } = s().currentTreeContext;
|
|
187
|
+
if (id === 1) return overflow; // sentinel only → no local path segment
|
|
188
|
+
const stripped = (id & ~(1 << (31 - Math.clz32(id)))).toString(32);
|
|
189
|
+
return stripped + overflow;
|
|
149
190
|
}
|
|
150
191
|
|
|
192
|
+
/**
|
|
193
|
+
* Generate a `useId`-compatible ID for the current call site.
|
|
194
|
+
*
|
|
195
|
+
* Format: `«<idPrefix>R<treeId>»` (React 19.1+)
|
|
196
|
+
* with an optional `H<n>` suffix for the n-th useId call in the same
|
|
197
|
+
* component (matching React 19's `localIdCounter` behaviour).
|
|
198
|
+
*
|
|
199
|
+
* React 19.1 switched from `_R_<id>_` to `«R<id>»` (U+00AB / U+00BB).
|
|
200
|
+
* This matches React 19.1's `mountId` output on the Fizz SSR renderer and
|
|
201
|
+
* the client hydration path, so the IDs produced here will agree with the
|
|
202
|
+
* real React runtime during `hydrateRoot`.
|
|
203
|
+
*/
|
|
151
204
|
export function makeId(): string {
|
|
152
205
|
const st = s();
|
|
153
206
|
const treeId = getTreeId();
|
|
154
207
|
const n = st.localIdCounter++;
|
|
155
|
-
let id = "
|
|
156
|
-
if (
|
|
157
|
-
id
|
|
158
|
-
if (n > 0) id += "H" + n.toString(32) + ":";
|
|
159
|
-
return id;
|
|
208
|
+
let id = "\u00ab" + st.idPrefix + "R" + treeId;
|
|
209
|
+
if (n > 0) id += "H" + n.toString(32);
|
|
210
|
+
return id + "\u00bb";
|
|
160
211
|
}
|