what-react 0.1.0

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/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "what-react",
3
+ "version": "0.1.0",
4
+ "description": "React compatibility layer for What Framework — use React packages with signals under the hood",
5
+ "type": "module",
6
+ "main": "src/index.js",
7
+ "exports": {
8
+ ".": "./src/index.js",
9
+ "./dom": "./src/dom.js",
10
+ "./jsx-runtime": "./src/jsx-runtime.js",
11
+ "./jsx-dev-runtime": "./src/jsx-dev-runtime.js",
12
+ "./vite": "./src/vite-plugin.js"
13
+ },
14
+ "files": [
15
+ "src"
16
+ ],
17
+ "keywords": [
18
+ "react",
19
+ "compat",
20
+ "what",
21
+ "framework",
22
+ "signals",
23
+ "compatibility"
24
+ ],
25
+ "peerDependencies": {
26
+ "what-core": "^0.5.3"
27
+ },
28
+ "author": "",
29
+ "license": "MIT",
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "https://github.com/zvndev/what-fw"
33
+ },
34
+ "bugs": {
35
+ "url": "https://github.com/zvndev/what-fw/issues"
36
+ },
37
+ "homepage": "https://whatframework.dev"
38
+ }
package/src/dom.js ADDED
@@ -0,0 +1,116 @@
1
+ /**
2
+ * what-react/dom — ReactDOM compatibility layer
3
+ *
4
+ * Implements ReactDOM's public API using What's mount() and rendering.
5
+ */
6
+
7
+ import { mount as whatMount, h, Fragment } from 'what-core';
8
+ import { flushSync as whatFlushSync } from 'what-core';
9
+
10
+ // ---- createRoot (React 18) ----
11
+
12
+ export function createRoot(container) {
13
+ let unmount = null;
14
+
15
+ return {
16
+ render(element) {
17
+ if (unmount) unmount();
18
+ unmount = whatMount(element, container);
19
+ },
20
+ unmount() {
21
+ if (unmount) {
22
+ unmount();
23
+ unmount = null;
24
+ }
25
+ container.innerHTML = '';
26
+ },
27
+ };
28
+ }
29
+
30
+ // ---- hydrateRoot ----
31
+ // Basic implementation — mounts fresh (true hydration would reuse existing DOM)
32
+
33
+ export function hydrateRoot(container, initialChildren) {
34
+ const root = createRoot(container);
35
+ root.render(initialChildren);
36
+ return root;
37
+ }
38
+
39
+ // ---- render (React 17 legacy) ----
40
+
41
+ export function render(element, container, callback) {
42
+ const root = createRoot(container);
43
+ root.render(element);
44
+ if (callback) queueMicrotask(callback);
45
+ return root;
46
+ }
47
+
48
+ // ---- unmountComponentAtNode (React 17 legacy) ----
49
+
50
+ export function unmountComponentAtNode(container) {
51
+ container.innerHTML = '';
52
+ return true;
53
+ }
54
+
55
+ // ---- createPortal ----
56
+
57
+ export function createPortal(children, container, key) {
58
+ // Create a vnode that the core reconciler recognizes as a portal.
59
+ // Core's createDOM routes '__portal' tagged vnodes to the internal portal handler,
60
+ // which renders children into the target container and returns a placeholder comment.
61
+ const portal = {
62
+ tag: '__portal',
63
+ props: { container, key },
64
+ children: Array.isArray(children) ? children : [children],
65
+ key: key || null,
66
+ _vnode: true,
67
+ };
68
+
69
+ return portal;
70
+ }
71
+
72
+ // ---- flushSync ----
73
+
74
+ export function flushSync(fn) {
75
+ if (fn) fn();
76
+ whatFlushSync();
77
+ }
78
+
79
+ // ---- findDOMNode (deprecated but needed for legacy packages) ----
80
+
81
+ export function findDOMNode(component) {
82
+ if (component == null) return null;
83
+ // If it's already a DOM node, return it
84
+ if (component instanceof HTMLElement) return component;
85
+ // Class component instance — look for _domNode or _container
86
+ if (component._domNode) return component._domNode;
87
+ // If the component has a ref attached, try that
88
+ if (component._ref?.current instanceof HTMLElement) return component._ref.current;
89
+ // what-c wrapper element — return the first child
90
+ if (component instanceof Element) return component;
91
+ return null;
92
+ }
93
+
94
+ // ---- batching ----
95
+
96
+ export function unstable_batchedUpdates(fn) {
97
+ fn();
98
+ }
99
+
100
+ // ---- Version ----
101
+ export const version = '18.3.1';
102
+
103
+ // ---- Default export ----
104
+ const ReactDOM = {
105
+ createRoot,
106
+ hydrateRoot,
107
+ render,
108
+ unmountComponentAtNode,
109
+ createPortal,
110
+ flushSync,
111
+ findDOMNode,
112
+ unstable_batchedUpdates,
113
+ version,
114
+ };
115
+
116
+ export default ReactDOM;
package/src/index.js ADDED
@@ -0,0 +1,580 @@
1
+ /**
2
+ * what-react — React compatibility layer for What Framework
3
+ *
4
+ * Implements React's public API using What's signals + reconciler.
5
+ * Alias "react" → "what-react" in your bundler to use React libraries.
6
+ *
7
+ * What's existing hooks already have positional tracking (hookIndex/hooks[]),
8
+ * so most hooks are thin re-exports. The main work is bridging createElement,
9
+ * forwardRef, Children, class components, and React-specific APIs.
10
+ */
11
+
12
+ import {
13
+ h,
14
+ Fragment as WhatFragment,
15
+ signal,
16
+ effect,
17
+ computed,
18
+ batch,
19
+ flushSync as whatFlushSync,
20
+ untrack,
21
+ memo as whatMemo,
22
+ lazy as whatLazy,
23
+ Suspense as WhatSuspense,
24
+ ErrorBoundary as WhatErrorBoundary,
25
+ useState as whatUseState,
26
+ useEffect as whatUseEffect,
27
+ useMemo as whatUseMemo,
28
+ useCallback as whatUseCallback,
29
+ useRef as whatUseRef,
30
+ useContext as whatUseContext,
31
+ useReducer as whatUseReducer,
32
+ createContext as whatCreateContext,
33
+ onMount,
34
+ onCleanup,
35
+ } from 'what-core';
36
+
37
+ // ---- Re-export What's hooks with React-compatible names ----
38
+
39
+ export const useState = whatUseState;
40
+ export const useEffect = whatUseEffect;
41
+ export const useMemo = whatUseMemo;
42
+ export const useCallback = whatUseCallback;
43
+ export const useRef = whatUseRef;
44
+ export const useContext = whatUseContext;
45
+ export const useReducer = whatUseReducer;
46
+ export const createContext = whatCreateContext;
47
+ export const Fragment = WhatFragment;
48
+ export const Suspense = WhatSuspense;
49
+ export const memo = whatMemo;
50
+ export const lazy = whatLazy;
51
+
52
+ // ---- Class component wrapper ----
53
+
54
+ const classWrapperCache = new WeakMap();
55
+
56
+ function isClassComponent(type) {
57
+ return (
58
+ typeof type === 'function' &&
59
+ (type.prototype?.isReactComponent || type.prototype?.render)
60
+ );
61
+ }
62
+
63
+ // Max re-renders per component per frame to prevent infinite loops
64
+ const MAX_RENDERS_PER_FRAME = 50;
65
+
66
+ function getClassWrapper(ClassComp) {
67
+ let wrapper = classWrapperCache.get(ClassComp);
68
+ if (wrapper) return wrapper;
69
+
70
+ wrapper = function ClassComponentWrapper(props) {
71
+ const instanceRef = whatUseRef(null);
72
+ const [renderCount, forceRender] = whatUseState(0);
73
+ const renderGuardRef = whatUseRef({ count: 0, frame: 0 });
74
+
75
+ // Render cycle guard — prevent infinite re-render loops
76
+ const currentFrame = renderGuardRef.current.frame;
77
+ if (typeof requestAnimationFrame !== 'undefined') {
78
+ renderGuardRef.current.count++;
79
+ if (renderGuardRef.current.count > MAX_RENDERS_PER_FRAME) {
80
+ console.error(`[what-react] Max re-renders exceeded for ${ClassComp.displayName || ClassComp.name || 'ClassComponent'}. Possible infinite loop.`);
81
+ return null;
82
+ }
83
+ // Reset count on next frame
84
+ if (renderGuardRef.current._raf === undefined) {
85
+ renderGuardRef.current._raf = requestAnimationFrame(() => {
86
+ renderGuardRef.current.count = 0;
87
+ renderGuardRef.current.frame++;
88
+ renderGuardRef.current._raf = undefined;
89
+ });
90
+ }
91
+ }
92
+
93
+ // Apply defaultProps
94
+ let mergedProps = props;
95
+ if (ClassComp.defaultProps) {
96
+ mergedProps = { ...ClassComp.defaultProps, ...props };
97
+ }
98
+
99
+ if (instanceRef.current === null) {
100
+ // Initialize state from constructor
101
+ const instance = new ClassComp(mergedProps);
102
+
103
+ // Apply getDerivedStateFromProps on initial render
104
+ if (ClassComp.getDerivedStateFromProps) {
105
+ const derived = ClassComp.getDerivedStateFromProps(mergedProps, instance.state);
106
+ if (derived !== null && derived !== undefined) {
107
+ instance.state = { ...instance.state, ...derived };
108
+ }
109
+ }
110
+
111
+ // Throttle forceUpdate — coalesce rapid setState calls
112
+ let updateScheduled = false;
113
+ instance._forceUpdate = () => {
114
+ if (!updateScheduled) {
115
+ updateScheduled = true;
116
+ queueMicrotask(() => {
117
+ updateScheduled = false;
118
+ forceRender(c => c + 1);
119
+ });
120
+ }
121
+ };
122
+ instanceRef.current = instance;
123
+ }
124
+
125
+ const instance = instanceRef.current;
126
+ instance.props = mergedProps;
127
+
128
+ // Apply getDerivedStateFromProps on every render (React semantics)
129
+ if (ClassComp.getDerivedStateFromProps) {
130
+ const derived = ClassComp.getDerivedStateFromProps(mergedProps, instance.state);
131
+ if (derived !== null && derived !== undefined) {
132
+ instance.state = { ...instance.state, ...derived };
133
+ }
134
+ }
135
+
136
+ // Static contextType support — inject this.context from nearest provider
137
+ if (ClassComp.contextType && ClassComp.contextType._whatContext) {
138
+ try {
139
+ instance.context = whatUseContext(ClassComp.contextType);
140
+ } catch (e) {
141
+ // Context not available — leave as undefined
142
+ }
143
+ }
144
+
145
+ // componentDidMount / componentWillUnmount lifecycle
146
+ whatUseEffect(() => {
147
+ instance._mounted = true;
148
+ if (instance.componentDidMount) {
149
+ instance.componentDidMount();
150
+ }
151
+ return () => {
152
+ instance._mounted = false;
153
+ if (instance.componentWillUnmount) {
154
+ instance.componentWillUnmount();
155
+ }
156
+ };
157
+ }, []);
158
+
159
+ // componentDidUpdate + getSnapshotBeforeUpdate
160
+ const prevRef = whatUseRef({ props: null, state: null, rendered: false, snapshot: undefined });
161
+ whatUseEffect(() => {
162
+ if (!prevRef.current.rendered) {
163
+ prevRef.current = { props: mergedProps, state: instance.state, rendered: true, snapshot: undefined };
164
+ return;
165
+ }
166
+ const prev = prevRef.current;
167
+ prevRef.current = { props: mergedProps, state: instance.state, rendered: true, snapshot: undefined };
168
+ if (instance.componentDidUpdate) {
169
+ instance.componentDidUpdate(prev.props, prev.state, prev.snapshot);
170
+ }
171
+ }, [mergedProps, renderCount]);
172
+
173
+ // getSnapshotBeforeUpdate — capture before DOM updates
174
+ // We approximate by calling it synchronously before render returns
175
+ if (instance.getSnapshotBeforeUpdate && prevRef.current.rendered) {
176
+ prevRef.current.snapshot = instance.getSnapshotBeforeUpdate(
177
+ prevRef.current.props, prevRef.current.state
178
+ );
179
+ }
180
+
181
+ return instance.render();
182
+ };
183
+
184
+ // Preserve static properties and displayName
185
+ wrapper.displayName = ClassComp.displayName || ClassComp.name || 'ClassComponent';
186
+ // Copy static properties (getDerivedStateFromProps, defaultProps, contextType, etc.)
187
+ for (const key of Object.getOwnPropertyNames(ClassComp)) {
188
+ if (key !== 'prototype' && key !== 'length' && key !== 'name' && key !== 'caller' && key !== 'arguments') {
189
+ try { wrapper[key] = ClassComp[key]; } catch (e) {}
190
+ }
191
+ }
192
+
193
+ classWrapperCache.set(ClassComp, wrapper);
194
+ return wrapper;
195
+ }
196
+
197
+ // ---- createElement ----
198
+
199
+ export function createElement(type, props, ...children) {
200
+ if (props == null) props = {};
201
+
202
+ // Wrap class components so What's reconciler can call them as functions
203
+ if (isClassComponent(type)) {
204
+ type = getClassWrapper(type);
205
+ }
206
+
207
+ // React libraries sometimes pass children via props instead of as spread args
208
+ // (e.g., React Router's createElement(Router, { children, location, ... })).
209
+ // Move props.children into the spread children array so h() puts them
210
+ // in vnode.children — otherwise the reconciler overwrites props.children.
211
+ if (children.length === 0 && props.children !== undefined) {
212
+ const pc = props.children;
213
+ children = Array.isArray(pc) ? pc : [pc];
214
+ props = { ...props };
215
+ delete props.children;
216
+ }
217
+
218
+ // Normalize className → class, htmlFor → for for HTML elements
219
+ if (typeof type === 'string') {
220
+ if ('className' in props) {
221
+ props.class = props.className;
222
+ delete props.className;
223
+ }
224
+ if ('htmlFor' in props) {
225
+ props.for = props.htmlFor;
226
+ delete props.htmlFor;
227
+ }
228
+ }
229
+
230
+ // Keep ref in props — What's reconciler handles ref for HTML elements,
231
+ // and forwardRef components extract it from props. No need to strip.
232
+
233
+ const vnode = children.length <= 1
234
+ ? h(type, props, children[0])
235
+ : h(type, props, ...children);
236
+
237
+ // Alias tag → type so React libraries can access element.type
238
+ vnode.type = vnode.tag;
239
+
240
+ // Mirror children into props for React compat — React libraries read
241
+ // element.props.children (e.g., React Router's createRoutesFromChildren)
242
+ if (vnode.children.length > 0) {
243
+ vnode.props = { ...vnode.props };
244
+ vnode.props.children = vnode.children.length === 1
245
+ ? vnode.children[0]
246
+ : vnode.children;
247
+ }
248
+
249
+ return vnode;
250
+ }
251
+
252
+ // ---- forwardRef ----
253
+
254
+ export function forwardRef(render) {
255
+ function ForwardRefComponent(props) {
256
+ const { ref, ...rest } = props;
257
+ return render(rest, ref || null);
258
+ }
259
+ ForwardRefComponent.displayName = render.displayName || render.name || 'ForwardRef';
260
+ ForwardRefComponent._forwardRef = true;
261
+ ForwardRefComponent.$$typeof = Symbol.for('react.forward_ref');
262
+ return ForwardRefComponent;
263
+ }
264
+
265
+ // ---- createRef ----
266
+
267
+ export function createRef() {
268
+ return { current: null };
269
+ }
270
+
271
+ // ---- Children utilities ----
272
+
273
+ export const Children = {
274
+ map(children, fn) {
275
+ if (children == null) return [];
276
+ const arr = Array.isArray(children) ? children : [children];
277
+ const result = [];
278
+ let index = 0;
279
+ for (const child of arr.flat(Infinity)) {
280
+ if (child == null || child === false || child === true) continue;
281
+ result.push(fn(child, index++));
282
+ }
283
+ return result;
284
+ },
285
+
286
+ forEach(children, fn) {
287
+ Children.map(children, fn);
288
+ },
289
+
290
+ count(children) {
291
+ if (children == null) return 0;
292
+ const arr = Array.isArray(children) ? children : [children];
293
+ return arr.flat(Infinity).filter(c => c != null && c !== false && c !== true).length;
294
+ },
295
+
296
+ toArray(children) {
297
+ if (children == null) return [];
298
+ const arr = Array.isArray(children) ? children : [children];
299
+ return arr.flat(Infinity).filter(c => c != null && c !== false && c !== true);
300
+ },
301
+
302
+ only(children) {
303
+ const arr = Children.toArray(children);
304
+ if (arr.length !== 1) {
305
+ throw new Error('React.Children.only expected to receive a single React element child.');
306
+ }
307
+ return arr[0];
308
+ },
309
+ };
310
+
311
+ // ---- cloneElement ----
312
+
313
+ export function cloneElement(element, props, ...children) {
314
+ if (!element) return element;
315
+
316
+ // Handle both vnode objects and plain React-style elements
317
+ const tag = element.tag || element.type;
318
+ const oldProps = element.props || {};
319
+ const oldChildren = element.children || [];
320
+ const oldKey = element.key;
321
+ const oldRef = oldProps.ref;
322
+
323
+ if (!tag) return element;
324
+
325
+ const newProps = { ...oldProps, ...props };
326
+ // Preserve ref from old element if not overridden
327
+ if (props && props.ref !== undefined) {
328
+ newProps.ref = props.ref;
329
+ } else if (oldRef !== undefined) {
330
+ newProps.ref = oldRef;
331
+ }
332
+ const newChildren = children.length > 0 ? children : oldChildren;
333
+ const newKey = props?.key !== undefined ? props.key : oldKey;
334
+ if (newKey !== undefined) newProps.key = newKey;
335
+
336
+ return createElement(tag, newProps, ...([].concat(newChildren || [])));
337
+ }
338
+
339
+ // ---- createFactory (deprecated but used by some libraries) ----
340
+
341
+ export function createFactory(type) {
342
+ const factory = createElement.bind(null, type);
343
+ factory.type = type;
344
+ return factory;
345
+ }
346
+
347
+ // ---- isValidElement ----
348
+
349
+ export function isValidElement(object) {
350
+ return (
351
+ typeof object === 'object' &&
352
+ object !== null &&
353
+ (object._vnode === true || object.$$typeof !== undefined)
354
+ );
355
+ }
356
+
357
+ // ---- useLayoutEffect ----
358
+
359
+ export function useLayoutEffect(fn, deps) {
360
+ return whatUseEffect(fn, deps);
361
+ }
362
+
363
+ // ---- useInsertionEffect ----
364
+ // React 18 hook for CSS-in-JS libraries. Runs synchronously before layout effects.
365
+ // We map it to useEffect since What doesn't have multi-phase commit.
366
+
367
+ export function useInsertionEffect(fn, deps) {
368
+ return whatUseEffect(fn, deps);
369
+ }
370
+
371
+ // ---- useImperativeHandle ----
372
+
373
+ export function useImperativeHandle(ref, createHandle, deps) {
374
+ useLayoutEffect(() => {
375
+ if (typeof ref === 'function') {
376
+ const handle = createHandle();
377
+ ref(handle);
378
+ return () => ref(null);
379
+ } else if (ref && typeof ref === 'object') {
380
+ const handle = createHandle();
381
+ ref.current = handle;
382
+ return () => { ref.current = null; };
383
+ }
384
+ }, deps);
385
+ }
386
+
387
+ // ---- useId ----
388
+ let idCounter = 0;
389
+ export function useId() {
390
+ const ref = whatUseRef(null);
391
+ if (ref.current === null) {
392
+ ref.current = ':w' + (++idCounter).toString(36) + ':';
393
+ }
394
+ return ref.current;
395
+ }
396
+
397
+ // ---- useDebugValue ----
398
+ export function useDebugValue() {}
399
+
400
+ // ---- useSyncExternalStore ----
401
+
402
+ export function useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot) {
403
+ const [value, setValue] = whatUseState(() => getSnapshot());
404
+ const snapshotRef = whatUseRef(getSnapshot());
405
+
406
+ whatUseEffect(() => {
407
+ const handleChange = () => {
408
+ const next = getSnapshot();
409
+ if (!Object.is(snapshotRef.current, next)) {
410
+ snapshotRef.current = next;
411
+ setValue(next);
412
+ }
413
+ };
414
+ handleChange();
415
+ return subscribe(handleChange);
416
+ }, [subscribe, getSnapshot]);
417
+
418
+ return value;
419
+ }
420
+
421
+ // ---- useTransition ----
422
+
423
+ export function useTransition() {
424
+ const [isPending, setIsPending] = whatUseState(false);
425
+
426
+ function startTransitionFn(fn) {
427
+ setIsPending(true);
428
+ queueMicrotask(() => {
429
+ batch(() => {
430
+ fn();
431
+ setIsPending(false);
432
+ });
433
+ });
434
+ }
435
+
436
+ return [isPending, startTransitionFn];
437
+ }
438
+
439
+ // ---- useDeferredValue ----
440
+
441
+ export function useDeferredValue(value) {
442
+ const [deferred, setDeferred] = whatUseState(value);
443
+
444
+ whatUseEffect(() => {
445
+ setDeferred(value);
446
+ }, [value]);
447
+
448
+ return deferred;
449
+ }
450
+
451
+ // ---- startTransition (module-level) ----
452
+
453
+ export function startTransition(fn) {
454
+ queueMicrotask(() => {
455
+ batch(fn);
456
+ });
457
+ }
458
+
459
+ // ---- StrictMode ----
460
+
461
+ export function StrictMode({ children }) {
462
+ return children;
463
+ }
464
+
465
+ // ---- Component / PureComponent ----
466
+ // Use function constructors (not native classes) so that transpiled code
467
+ // using Component.call(this, props) works alongside native class extends.
468
+
469
+ export function Component(props) {
470
+ this.props = props;
471
+ this.state = {};
472
+ this._stateSignal = null;
473
+ this._mounted = false;
474
+ this._forceUpdate = null;
475
+ }
476
+
477
+ Component.prototype.isReactComponent = {};
478
+
479
+ Component.prototype.setState = function(update, callback) {
480
+ const nextState = typeof update === 'function'
481
+ ? { ...this.state, ...update(this.state, this.props) }
482
+ : { ...this.state, ...update };
483
+
484
+ this.state = nextState;
485
+
486
+ if (this._forceUpdate) {
487
+ this._forceUpdate();
488
+ }
489
+
490
+ if (callback) {
491
+ queueMicrotask(callback);
492
+ }
493
+ };
494
+
495
+ Component.prototype.forceUpdate = function(callback) {
496
+ if (this._forceUpdate) {
497
+ this._forceUpdate();
498
+ }
499
+ if (callback) {
500
+ queueMicrotask(callback);
501
+ }
502
+ };
503
+
504
+ Component.prototype.render = function() {
505
+ return null;
506
+ };
507
+
508
+ export function PureComponent(props) {
509
+ Component.call(this, props);
510
+ }
511
+
512
+ PureComponent.prototype = Object.create(Component.prototype);
513
+ PureComponent.prototype.constructor = PureComponent;
514
+ PureComponent.prototype.isPureReactComponent = true;
515
+
516
+ PureComponent.prototype.shouldComponentUpdate = function(nextProps, nextState) {
517
+ return !shallowEqual(this.props, nextProps) || !shallowEqual(this.state, nextState);
518
+ };
519
+
520
+ // ---- Internal helpers ----
521
+
522
+ function shallowEqual(a, b) {
523
+ if (Object.is(a, b)) return true;
524
+ if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null) return false;
525
+ const keysA = Object.keys(a);
526
+ const keysB = Object.keys(b);
527
+ if (keysA.length !== keysB.length) return false;
528
+ for (const key of keysA) {
529
+ if (!Object.is(a[key], b[key])) return false;
530
+ }
531
+ return true;
532
+ }
533
+
534
+ // ---- React internals that some libraries check ----
535
+ export const __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = {
536
+ ReactCurrentOwner: { current: null },
537
+ ReactCurrentDispatcher: { current: null },
538
+ };
539
+
540
+ // ---- Version ----
541
+ export const version = '18.3.1';
542
+
543
+ // ---- Default export (import * as React from 'react') ----
544
+ const React = {
545
+ useState: whatUseState,
546
+ useEffect: whatUseEffect,
547
+ useLayoutEffect,
548
+ useInsertionEffect,
549
+ useMemo: whatUseMemo,
550
+ useCallback: whatUseCallback,
551
+ useRef: whatUseRef,
552
+ useContext: whatUseContext,
553
+ useReducer: whatUseReducer,
554
+ useImperativeHandle,
555
+ useId,
556
+ useDebugValue,
557
+ useSyncExternalStore,
558
+ useTransition,
559
+ useDeferredValue,
560
+ createElement,
561
+ createContext: whatCreateContext,
562
+ createRef,
563
+ createFactory,
564
+ forwardRef,
565
+ cloneElement,
566
+ isValidElement,
567
+ Component,
568
+ PureComponent,
569
+ Fragment: WhatFragment,
570
+ Suspense: WhatSuspense,
571
+ StrictMode,
572
+ memo: whatMemo,
573
+ lazy: whatLazy,
574
+ Children,
575
+ startTransition,
576
+ version,
577
+ __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
578
+ };
579
+
580
+ export default React;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * what-react/jsx-dev-runtime — JSX development runtime
3
+ * Same as jsx-runtime but with development warnings.
4
+ */
5
+
6
+ export { jsx, jsxs, jsxDEV, Fragment } from './jsx-runtime.js';
@@ -0,0 +1,38 @@
1
+ /**
2
+ * what-react/jsx-runtime — JSX automatic runtime
3
+ *
4
+ * When React libraries are compiled with the automatic JSX runtime,
5
+ * they import jsx/jsxs from 'react/jsx-runtime' instead of calling
6
+ * React.createElement. This module provides those functions.
7
+ */
8
+
9
+ import { createElement } from './index.js';
10
+ import { Fragment } from 'what-core';
11
+
12
+ export { Fragment };
13
+
14
+ export function jsx(type, props, key) {
15
+ if (key !== undefined) {
16
+ props = { ...props, key };
17
+ }
18
+
19
+ // Extract children from props (automatic runtime puts children in props)
20
+ const { children, ...rest } = props || {};
21
+
22
+ if (children === undefined) {
23
+ return createElement(type, rest);
24
+ }
25
+
26
+ if (Array.isArray(children)) {
27
+ return createElement(type, rest, ...children);
28
+ }
29
+
30
+ return createElement(type, rest, children);
31
+ }
32
+
33
+ // jsxs is the same as jsx — React uses it for static children optimization
34
+ // but we don't need that distinction
35
+ export const jsxs = jsx;
36
+
37
+ // Development version
38
+ export const jsxDEV = jsx;
@@ -0,0 +1,79 @@
1
+ /**
2
+ * ESM shim for use-sync-external-store/with-selector
3
+ * Used by react-redux. Provides useSyncExternalStoreWithSelector
4
+ * which wraps useSyncExternalStore with selector + isEqual support.
5
+ */
6
+ import { useSyncExternalStore, useRef, useEffect, useMemo, useDebugValue } from './index.js';
7
+
8
+ export function useSyncExternalStoreWithSelector(
9
+ subscribe,
10
+ getSnapshot,
11
+ getServerSnapshot,
12
+ selector,
13
+ isEqual
14
+ ) {
15
+ const instRef = useRef(null);
16
+ if (instRef.current === null) {
17
+ instRef.current = { hasValue: false, value: null };
18
+ }
19
+ const inst = instRef.current;
20
+
21
+ const [getSelection, getServerSelection] = useMemo(() => {
22
+ let hasMemo = false;
23
+ let memoizedSnapshot;
24
+ let memoizedSelection;
25
+
26
+ const memoizedSelector = (nextSnapshot) => {
27
+ if (!hasMemo) {
28
+ hasMemo = true;
29
+ memoizedSnapshot = nextSnapshot;
30
+ const nextSelection = selector(nextSnapshot);
31
+ if (isEqual !== undefined && inst.hasValue) {
32
+ const currentSelection = inst.value;
33
+ if (isEqual(currentSelection, nextSelection)) {
34
+ memoizedSelection = currentSelection;
35
+ return currentSelection;
36
+ }
37
+ }
38
+ memoizedSelection = nextSelection;
39
+ return nextSelection;
40
+ }
41
+
42
+ const prevSnapshot = memoizedSnapshot;
43
+ const prevSelection = memoizedSelection;
44
+
45
+ if (Object.is(prevSnapshot, nextSnapshot)) {
46
+ return prevSelection;
47
+ }
48
+
49
+ const nextSelection = selector(nextSnapshot);
50
+
51
+ if (isEqual !== undefined && isEqual(prevSelection, nextSelection)) {
52
+ memoizedSnapshot = nextSnapshot;
53
+ return prevSelection;
54
+ }
55
+
56
+ memoizedSnapshot = nextSnapshot;
57
+ memoizedSelection = nextSelection;
58
+ return nextSelection;
59
+ };
60
+
61
+ const getSnapshotWithSelector = () => memoizedSelector(getSnapshot());
62
+ const getServerSnapshotWithSelector = getServerSnapshot === undefined
63
+ ? undefined
64
+ : () => memoizedSelector(getServerSnapshot());
65
+
66
+ return [getSnapshotWithSelector, getServerSnapshotWithSelector];
67
+ }, [getSnapshot, getServerSnapshot, selector, isEqual]);
68
+
69
+ const value = useSyncExternalStore(subscribe, getSelection, getServerSelection);
70
+
71
+ useEffect(() => {
72
+ inst.hasValue = true;
73
+ inst.value = value;
74
+ }, [value]);
75
+
76
+ useDebugValue(value);
77
+
78
+ return value;
79
+ }
@@ -0,0 +1,195 @@
1
+ /**
2
+ * what-react Vite Plugin — One-line React → What Framework migration
3
+ *
4
+ * Usage:
5
+ * import { reactCompat } from 'what-react/vite';
6
+ * export default defineConfig({ plugins: [reactCompat()] });
7
+ *
8
+ * This plugin aliases all React imports to what-react, configures JSX,
9
+ * and excludes React-ecosystem packages from Vite's pre-bundling to
10
+ * prevent dual module instances.
11
+ */
12
+ import { createRequire } from 'module';
13
+ import path from 'path';
14
+ import fs from 'fs';
15
+
16
+ // Common React ecosystem packages that import 'react' internally.
17
+ // These must be excluded from Vite's optimizeDeps to prevent pre-bundling
18
+ // with real React, which creates dual module instances.
19
+ const KNOWN_REACT_PACKAGES = [
20
+ // State management
21
+ 'zustand', 'jotai', 'valtio', 'mobx-react', 'mobx-react-lite',
22
+ 'react-redux', 'redux', '@reduxjs/toolkit',
23
+ // Data fetching
24
+ '@tanstack/react-query', 'swr',
25
+ // Forms
26
+ 'react-hook-form',
27
+ // UI component libraries
28
+ '@radix-ui', '@headlessui/react', 'antd', '@ant-design',
29
+ '@mui/material', '@mui/x-data-grid', '@chakra-ui/react',
30
+ // Tables & grids
31
+ '@tanstack/react-table', '@tanstack/table-core',
32
+ // Virtualization
33
+ '@tanstack/react-virtual', '@tanstack/virtual-core',
34
+ 'react-window', 'react-virtualized',
35
+ // Animation
36
+ 'framer-motion', 'motion', '@react-spring/web', 'react-spring',
37
+ // Drag and drop
38
+ '@dnd-kit/core', '@dnd-kit/sortable', 'react-dnd',
39
+ // Routing
40
+ 'react-router', 'react-router-dom',
41
+ // Notifications
42
+ 'react-hot-toast', 'react-toastify',
43
+ // Icons
44
+ 'react-icons', '@heroicons/react',
45
+ // Misc
46
+ 'react-markdown', 'react-helmet', 'react-helmet-async',
47
+ 'react-i18next', 'react-error-boundary',
48
+ 'react-select', 'react-datepicker',
49
+ // Internal shims
50
+ 'use-sync-external-store', 'immer',
51
+ ];
52
+
53
+ /**
54
+ * Resolve the directory of a package from the user's project
55
+ */
56
+ function resolvePackageDir(packageName, fromDir) {
57
+ try {
58
+ const require = createRequire(fromDir + '/');
59
+ const resolved = require.resolve(packageName);
60
+ // Walk up from the resolved file to find the package root
61
+ let dir = path.dirname(resolved);
62
+ while (dir !== path.dirname(dir)) {
63
+ if (fs.existsSync(path.join(dir, 'package.json'))) {
64
+ const pkg = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8'));
65
+ if (pkg.name === packageName) return dir;
66
+ }
67
+ dir = path.dirname(dir);
68
+ }
69
+ return path.dirname(resolved);
70
+ } catch {
71
+ return null;
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Auto-detect which React packages are installed in the project
77
+ */
78
+ function detectInstalledReactPackages(projectRoot) {
79
+ const installed = [];
80
+ for (const pkg of KNOWN_REACT_PACKAGES) {
81
+ try {
82
+ const require = createRequire(projectRoot + '/');
83
+ require.resolve(pkg);
84
+ installed.push(pkg);
85
+ } catch {
86
+ // Not installed, skip
87
+ }
88
+ }
89
+ return installed;
90
+ }
91
+
92
+ /**
93
+ * @param {object} [options]
94
+ * @param {string[]} [options.exclude] - Additional packages to exclude from pre-bundling
95
+ * @param {boolean} [options.autoDetect=true] - Auto-detect installed React packages to exclude
96
+ * @returns {import('vite').Plugin}
97
+ */
98
+ export function reactCompat(options = {}) {
99
+ const { exclude = [], autoDetect = true } = options;
100
+
101
+ let compatDir;
102
+ let whatCorePath;
103
+ let whatCoreRenderPath;
104
+
105
+ return {
106
+ name: 'what-react-compat',
107
+ enforce: 'pre',
108
+
109
+ config(config, { command }) {
110
+ const root = config.root || process.cwd();
111
+
112
+ // Resolve what-react and what-core paths from installed packages
113
+ compatDir = resolvePackageDir('what-react', root);
114
+ // compatSrc = the directory containing index.js, jsx-runtime.js, dom.js, etc.
115
+ let compatSrc;
116
+ if (compatDir) {
117
+ compatSrc = path.join(compatDir, 'src');
118
+ } else {
119
+ // Fallback: this plugin file lives in src/ — use its directory directly
120
+ compatSrc = path.dirname(new URL(import.meta.url).pathname);
121
+ compatDir = path.dirname(compatSrc);
122
+ }
123
+
124
+ const whatCoreDir = resolvePackageDir('what-core', root);
125
+ if (whatCoreDir) {
126
+ whatCorePath = path.join(whatCoreDir, 'src', 'index.js');
127
+ whatCoreRenderPath = path.join(whatCoreDir, 'src', 'render.js');
128
+ } else {
129
+ // Fallback: resolve from what-react's peer dep
130
+ const whatCoreFromCompat = resolvePackageDir('what-core', compatDir);
131
+ if (whatCoreFromCompat) {
132
+ whatCorePath = path.join(whatCoreFromCompat, 'src', 'index.js');
133
+ whatCoreRenderPath = path.join(whatCoreFromCompat, 'src', 'render.js');
134
+ }
135
+ }
136
+
137
+ // Auto-detect installed React ecosystem packages
138
+ const autoExclude = autoDetect ? detectInstalledReactPackages(root) : [];
139
+ const allExclude = [
140
+ 'what-core', 'what-react',
141
+ 'react', 'react-dom',
142
+ ...autoExclude,
143
+ ...exclude,
144
+ ];
145
+ // Deduplicate
146
+ const uniqueExclude = [...new Set(allExclude)];
147
+
148
+ // Build alias map
149
+ const aliases = {
150
+ 'react/jsx-runtime': path.join(compatSrc, 'jsx-runtime.js'),
151
+ 'react/jsx-dev-runtime': path.join(compatSrc, 'jsx-dev-runtime.js'),
152
+ 'react-dom/client': path.join(compatSrc, 'dom.js'),
153
+ 'react-dom': path.join(compatSrc, 'dom.js'),
154
+ 'react': path.join(compatSrc, 'index.js'),
155
+ // use-sync-external-store shims (needed by react-redux, zustand internals)
156
+ 'use-sync-external-store/with-selector.js': path.join(compatSrc, 'use-sync-external-store-with-selector.js'),
157
+ 'use-sync-external-store/with-selector': path.join(compatSrc, 'use-sync-external-store-with-selector.js'),
158
+ 'use-sync-external-store/shim/with-selector.js': path.join(compatSrc, 'use-sync-external-store-with-selector.js'),
159
+ 'use-sync-external-store/shim/with-selector': path.join(compatSrc, 'use-sync-external-store-with-selector.js'),
160
+ 'use-sync-external-store/shim/index.js': path.join(compatSrc, 'index.js'),
161
+ 'use-sync-external-store/shim': path.join(compatSrc, 'index.js'),
162
+ };
163
+
164
+ // Add what-core aliases if resolved
165
+ if (whatCorePath) {
166
+ aliases['what-framework/render'] = whatCoreRenderPath;
167
+ aliases['what-framework'] = whatCorePath;
168
+ aliases['what-core/render'] = whatCoreRenderPath;
169
+ aliases['what-core'] = whatCorePath;
170
+ }
171
+
172
+ return {
173
+ esbuild: {
174
+ jsx: 'automatic',
175
+ jsxImportSource: 'react', // aliased to what-react
176
+ },
177
+ resolve: {
178
+ alias: aliases,
179
+ dedupe: ['what-core'],
180
+ },
181
+ optimizeDeps: {
182
+ exclude: uniqueExclude,
183
+ },
184
+ };
185
+ },
186
+
187
+ configResolved(config) {
188
+ // Log what we set up
189
+ const excluded = config.optimizeDeps?.exclude?.length || 0;
190
+ console.log(`\n ⚡ what-react compat active — ${excluded} packages excluded from pre-bundling\n`);
191
+ },
192
+ };
193
+ }
194
+
195
+ export default reactCompat;