@pyreon/preact-compat 0.21.0 → 0.23.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/README.md +86 -99
- package/lib/_chunks/jsx-runtime-D77N0lwB.js +157 -0
- package/lib/analysis/index.js.html +1 -1
- package/lib/hooks.js +1 -11
- package/lib/index.js +4 -0
- package/lib/jsx-runtime.js +2 -150
- package/package.json +5 -5
- package/src/index.ts +13 -0
- package/src/tests/new-apis.test.ts +43 -0
- package/lib/analysis/hooks.js.html +0 -5406
- package/lib/analysis/jsx-runtime.js.html +0 -5406
- package/lib/analysis/signals.js.html +0 -5406
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# @pyreon/preact-compat
|
|
2
2
|
|
|
3
|
-
Preact-compatible API shim that runs on Pyreon's
|
|
3
|
+
Preact-compatible API shim — write Preact-style code that runs on Pyreon's reactive engine.
|
|
4
|
+
|
|
5
|
+
`@pyreon/preact-compat` mirrors Preact's module structure (`@pyreon/preact-compat`, `@pyreon/preact-compat/hooks`, `@pyreon/preact-compat/signals`) and provides `h` / `Fragment` / `render` / `hydrate` / `Component` / `PureComponent` / `createContext` / `createRef` / `cloneElement` / `createPortal` / `lazy` / `Suspense` / `ErrorBoundary` plus the standard hooks set, all backed by Pyreon's signal-based reactivity. **This is a compat shim, not Preact** — it intentionally diverges in places where Preact's render-on-state-change model conflicts with Pyreon's run-once + fine-grained-reactivity model. The escape hatch is to drop the compat layer and use Pyreon's native API directly.
|
|
4
6
|
|
|
5
7
|
## Install
|
|
6
8
|
|
|
@@ -8,139 +10,124 @@ Preact-compatible API shim that runs on Pyreon's signal-based reactive engine. M
|
|
|
8
10
|
bun add @pyreon/preact-compat
|
|
9
11
|
```
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
Then alias your Preact imports (or use `pyreon({ compat: 'preact' })` from `@pyreon/vite-plugin` for zero code changes):
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { h, render, Fragment } from '@pyreon/preact-compat'
|
|
17
|
+
import { useState, useEffect } from '@pyreon/preact-compat/hooks'
|
|
18
|
+
import { signal, computed } from '@pyreon/preact-compat/signals'
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Quick start
|
|
12
22
|
|
|
13
23
|
```tsx
|
|
14
|
-
|
|
15
|
-
// import { render } from "preact"
|
|
16
|
-
// import { useState, useEffect } from "preact/hooks"
|
|
17
|
-
// With:
|
|
18
|
-
import { render } from '@pyreon/preact-compat'
|
|
24
|
+
import { h, render } from '@pyreon/preact-compat'
|
|
19
25
|
import { useState, useEffect } from '@pyreon/preact-compat/hooks'
|
|
20
26
|
|
|
21
27
|
function Counter() {
|
|
22
28
|
const [count, setCount] = useState(0)
|
|
29
|
+
|
|
23
30
|
useEffect(() => {
|
|
24
|
-
|
|
31
|
+
document.title = `Count: ${count()}`
|
|
25
32
|
})
|
|
26
|
-
return <button onClick={() => setCount((c) => c + 1)}>{count}</button>
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
render(<Counter />, document.getElementById('app')!)
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
### Signals
|
|
33
|
-
|
|
34
|
-
```tsx
|
|
35
|
-
import { signal, computed, effect } from '@pyreon/preact-compat/signals'
|
|
36
|
-
|
|
37
|
-
const count = signal(0)
|
|
38
|
-
const doubled = computed(() => count.value * 2)
|
|
39
33
|
|
|
40
|
-
function Display() {
|
|
41
|
-
effect(() => console.log('doubled:', doubled.value))
|
|
42
34
|
return (
|
|
43
35
|
<div>
|
|
44
|
-
<
|
|
45
|
-
<button onClick={() =>
|
|
36
|
+
<p>Count: {count()}</p>
|
|
37
|
+
<button onClick={() => setCount((prev) => prev + 1)}>+1</button>
|
|
46
38
|
</div>
|
|
47
39
|
)
|
|
48
40
|
}
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
### Class Components
|
|
52
41
|
|
|
53
|
-
|
|
54
|
-
|
|
42
|
+
render(<Counter />, document.getElementById('app')!)
|
|
43
|
+
```
|
|
55
44
|
|
|
56
|
-
|
|
57
|
-
name: string
|
|
58
|
-
}
|
|
59
|
-
interface State {
|
|
60
|
-
clicked: boolean
|
|
61
|
-
}
|
|
45
|
+
## Subpath exports
|
|
62
46
|
|
|
63
|
-
|
|
64
|
-
|
|
47
|
+
| Subpath | Surface |
|
|
48
|
+
| --------------------------------------- | --------------------------------------------------------------------------------------------- |
|
|
49
|
+
| `@pyreon/preact-compat` | Core: `h` / `createElement`, `Fragment`, `render`, `hydrate`, `Component`, `PureComponent`, `createContext` / `useContext`, `createRef`, `cloneElement`, `toChildArray`, `isValidElement`, `createPortal`, `lazy`, `Suspense`, `ErrorBoundary`, `options`, `version` |
|
|
50
|
+
| `@pyreon/preact-compat/hooks` | `useState`, `useReducer`, `useEffect`, `useLayoutEffect`, `useMemo`, `useCallback`, `useRef`, `useId`, `memo`, `forwardRef`, `useImperativeHandle`, `useDebugValue`, `useTransition`, `useDeferredValue`, `useErrorBoundary` |
|
|
51
|
+
| `@pyreon/preact-compat/signals` | `signal`, `computed`, `effect`, `batch`, `ReadonlySignal`, `WritableSignal` |
|
|
52
|
+
| `@pyreon/preact-compat/jsx-runtime` | JSX automatic runtime (`jsx`, `jsxs`, `Fragment`) |
|
|
53
|
+
| `@pyreon/preact-compat/jsx-dev-runtime` | Dev variant — same runtime, with source location info |
|
|
65
54
|
|
|
66
|
-
|
|
67
|
-
this.setState({ clicked: true })
|
|
68
|
-
}
|
|
55
|
+
## Key differences from Preact
|
|
69
56
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
57
|
+
| Behavior | Preact | `@pyreon/preact-compat` |
|
|
58
|
+
| ------------------- | ------------------------------------- | ---------------------------------------------------------------------- |
|
|
59
|
+
| Component execution | Re-runs render on every state change | Runs **once** (setup phase) |
|
|
60
|
+
| `useState` getter | Returns the value directly | Returns a **getter function** — call `count()` to read |
|
|
61
|
+
| `useEffect` deps | Controls when the effect re-runs | Deps array is **ignored** — Pyreon tracks dependencies automatically |
|
|
62
|
+
| `useCallback` | Memoizes across renders | **No-op** — returns `fn` as-is |
|
|
63
|
+
| `useMemo` | Returns the memoized value | Returns a **getter function** — call `value()` to read |
|
|
64
|
+
| `useLayoutEffect` | Fires synchronously before paint | Same as `useEffect` |
|
|
65
|
+
| Signals `.value` | Native Preact Signals API | Wrapped Pyreon signals with the same `.value` interface |
|
|
66
|
+
| Class components | Full lifecycle support | `setState` and `forceUpdate` work; lifecycle methods are not called |
|
|
67
|
+
| Hooks rules | Must be called at top level | **No restrictions** — call anywhere in component setup |
|
|
80
68
|
|
|
81
|
-
|
|
69
|
+
### Read state via a getter
|
|
82
70
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
71
|
+
```tsx
|
|
72
|
+
// Preact
|
|
73
|
+
const [count, setCount] = useState(0)
|
|
74
|
+
console.log(count) // 0
|
|
87
75
|
|
|
88
|
-
|
|
76
|
+
// @pyreon/preact-compat
|
|
77
|
+
const [count, setCount] = useState(0)
|
|
78
|
+
console.log(count()) // 0 — call the function
|
|
79
|
+
```
|
|
89
80
|
|
|
90
|
-
###
|
|
81
|
+
### No stale closures
|
|
91
82
|
|
|
92
|
-
|
|
83
|
+
Signal reads always return the current value. Preact-style `setInterval` callbacks that needed `[count]` deps to avoid stale closures Just Work without them:
|
|
93
84
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
- **`toChildArray(children)`** -- normalize children to a flat array.
|
|
103
|
-
- **`isValidElement(x)`** -- type guard for VNodes.
|
|
85
|
+
```tsx
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
const id = setInterval(() => {
|
|
88
|
+
setCount((prev) => prev + 1) // always reads the latest
|
|
89
|
+
}, 1000)
|
|
90
|
+
return () => clearInterval(id)
|
|
91
|
+
})
|
|
92
|
+
```
|
|
104
93
|
|
|
105
|
-
###
|
|
94
|
+
### Signals subpath
|
|
106
95
|
|
|
107
|
-
|
|
96
|
+
`@pyreon/preact-compat/signals` mirrors `@preact/signals` — `signal(initial)` / `computed(fn)` / `effect(fn)` / `batch(fn)` — and the returned objects expose a `.value` getter/setter so existing `@preact/signals` consumer code keeps working.
|
|
108
97
|
|
|
109
|
-
-
|
|
110
|
-
- **`useReducer(reducer, initial)`** -- returns `[getter, dispatch]`.
|
|
111
|
-
- **`useEffect(fn, deps?)`** -- reactive effect. `[]` deps means mount-only.
|
|
112
|
-
- **`useLayoutEffect`** -- alias for `useEffect`.
|
|
113
|
-
- **`useMemo(fn, deps?)`** -- returns a computed getter. Deps are ignored.
|
|
114
|
-
- **`useCallback(fn, deps?)`** -- returns `fn` as-is (no-op).
|
|
115
|
-
- **`useRef(initial?)`** -- returns `{ current }`.
|
|
116
|
-
- **`useId()`** -- stable unique string per component instance.
|
|
117
|
-
- **`useErrorBoundary`** -- alias for `onErrorCaptured`.
|
|
98
|
+
## Drop-in compat mode
|
|
118
99
|
|
|
119
|
-
|
|
100
|
+
`@pyreon/vite-plugin` can alias every `preact` / `preact/hooks` / `@preact/signals` import to this package — no code changes:
|
|
120
101
|
|
|
121
|
-
|
|
102
|
+
```ts
|
|
103
|
+
// vite.config.ts
|
|
104
|
+
import pyreon from '@pyreon/vite-plugin'
|
|
105
|
+
export default { plugins: [pyreon({ compat: 'preact' })] }
|
|
106
|
+
```
|
|
122
107
|
|
|
123
|
-
|
|
124
|
-
- **`computed(fn)`** -- returns `{ value }` read-only accessor.
|
|
125
|
-
- **`effect(fn)`** -- reactive side effect, returns dispose function.
|
|
126
|
-
- **`batch(fn)`** -- coalesce multiple signal writes.
|
|
108
|
+
`tsconfig.json`:
|
|
127
109
|
|
|
128
|
-
|
|
110
|
+
```jsonc
|
|
111
|
+
{
|
|
112
|
+
"compilerOptions": {
|
|
113
|
+
"jsx": "react-jsx",
|
|
114
|
+
"jsxImportSource": "@pyreon/preact-compat"
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
129
118
|
|
|
130
|
-
|
|
119
|
+
## Gotchas
|
|
131
120
|
|
|
132
|
-
|
|
121
|
+
- **Run-once mental model.** Components don't re-run on state change — read signals/getters where they're used, not destructured into locals at the top of the function.
|
|
122
|
+
- **`useEffect` deps are ignored.** Dependency tracking is automatic. Effects re-run when any signal they read changes.
|
|
123
|
+
- **`useCallback` is a no-op.** Pyreon doesn't need referential stability across renders because there are no renders.
|
|
124
|
+
- **Class-component lifecycle methods don't fire.** `setState` + `forceUpdate` work, but `componentDidMount` / `componentDidUpdate` / `componentWillUnmount` are not invoked. Use `onMount` / `onUnmount` from `@pyreon/core` for lifecycle.
|
|
125
|
+
- **`version`** reports `10.0.0-pyreon` — code that gates on Preact 10 keeps working; code that asserts equality to a specific Preact version won't match.
|
|
133
126
|
|
|
134
|
-
|
|
135
|
-
import { nativeCompat, provide, createContext } from '@pyreon/core'
|
|
127
|
+
## Documentation
|
|
136
128
|
|
|
137
|
-
|
|
129
|
+
Full docs: [docs.pyreon.dev/docs/preact-compat](https://docs.pyreon.dev/docs/preact-compat) (or `docs/docs/preact-compat.md` in this repo).
|
|
138
130
|
|
|
139
|
-
|
|
140
|
-
provide(MyCtx, props.value)
|
|
141
|
-
return props.children as never
|
|
142
|
-
}
|
|
143
|
-
nativeCompat(MyProvider) // ← required for compat-mode apps
|
|
144
|
-
```
|
|
131
|
+
## License
|
|
145
132
|
|
|
146
|
-
|
|
133
|
+
MIT
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { Fragment, h, isNativeCompat, mapCompatDomProps, onUnmount } from "@pyreon/core";
|
|
2
|
+
import { signal } from "@pyreon/reactivity";
|
|
3
|
+
|
|
4
|
+
//#region src/jsx-runtime.ts
|
|
5
|
+
let _currentCtx = null;
|
|
6
|
+
let _hookIndex = 0;
|
|
7
|
+
function getCurrentCtx() {
|
|
8
|
+
return _currentCtx;
|
|
9
|
+
}
|
|
10
|
+
function getHookIndex() {
|
|
11
|
+
return _hookIndex++;
|
|
12
|
+
}
|
|
13
|
+
function beginRender(ctx) {
|
|
14
|
+
_currentCtx = ctx;
|
|
15
|
+
_hookIndex = 0;
|
|
16
|
+
ctx.pendingEffects = [];
|
|
17
|
+
ctx.pendingLayoutEffects = [];
|
|
18
|
+
}
|
|
19
|
+
function endRender() {
|
|
20
|
+
_currentCtx = null;
|
|
21
|
+
_hookIndex = 0;
|
|
22
|
+
}
|
|
23
|
+
function runLayoutEffects(entries) {
|
|
24
|
+
for (const entry of entries) {
|
|
25
|
+
if (entry.cleanup) entry.cleanup();
|
|
26
|
+
const cleanup = entry.fn();
|
|
27
|
+
entry.cleanup = typeof cleanup === "function" ? cleanup : void 0;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function scheduleEffects(ctx, entries) {
|
|
31
|
+
if (entries.length === 0) return;
|
|
32
|
+
queueMicrotask(() => {
|
|
33
|
+
for (const entry of entries) {
|
|
34
|
+
if (ctx.unmounted) return;
|
|
35
|
+
if (entry.cleanup) entry.cleanup();
|
|
36
|
+
const cleanup = entry.fn();
|
|
37
|
+
entry.cleanup = typeof cleanup === "function" ? cleanup : void 0;
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
function isClassComponent(type) {
|
|
42
|
+
return type.prototype != null && typeof type.prototype.render === "function";
|
|
43
|
+
}
|
|
44
|
+
function wrapClassComponent(ClassComp) {
|
|
45
|
+
const wrapped = ((props) => {
|
|
46
|
+
const instance = new ClassComp(props);
|
|
47
|
+
const version = signal(0);
|
|
48
|
+
let updateScheduled = false;
|
|
49
|
+
const origSetState = instance.setState.bind(instance);
|
|
50
|
+
instance.setState = (partial) => {
|
|
51
|
+
origSetState(partial);
|
|
52
|
+
if (!updateScheduled) {
|
|
53
|
+
updateScheduled = true;
|
|
54
|
+
queueMicrotask(() => {
|
|
55
|
+
updateScheduled = false;
|
|
56
|
+
version.set(version.peek() + 1);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
instance.forceUpdate = () => {
|
|
61
|
+
version.set(version.peek() + 1);
|
|
62
|
+
};
|
|
63
|
+
let didMountFired = false;
|
|
64
|
+
onUnmount(() => {
|
|
65
|
+
if (typeof instance.componentWillUnmount === "function") instance.componentWillUnmount();
|
|
66
|
+
});
|
|
67
|
+
return () => {
|
|
68
|
+
const ver = version();
|
|
69
|
+
instance.props = props;
|
|
70
|
+
if (didMountFired && ver > 0 && typeof instance.shouldComponentUpdate === "function") {
|
|
71
|
+
if (!instance.shouldComponentUpdate(props, instance.state)) return instance._lastResult;
|
|
72
|
+
}
|
|
73
|
+
const result = instance.render();
|
|
74
|
+
instance._lastResult = result;
|
|
75
|
+
if (!didMountFired) {
|
|
76
|
+
didMountFired = true;
|
|
77
|
+
if (typeof instance.componentDidMount === "function") queueMicrotask(() => instance.componentDidMount());
|
|
78
|
+
} else if (ver > 0) {
|
|
79
|
+
if (typeof instance.componentDidUpdate === "function") queueMicrotask(() => instance.componentDidUpdate());
|
|
80
|
+
}
|
|
81
|
+
return result;
|
|
82
|
+
};
|
|
83
|
+
});
|
|
84
|
+
return wrapped;
|
|
85
|
+
}
|
|
86
|
+
const _wrapperCache = /* @__PURE__ */ new WeakMap();
|
|
87
|
+
function wrapCompatComponent(preactComponent) {
|
|
88
|
+
let wrapped = _wrapperCache.get(preactComponent);
|
|
89
|
+
if (wrapped) return wrapped;
|
|
90
|
+
if (isClassComponent(preactComponent)) {
|
|
91
|
+
wrapped = wrapClassComponent(preactComponent);
|
|
92
|
+
_wrapperCache.set(preactComponent, wrapped);
|
|
93
|
+
return wrapped;
|
|
94
|
+
}
|
|
95
|
+
wrapped = ((props) => {
|
|
96
|
+
const ctx = {
|
|
97
|
+
hooks: [],
|
|
98
|
+
scheduleRerender: () => {},
|
|
99
|
+
pendingEffects: [],
|
|
100
|
+
pendingLayoutEffects: [],
|
|
101
|
+
unmounted: false
|
|
102
|
+
};
|
|
103
|
+
const version = signal(0);
|
|
104
|
+
let updateScheduled = false;
|
|
105
|
+
ctx.scheduleRerender = () => {
|
|
106
|
+
if (ctx.unmounted || updateScheduled) return;
|
|
107
|
+
updateScheduled = true;
|
|
108
|
+
queueMicrotask(() => {
|
|
109
|
+
updateScheduled = false;
|
|
110
|
+
if (!ctx.unmounted) version.set(version.peek() + 1);
|
|
111
|
+
});
|
|
112
|
+
};
|
|
113
|
+
onUnmount(() => {
|
|
114
|
+
ctx.unmounted = true;
|
|
115
|
+
for (const hook of ctx.hooks) if (hook && typeof hook === "object" && "cleanup" in hook) {
|
|
116
|
+
const entry = hook;
|
|
117
|
+
if (typeof entry.cleanup === "function") entry.cleanup();
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
return () => {
|
|
121
|
+
version();
|
|
122
|
+
beginRender(ctx);
|
|
123
|
+
const result = preactComponent(props);
|
|
124
|
+
const layoutEffects = ctx.pendingLayoutEffects;
|
|
125
|
+
const effects = ctx.pendingEffects;
|
|
126
|
+
endRender();
|
|
127
|
+
runLayoutEffects(layoutEffects);
|
|
128
|
+
scheduleEffects(ctx, effects);
|
|
129
|
+
return result;
|
|
130
|
+
};
|
|
131
|
+
});
|
|
132
|
+
_wrapperCache.set(preactComponent, wrapped);
|
|
133
|
+
return wrapped;
|
|
134
|
+
}
|
|
135
|
+
function jsx(type, props, key) {
|
|
136
|
+
const { children, ...rest } = props;
|
|
137
|
+
const propsWithKey = key != null ? {
|
|
138
|
+
...rest,
|
|
139
|
+
key
|
|
140
|
+
} : rest;
|
|
141
|
+
if (typeof type === "function") {
|
|
142
|
+
const componentProps = children !== void 0 ? {
|
|
143
|
+
...propsWithKey,
|
|
144
|
+
children
|
|
145
|
+
} : propsWithKey;
|
|
146
|
+
if (isNativeCompat(type)) return h(type, componentProps);
|
|
147
|
+
return h(wrapCompatComponent(type), componentProps);
|
|
148
|
+
}
|
|
149
|
+
const childArray = children === void 0 ? [] : Array.isArray(children) ? children : [children];
|
|
150
|
+
mapCompatDomProps(propsWithKey, type);
|
|
151
|
+
return h(type, propsWithKey, ...childArray);
|
|
152
|
+
}
|
|
153
|
+
const jsxs = jsx;
|
|
154
|
+
|
|
155
|
+
//#endregion
|
|
156
|
+
export { jsxs as a, jsx as i, getCurrentCtx as n, getHookIndex as r, Fragment as t };
|
|
157
|
+
//# sourceMappingURL=jsx-runtime-D77N0lwB.js.map
|
|
@@ -5386,7 +5386,7 @@ var drawChart = (function (exports) {
|
|
|
5386
5386
|
</script>
|
|
5387
5387
|
<script>
|
|
5388
5388
|
/*<!--*/
|
|
5389
|
-
const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src/index.ts","uid":"
|
|
5389
|
+
const data = {"version":2,"tree":{"name":"root","children":[{"name":"hooks.js","children":[{"name":"src/hooks.ts","uid":"cdd5db5a-1"}]},{"name":"index.js","children":[{"name":"src/index.ts","uid":"cdd5db5a-3"}]},{"name":"jsx-runtime.js","children":[{"name":"src/jsx-dev-runtime.ts","uid":"cdd5db5a-5"}]},{"name":"signals.js","children":[{"name":"src/signals.ts","uid":"cdd5db5a-7"}]},{"name":"_chunks/jsx-runtime-D77N0lwB.js","children":[{"name":"src/jsx-runtime.ts","uid":"cdd5db5a-9"}]}],"isRoot":true},"nodeParts":{"cdd5db5a-1":{"renderedLength":6949,"gzipLength":1987,"brotliLength":0,"metaUid":"cdd5db5a-0"},"cdd5db5a-3":{"renderedLength":3350,"gzipLength":1329,"brotliLength":0,"metaUid":"cdd5db5a-2"},"cdd5db5a-5":{"renderedLength":0,"gzipLength":0,"brotliLength":0,"metaUid":"cdd5db5a-4"},"cdd5db5a-7":{"renderedLength":876,"gzipLength":383,"brotliLength":0,"metaUid":"cdd5db5a-6"},"cdd5db5a-9":{"renderedLength":4364,"gzipLength":1292,"brotliLength":0,"metaUid":"cdd5db5a-8"}},"nodeMetas":{"cdd5db5a-0":{"id":"/src/hooks.ts","moduleParts":{"hooks.js":"cdd5db5a-1"},"imported":[{"uid":"cdd5db5a-10"},{"uid":"cdd5db5a-8"}],"importedBy":[],"isEntry":true},"cdd5db5a-2":{"id":"/src/index.ts","moduleParts":{"index.js":"cdd5db5a-3"},"imported":[{"uid":"cdd5db5a-10"},{"uid":"cdd5db5a-11"},{"uid":"cdd5db5a-12"}],"importedBy":[],"isEntry":true},"cdd5db5a-4":{"id":"/src/jsx-dev-runtime.ts","moduleParts":{"jsx-runtime.js":"cdd5db5a-5"},"imported":[{"uid":"cdd5db5a-8"}],"importedBy":[],"isEntry":true},"cdd5db5a-6":{"id":"/src/signals.ts","moduleParts":{"signals.js":"cdd5db5a-7"},"imported":[{"uid":"cdd5db5a-11"}],"importedBy":[],"isEntry":true},"cdd5db5a-8":{"id":"/src/jsx-runtime.ts","moduleParts":{"_chunks/jsx-runtime-D77N0lwB.js":"cdd5db5a-9"},"imported":[{"uid":"cdd5db5a-10"},{"uid":"cdd5db5a-11"}],"importedBy":[{"uid":"cdd5db5a-0"},{"uid":"cdd5db5a-4"}]},"cdd5db5a-10":{"id":"@pyreon/core","moduleParts":{},"imported":[],"importedBy":[{"uid":"cdd5db5a-0"},{"uid":"cdd5db5a-8"},{"uid":"cdd5db5a-2"}]},"cdd5db5a-11":{"id":"@pyreon/reactivity","moduleParts":{},"imported":[],"importedBy":[{"uid":"cdd5db5a-8"},{"uid":"cdd5db5a-2"},{"uid":"cdd5db5a-6"}]},"cdd5db5a-12":{"id":"@pyreon/runtime-dom","moduleParts":{},"imported":[],"importedBy":[{"uid":"cdd5db5a-2"}]}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
|
|
5390
5390
|
|
|
5391
5391
|
const run = () => {
|
|
5392
5392
|
const width = window.innerWidth;
|
package/lib/hooks.js
CHANGED
|
@@ -1,16 +1,6 @@
|
|
|
1
|
+
import { n as getCurrentCtx, r as getHookIndex } from "./_chunks/jsx-runtime-D77N0lwB.js";
|
|
1
2
|
import { onErrorCaptured, shallowEqualProps, useContext } from "@pyreon/core";
|
|
2
3
|
|
|
3
|
-
//#region src/jsx-runtime.ts
|
|
4
|
-
let _currentCtx = null;
|
|
5
|
-
let _hookIndex = 0;
|
|
6
|
-
function getCurrentCtx() {
|
|
7
|
-
return _currentCtx;
|
|
8
|
-
}
|
|
9
|
-
function getHookIndex() {
|
|
10
|
-
return _hookIndex++;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
//#endregion
|
|
14
4
|
//#region src/hooks.ts
|
|
15
5
|
function requireCtx() {
|
|
16
6
|
const ctx = getCurrentCtx();
|
package/lib/index.js
CHANGED
|
@@ -108,6 +108,10 @@ function toChildArray(children) {
|
|
|
108
108
|
}
|
|
109
109
|
function flatten(value, out) {
|
|
110
110
|
if (value == null || typeof value === "boolean") return;
|
|
111
|
+
if (typeof value === "function") {
|
|
112
|
+
flatten(value(), out);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
111
115
|
if (Array.isArray(value)) for (const child of value) flatten(child, out);
|
|
112
116
|
else out.push(value);
|
|
113
117
|
}
|
package/lib/jsx-runtime.js
CHANGED
|
@@ -1,151 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { signal } from "@pyreon/reactivity";
|
|
1
|
+
import { a as jsxs, i as jsx, t as Fragment } from "./_chunks/jsx-runtime-D77N0lwB.js";
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
let _currentCtx = null;
|
|
6
|
-
let _hookIndex = 0;
|
|
7
|
-
function beginRender(ctx) {
|
|
8
|
-
_currentCtx = ctx;
|
|
9
|
-
_hookIndex = 0;
|
|
10
|
-
ctx.pendingEffects = [];
|
|
11
|
-
ctx.pendingLayoutEffects = [];
|
|
12
|
-
}
|
|
13
|
-
function endRender() {
|
|
14
|
-
_currentCtx = null;
|
|
15
|
-
_hookIndex = 0;
|
|
16
|
-
}
|
|
17
|
-
function runLayoutEffects(entries) {
|
|
18
|
-
for (const entry of entries) {
|
|
19
|
-
if (entry.cleanup) entry.cleanup();
|
|
20
|
-
const cleanup = entry.fn();
|
|
21
|
-
entry.cleanup = typeof cleanup === "function" ? cleanup : void 0;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
function scheduleEffects(ctx, entries) {
|
|
25
|
-
if (entries.length === 0) return;
|
|
26
|
-
queueMicrotask(() => {
|
|
27
|
-
for (const entry of entries) {
|
|
28
|
-
if (ctx.unmounted) return;
|
|
29
|
-
if (entry.cleanup) entry.cleanup();
|
|
30
|
-
const cleanup = entry.fn();
|
|
31
|
-
entry.cleanup = typeof cleanup === "function" ? cleanup : void 0;
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
function isClassComponent(type) {
|
|
36
|
-
return type.prototype != null && typeof type.prototype.render === "function";
|
|
37
|
-
}
|
|
38
|
-
function wrapClassComponent(ClassComp) {
|
|
39
|
-
const wrapped = ((props) => {
|
|
40
|
-
const instance = new ClassComp(props);
|
|
41
|
-
const version = signal(0);
|
|
42
|
-
let updateScheduled = false;
|
|
43
|
-
const origSetState = instance.setState.bind(instance);
|
|
44
|
-
instance.setState = (partial) => {
|
|
45
|
-
origSetState(partial);
|
|
46
|
-
if (!updateScheduled) {
|
|
47
|
-
updateScheduled = true;
|
|
48
|
-
queueMicrotask(() => {
|
|
49
|
-
updateScheduled = false;
|
|
50
|
-
version.set(version.peek() + 1);
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
instance.forceUpdate = () => {
|
|
55
|
-
version.set(version.peek() + 1);
|
|
56
|
-
};
|
|
57
|
-
let didMountFired = false;
|
|
58
|
-
onUnmount(() => {
|
|
59
|
-
if (typeof instance.componentWillUnmount === "function") instance.componentWillUnmount();
|
|
60
|
-
});
|
|
61
|
-
return () => {
|
|
62
|
-
const ver = version();
|
|
63
|
-
instance.props = props;
|
|
64
|
-
if (didMountFired && ver > 0 && typeof instance.shouldComponentUpdate === "function") {
|
|
65
|
-
if (!instance.shouldComponentUpdate(props, instance.state)) return instance._lastResult;
|
|
66
|
-
}
|
|
67
|
-
const result = instance.render();
|
|
68
|
-
instance._lastResult = result;
|
|
69
|
-
if (!didMountFired) {
|
|
70
|
-
didMountFired = true;
|
|
71
|
-
if (typeof instance.componentDidMount === "function") queueMicrotask(() => instance.componentDidMount());
|
|
72
|
-
} else if (ver > 0) {
|
|
73
|
-
if (typeof instance.componentDidUpdate === "function") queueMicrotask(() => instance.componentDidUpdate());
|
|
74
|
-
}
|
|
75
|
-
return result;
|
|
76
|
-
};
|
|
77
|
-
});
|
|
78
|
-
return wrapped;
|
|
79
|
-
}
|
|
80
|
-
const _wrapperCache = /* @__PURE__ */ new WeakMap();
|
|
81
|
-
function wrapCompatComponent(preactComponent) {
|
|
82
|
-
let wrapped = _wrapperCache.get(preactComponent);
|
|
83
|
-
if (wrapped) return wrapped;
|
|
84
|
-
if (isClassComponent(preactComponent)) {
|
|
85
|
-
wrapped = wrapClassComponent(preactComponent);
|
|
86
|
-
_wrapperCache.set(preactComponent, wrapped);
|
|
87
|
-
return wrapped;
|
|
88
|
-
}
|
|
89
|
-
wrapped = ((props) => {
|
|
90
|
-
const ctx = {
|
|
91
|
-
hooks: [],
|
|
92
|
-
scheduleRerender: () => {},
|
|
93
|
-
pendingEffects: [],
|
|
94
|
-
pendingLayoutEffects: [],
|
|
95
|
-
unmounted: false
|
|
96
|
-
};
|
|
97
|
-
const version = signal(0);
|
|
98
|
-
let updateScheduled = false;
|
|
99
|
-
ctx.scheduleRerender = () => {
|
|
100
|
-
if (ctx.unmounted || updateScheduled) return;
|
|
101
|
-
updateScheduled = true;
|
|
102
|
-
queueMicrotask(() => {
|
|
103
|
-
updateScheduled = false;
|
|
104
|
-
if (!ctx.unmounted) version.set(version.peek() + 1);
|
|
105
|
-
});
|
|
106
|
-
};
|
|
107
|
-
onUnmount(() => {
|
|
108
|
-
ctx.unmounted = true;
|
|
109
|
-
for (const hook of ctx.hooks) if (hook && typeof hook === "object" && "cleanup" in hook) {
|
|
110
|
-
const entry = hook;
|
|
111
|
-
if (typeof entry.cleanup === "function") entry.cleanup();
|
|
112
|
-
}
|
|
113
|
-
});
|
|
114
|
-
return () => {
|
|
115
|
-
version();
|
|
116
|
-
beginRender(ctx);
|
|
117
|
-
const result = preactComponent(props);
|
|
118
|
-
const layoutEffects = ctx.pendingLayoutEffects;
|
|
119
|
-
const effects = ctx.pendingEffects;
|
|
120
|
-
endRender();
|
|
121
|
-
runLayoutEffects(layoutEffects);
|
|
122
|
-
scheduleEffects(ctx, effects);
|
|
123
|
-
return result;
|
|
124
|
-
};
|
|
125
|
-
});
|
|
126
|
-
_wrapperCache.set(preactComponent, wrapped);
|
|
127
|
-
return wrapped;
|
|
128
|
-
}
|
|
129
|
-
function jsx(type, props, key) {
|
|
130
|
-
const { children, ...rest } = props;
|
|
131
|
-
const propsWithKey = key != null ? {
|
|
132
|
-
...rest,
|
|
133
|
-
key
|
|
134
|
-
} : rest;
|
|
135
|
-
if (typeof type === "function") {
|
|
136
|
-
const componentProps = children !== void 0 ? {
|
|
137
|
-
...propsWithKey,
|
|
138
|
-
children
|
|
139
|
-
} : propsWithKey;
|
|
140
|
-
if (isNativeCompat(type)) return h(type, componentProps);
|
|
141
|
-
return h(wrapCompatComponent(type), componentProps);
|
|
142
|
-
}
|
|
143
|
-
const childArray = children === void 0 ? [] : Array.isArray(children) ? children : [children];
|
|
144
|
-
mapCompatDomProps(propsWithKey, type);
|
|
145
|
-
return h(type, propsWithKey, ...childArray);
|
|
146
|
-
}
|
|
147
|
-
const jsxs = jsx;
|
|
148
|
-
|
|
149
|
-
//#endregion
|
|
150
|
-
export { Fragment, jsx, jsxs };
|
|
151
|
-
//# sourceMappingURL=jsx-runtime.js.map
|
|
3
|
+
export { Fragment, jsx, jsxs };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pyreon/preact-compat",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"description": "Preact-compatible API shim for Pyreon — write Preact-style code that runs on Pyreon's reactive engine",
|
|
5
5
|
"homepage": "https://github.com/pyreon/pyreon/tree/main/packages/preact-compat#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -64,13 +64,13 @@
|
|
|
64
64
|
"prepublishOnly": "bun run build"
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
|
-
"@pyreon/core": "^0.
|
|
68
|
-
"@pyreon/reactivity": "^0.
|
|
69
|
-
"@pyreon/runtime-dom": "^0.
|
|
67
|
+
"@pyreon/core": "^0.23.0",
|
|
68
|
+
"@pyreon/reactivity": "^0.23.0",
|
|
69
|
+
"@pyreon/runtime-dom": "^0.23.0"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@happy-dom/global-registrator": "^20.8.9",
|
|
73
|
-
"@pyreon/test-utils": "^0.13.
|
|
73
|
+
"@pyreon/test-utils": "^0.13.10",
|
|
74
74
|
"@vitest/browser-playwright": "^4.1.4",
|
|
75
75
|
"happy-dom": "^20.8.3"
|
|
76
76
|
}
|
package/src/index.ts
CHANGED
|
@@ -188,6 +188,19 @@ export function toChildArray(children: NestedChildren): VNodeChild[] {
|
|
|
188
188
|
|
|
189
189
|
function flatten(value: NestedChildren, out: VNodeChild[]): void {
|
|
190
190
|
if (value == null || typeof value === 'boolean') return
|
|
191
|
+
// Unwrap the Pyreon compiler's `() => x` accessor wrap. When a parent
|
|
192
|
+
// emits `<MyComp>{data.map(fn)}</MyComp>` (any non-stable expression
|
|
193
|
+
// as children — CallExpression, ConditionalExpression, etc.), the
|
|
194
|
+
// compiler's prop-inlining pass rewrites it as
|
|
195
|
+
// `MyComp({ children: () => data.map(fn) })`. Iterating the function
|
|
196
|
+
// as a child would silently treat the function as ONE child and the
|
|
197
|
+
// downstream `toChildArray` call would lose every real child after
|
|
198
|
+
// position 0. Mirrors the kinetic Iterator + elements Iterator fix
|
|
199
|
+
// from PRs #731 / #736.
|
|
200
|
+
if (typeof value === 'function') {
|
|
201
|
+
flatten((value as () => NestedChildren)(), out)
|
|
202
|
+
return
|
|
203
|
+
}
|
|
191
204
|
if (Array.isArray(value)) {
|
|
192
205
|
for (const child of value) {
|
|
193
206
|
flatten(child, out)
|