what-core 0.5.3 → 0.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +119 -0
- package/package.json +2 -2
- package/src/data.js +17 -0
- package/src/dom.js +3 -1
- package/src/index.js +2 -1
- package/src/reactive.js +18 -2
package/README.md
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# what-core
|
|
2
|
+
|
|
3
|
+
The reactive engine behind [What Framework](https://whatfw.com). Provides signals, fine-grained reactivity, components, hooks, and DOM rendering -- all without a virtual DOM diffing step.
|
|
4
|
+
|
|
5
|
+
Most users should install [`what-framework`](https://www.npmjs.com/package/what-framework) instead. `what-core` is the internal engine consumed by other What packages.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install what-core
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Reactive Primitives
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
import { signal, computed, effect, batch, untrack } from 'what-core';
|
|
17
|
+
|
|
18
|
+
const count = signal(0);
|
|
19
|
+
|
|
20
|
+
// Read
|
|
21
|
+
count(); // 0
|
|
22
|
+
|
|
23
|
+
// Write
|
|
24
|
+
count.set(5);
|
|
25
|
+
count.set(c => c + 1);
|
|
26
|
+
|
|
27
|
+
// Derived value
|
|
28
|
+
const doubled = computed(() => count() * 2);
|
|
29
|
+
|
|
30
|
+
// Side effects
|
|
31
|
+
effect(() => {
|
|
32
|
+
console.log('Count:', count());
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Batch updates (effects run once at the end)
|
|
36
|
+
batch(() => {
|
|
37
|
+
a.set(1);
|
|
38
|
+
b.set(2);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Read without subscribing
|
|
42
|
+
untrack(() => someSignal());
|
|
43
|
+
count.peek();
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Hooks
|
|
47
|
+
|
|
48
|
+
React-compatible hooks backed by signals internally.
|
|
49
|
+
|
|
50
|
+
```js
|
|
51
|
+
import {
|
|
52
|
+
useState, useEffect, useMemo, useCallback,
|
|
53
|
+
useRef, useReducer, useContext, createContext,
|
|
54
|
+
onMount, onCleanup,
|
|
55
|
+
} from 'what-core';
|
|
56
|
+
|
|
57
|
+
const [count, setCount] = useState(0);
|
|
58
|
+
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
const id = setInterval(tick, 1000);
|
|
61
|
+
return () => clearInterval(id);
|
|
62
|
+
}, []);
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Components
|
|
66
|
+
|
|
67
|
+
```js
|
|
68
|
+
import { h, mount, Fragment, memo, lazy, Suspense, ErrorBoundary, Show, For } from 'what-core';
|
|
69
|
+
|
|
70
|
+
function Counter() {
|
|
71
|
+
const count = signal(0);
|
|
72
|
+
return h('button', { onclick: () => count.set(c => c + 1) }, () => count());
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
mount(h(Counter), '#app');
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Additional Modules
|
|
79
|
+
|
|
80
|
+
| Export path | Contents |
|
|
81
|
+
|---|---|
|
|
82
|
+
| `what-core` | Signals, hooks, components, store, forms, data fetching, animation, a11y, skeleton loaders |
|
|
83
|
+
| `what-core/render` | Fine-grained rendering primitives (`template`, `insert`, `spread`, `delegateEvents`) |
|
|
84
|
+
| `what-core/jsx-runtime` | JSX automatic runtime |
|
|
85
|
+
| `what-core/testing` | Test utilities |
|
|
86
|
+
|
|
87
|
+
## API Overview
|
|
88
|
+
|
|
89
|
+
**Reactivity** -- `signal`, `computed`, `effect`, `batch`, `untrack`, `flushSync`, `createRoot`
|
|
90
|
+
|
|
91
|
+
**Rendering** -- `h`, `Fragment`, `html`, `mount`, `template`, `insert`, `spread`, `delegateEvents`
|
|
92
|
+
|
|
93
|
+
**Hooks** -- `useState`, `useSignal`, `useComputed`, `useEffect`, `useMemo`, `useCallback`, `useRef`, `useContext`, `useReducer`, `createContext`, `onMount`, `onCleanup`, `createResource`
|
|
94
|
+
|
|
95
|
+
**Components** -- `memo`, `lazy`, `Suspense`, `ErrorBoundary`, `Show`, `For`, `Switch`, `Match`, `Island`, `Portal`
|
|
96
|
+
|
|
97
|
+
**Store** -- `createStore`, `derived`, `atom`
|
|
98
|
+
|
|
99
|
+
**Data Fetching** -- `useSWR`, `useQuery`, `useInfiniteQuery`, `invalidateQueries`, `prefetchQuery`
|
|
100
|
+
|
|
101
|
+
**Forms** -- `useForm`, `useField`, `rules`, `zodResolver`, `Input`, `Select`, `Checkbox`, `ErrorMessage`
|
|
102
|
+
|
|
103
|
+
**Animation** -- `spring`, `tween`, `easings`, `useGesture`, `useTransition`
|
|
104
|
+
|
|
105
|
+
**Accessibility** -- `useFocusTrap`, `FocusTrap`, `announce`, `SkipLink`, `useRovingTabIndex`, `VisuallyHidden`, `useId`
|
|
106
|
+
|
|
107
|
+
**Scheduler** -- `scheduleRead`, `scheduleWrite`, `measure`, `mutate`, `onResize`, `onIntersect`
|
|
108
|
+
|
|
109
|
+
**Head** -- `Head`, `clearHead`
|
|
110
|
+
|
|
111
|
+
## Links
|
|
112
|
+
|
|
113
|
+
- [Documentation](https://whatfw.com)
|
|
114
|
+
- [GitHub](https://github.com/zvndev/what-fw)
|
|
115
|
+
- [Benchmarks](https://benchmarks.whatfw.com)
|
|
116
|
+
|
|
117
|
+
## License
|
|
118
|
+
|
|
119
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "what-core",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.4",
|
|
4
4
|
"description": "What Framework - The closest framework to vanilla JS",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/what.js",
|
|
@@ -53,5 +53,5 @@
|
|
|
53
53
|
"bugs": {
|
|
54
54
|
"url": "https://github.com/zvndev/what-fw/issues"
|
|
55
55
|
},
|
|
56
|
-
"homepage": "https://
|
|
56
|
+
"homepage": "https://whatfw.com"
|
|
57
57
|
}
|
package/src/data.js
CHANGED
|
@@ -636,3 +636,20 @@ export function clearCache() {
|
|
|
636
636
|
lastFetchTimestamps.clear();
|
|
637
637
|
inFlightRequests.clear();
|
|
638
638
|
}
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* Get a snapshot of all cache entries for devtools.
|
|
642
|
+
* @internal
|
|
643
|
+
*/
|
|
644
|
+
export function __getCacheSnapshot() {
|
|
645
|
+
const entries = [];
|
|
646
|
+
for (const [key, sig] of cacheSignals) {
|
|
647
|
+
entries.push({
|
|
648
|
+
key,
|
|
649
|
+
data: sig.peek(),
|
|
650
|
+
error: errorSignals.has(key) ? errorSignals.get(key).peek() : null,
|
|
651
|
+
isValidating: validatingSignals.has(key) ? validatingSignals.get(key).peek() : false,
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
return entries;
|
|
655
|
+
}
|
package/src/dom.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Components use <what-c> wrapper elements (display:contents) for clean reconciliation.
|
|
4
4
|
// No virtual DOM tree kept in memory — we diff against the live DOM.
|
|
5
5
|
|
|
6
|
-
import { effect, batch, untrack, signal } from './reactive.js';
|
|
6
|
+
import { effect, batch, untrack, signal, __DEV__, __devtools } from './reactive.js';
|
|
7
7
|
import { reportError, _injectGetCurrentComponent, shallowEqual } from './components.js';
|
|
8
8
|
import { _setComponentRef } from './helpers.js';
|
|
9
9
|
|
|
@@ -97,6 +97,7 @@ function disposeComponent(ctx) {
|
|
|
97
97
|
try { dispose(); } catch (e) { /* effect already disposed */ }
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
if (__DEV__ && __devtools?.onComponentUnmount) __devtools.onComponentUnmount(ctx);
|
|
100
101
|
mountedComponents.delete(ctx);
|
|
101
102
|
}
|
|
102
103
|
|
|
@@ -304,6 +305,7 @@ function createComponent(vnode, parent, isSvg) {
|
|
|
304
305
|
|
|
305
306
|
// Track for disposal
|
|
306
307
|
mountedComponents.add(ctx);
|
|
308
|
+
if (__DEV__ && __devtools?.onComponentMount) __devtools.onComponentMount(ctx);
|
|
307
309
|
|
|
308
310
|
// Props signal for reactive updates from parent
|
|
309
311
|
// Match React's children semantics: 0→undefined, 1→single child, N→array
|
package/src/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// The closest framework to vanilla JS.
|
|
3
3
|
|
|
4
4
|
// Reactive primitives
|
|
5
|
-
export { signal, computed, effect, memo as signalMemo, batch, untrack, flushSync, createRoot } from './reactive.js';
|
|
5
|
+
export { signal, computed, effect, memo as signalMemo, batch, untrack, flushSync, createRoot, __setDevToolsHooks } from './reactive.js';
|
|
6
6
|
|
|
7
7
|
// Fine-grained rendering primitives
|
|
8
8
|
export { template, insert, mapArray, spread, setProp, delegateEvents, on, classList } from './render.js';
|
|
@@ -132,6 +132,7 @@ export {
|
|
|
132
132
|
setQueryData,
|
|
133
133
|
getQueryData,
|
|
134
134
|
clearCache,
|
|
135
|
+
__getCacheSnapshot,
|
|
135
136
|
} from './data.js';
|
|
136
137
|
|
|
137
138
|
// Form utilities
|
package/src/reactive.js
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
// Signals + Effects: fine-grained reactivity without virtual DOM overhead
|
|
3
3
|
|
|
4
4
|
// Dev-mode flag — build tools can dead-code-eliminate when false
|
|
5
|
-
export const __DEV__ = typeof process !== 'undefined'
|
|
5
|
+
export const __DEV__ = typeof process !== 'undefined'
|
|
6
|
+
? process.env?.NODE_ENV !== 'production'
|
|
7
|
+
: true;
|
|
6
8
|
|
|
7
9
|
// DevTools hooks — set by what-devtools when installed.
|
|
8
10
|
// These are no-ops in production (dead-code eliminated with __DEV__).
|
|
@@ -22,7 +24,7 @@ let pendingEffects = [];
|
|
|
22
24
|
// A reactive value. Reading inside an effect auto-tracks the dependency.
|
|
23
25
|
// Writing triggers only the effects that depend on this signal.
|
|
24
26
|
|
|
25
|
-
export function signal(initial) {
|
|
27
|
+
export function signal(initial, debugName) {
|
|
26
28
|
let value = initial;
|
|
27
29
|
const subs = new Set();
|
|
28
30
|
|
|
@@ -59,6 +61,10 @@ export function signal(initial) {
|
|
|
59
61
|
};
|
|
60
62
|
|
|
61
63
|
sig._signal = true;
|
|
64
|
+
if (__DEV__) {
|
|
65
|
+
sig._subs = subs;
|
|
66
|
+
if (debugName) sig._debugName = debugName;
|
|
67
|
+
}
|
|
62
68
|
|
|
63
69
|
// Notify devtools of signal creation
|
|
64
70
|
if (__DEV__ && __devtools) __devtools.onSignalCreate(sig);
|
|
@@ -175,9 +181,13 @@ function _runEffect(e) {
|
|
|
175
181
|
try {
|
|
176
182
|
const result = e.fn();
|
|
177
183
|
if (typeof result === 'function') e._cleanup = result;
|
|
184
|
+
} catch (err) {
|
|
185
|
+
if (__devtools?.onError) __devtools.onError(err, { type: 'effect', effect: e });
|
|
186
|
+
if (__DEV__) console.warn('[what] Error in stable effect:', err);
|
|
178
187
|
} finally {
|
|
179
188
|
currentEffect = prev;
|
|
180
189
|
}
|
|
190
|
+
if (__DEV__ && __devtools?.onEffectRun) __devtools.onEffectRun(e);
|
|
181
191
|
return;
|
|
182
192
|
}
|
|
183
193
|
|
|
@@ -185,6 +195,7 @@ function _runEffect(e) {
|
|
|
185
195
|
// Run effect cleanup from previous run
|
|
186
196
|
if (e._cleanup) {
|
|
187
197
|
try { e._cleanup(); } catch (err) {
|
|
198
|
+
if (__devtools?.onError) __devtools.onError(err, { type: 'effect-cleanup', effect: e });
|
|
188
199
|
if (__DEV__) console.warn('[what] Error in effect cleanup:', err);
|
|
189
200
|
}
|
|
190
201
|
e._cleanup = null;
|
|
@@ -197,9 +208,13 @@ function _runEffect(e) {
|
|
|
197
208
|
if (typeof result === 'function') {
|
|
198
209
|
e._cleanup = result;
|
|
199
210
|
}
|
|
211
|
+
} catch (err) {
|
|
212
|
+
if (__devtools?.onError) __devtools.onError(err, { type: 'effect', effect: e });
|
|
213
|
+
throw err;
|
|
200
214
|
} finally {
|
|
201
215
|
currentEffect = prev;
|
|
202
216
|
}
|
|
217
|
+
if (__DEV__ && __devtools?.onEffectRun) __devtools.onEffectRun(e);
|
|
203
218
|
}
|
|
204
219
|
|
|
205
220
|
function _disposeEffect(e) {
|
|
@@ -238,6 +253,7 @@ function notify(subs) {
|
|
|
238
253
|
e._cleanup = result;
|
|
239
254
|
}
|
|
240
255
|
} catch (err) {
|
|
256
|
+
if (__devtools?.onError) __devtools.onError(err, { type: 'effect', effect: e });
|
|
241
257
|
if (__DEV__) console.warn('[what] Error in stable effect:', err);
|
|
242
258
|
} finally {
|
|
243
259
|
currentEffect = prev;
|