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 +38 -0
- package/src/dom.js +116 -0
- package/src/index.js +580 -0
- package/src/jsx-dev-runtime.js +6 -0
- package/src/jsx-runtime.js +38 -0
- package/src/use-sync-external-store-with-selector.js +79 -0
- package/src/vite-plugin.js +195 -0
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,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;
|