@pyreon/vue-compat 0.22.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 +71 -143
- package/lib/_chunks/jsx-runtime-CG6mH_9E.js +113 -0
- package/lib/analysis/index.js.html +1 -1
- package/lib/index.js +17 -16
- package/lib/jsx-runtime.js +2 -106
- package/package.json +5 -5
- package/src/index.ts +33 -6
- package/src/tests/provide-stack-leak-repro.test.ts +81 -0
- package/lib/analysis/jsx-runtime.js.html +0 -5406
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# @pyreon/vue-compat
|
|
2
2
|
|
|
3
|
-
Vue 3 Composition API shim that runs on Pyreon's
|
|
3
|
+
Vue 3 Composition API shim — write Vue-style code that runs on Pyreon's reactive engine.
|
|
4
|
+
|
|
5
|
+
`@pyreon/vue-compat` provides the Vue 3 Composition API surface (`ref`, `shallowRef`, `computed`, `reactive`, `shallowReactive`, `readonly`, `shallowReadonly`, `toRef`, `toRefs`, `toRaw`, `toValue`, `unref`, `isRef`, `isReactive`, `isReadonly`, `isProxy`, `markRaw`, `triggerRef`, `watch`, `watchEffect`, lifecycle hooks `onMounted` / `onUnmounted` / `onUpdated` / `onBeforeMount` / `onBeforeUnmount`, `nextTick`, `provide` / `inject`, `defineComponent`, `defineAsyncComponent`, `createApp`, `effectScope` / `getCurrentScope` / `onScopeDispose`, error/render-track hooks, `<Teleport>`, `<KeepAlive>`, `<Transition>`) all running on Pyreon's reactive engine. **This is a runtime shim, not Vue** — it covers what code imports, NOT the Single-File Component compiler. `.vue` files require a separate SFC compiler.
|
|
4
6
|
|
|
5
7
|
## Install
|
|
6
8
|
|
|
@@ -8,178 +10,104 @@ Vue 3 Composition API shim that runs on Pyreon's signal-based reactive engine. M
|
|
|
8
10
|
bun add @pyreon/vue-compat
|
|
9
11
|
```
|
|
10
12
|
|
|
11
|
-
## Quick
|
|
13
|
+
## Quick start
|
|
12
14
|
|
|
13
15
|
```tsx
|
|
14
|
-
|
|
15
|
-
// import { ref, computed, watch } from "vue"
|
|
16
|
-
// With:
|
|
17
|
-
import { ref, computed, watch } from '@pyreon/vue-compat'
|
|
16
|
+
import { ref, computed, watch, onMounted } from '@pyreon/vue-compat'
|
|
18
17
|
|
|
19
18
|
function Counter() {
|
|
20
19
|
const count = ref(0)
|
|
21
20
|
const doubled = computed(() => count.value * 2)
|
|
22
21
|
|
|
23
|
-
watch(count, (
|
|
24
|
-
console.log(`count: ${
|
|
22
|
+
watch(count, (next, prev) => {
|
|
23
|
+
console.log(`count: ${prev} → ${next}`)
|
|
25
24
|
})
|
|
26
25
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
<span>{doubled.value}</span>
|
|
30
|
-
<button onClick={() => count.value++}>Count: {count.value}</button>
|
|
31
|
-
</div>
|
|
32
|
-
)
|
|
33
|
-
}
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
### Reactive Objects
|
|
37
|
-
|
|
38
|
-
```tsx
|
|
39
|
-
import { reactive, watchEffect } from '@pyreon/vue-compat'
|
|
40
|
-
|
|
41
|
-
function UserForm() {
|
|
42
|
-
const form = reactive({ name: '', email: '' })
|
|
43
|
-
|
|
44
|
-
watchEffect(() => {
|
|
45
|
-
console.log('form changed:', form.name, form.email)
|
|
26
|
+
onMounted(() => {
|
|
27
|
+
console.log('mounted')
|
|
46
28
|
})
|
|
47
29
|
|
|
48
30
|
return (
|
|
49
31
|
<div>
|
|
50
|
-
<
|
|
51
|
-
|
|
52
|
-
onInput={(e) => (form.name = e.currentTarget.value)}
|
|
53
|
-
placeholder="Name"
|
|
54
|
-
/>
|
|
55
|
-
<input
|
|
56
|
-
value={form.email}
|
|
57
|
-
onInput={(e) => (form.email = e.currentTarget.value)}
|
|
58
|
-
placeholder="Email"
|
|
59
|
-
/>
|
|
60
|
-
<p>
|
|
61
|
-
Hello, {form.name} ({form.email})
|
|
62
|
-
</p>
|
|
32
|
+
<p>Count: {count.value}, doubled: {doubled.value}</p>
|
|
33
|
+
<button onClick={() => count.value++}>+1</button>
|
|
63
34
|
</div>
|
|
64
35
|
)
|
|
65
36
|
}
|
|
66
37
|
```
|
|
67
38
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
39
|
+
## Subpath exports
|
|
40
|
+
|
|
41
|
+
| Subpath | Surface |
|
|
42
|
+
| ------------------------------------ | --------------------------------------------------------------------------------------------- |
|
|
43
|
+
| `@pyreon/vue-compat` | Full Composition API surface — see API table below |
|
|
44
|
+
| `@pyreon/vue-compat/jsx-runtime` | JSX automatic runtime (`jsx`, `jsxs`, `Fragment`) |
|
|
45
|
+
| `@pyreon/vue-compat/jsx-dev-runtime` | Dev variant — same runtime |
|
|
46
|
+
|
|
47
|
+
## API surface
|
|
48
|
+
|
|
49
|
+
| Category | Exports |
|
|
50
|
+
| ---------------- | --------------------------------------------------------------------------------------------- |
|
|
51
|
+
| Refs | `ref`, `shallowRef`, `triggerRef`, `isRef`, `unref`, `toValue`, `toRef`, `toRefs` |
|
|
52
|
+
| Computed | `computed` (getter form + writable `{ get, set }` form) |
|
|
53
|
+
| Reactive | `reactive`, `shallowReactive`, `readonly`, `shallowReadonly`, `toRaw`, `markRaw`, `isReactive`, `isReadonly`, `isProxy` |
|
|
54
|
+
| Watchers | `watch`, `watchEffect`, `WatchOptions` |
|
|
55
|
+
| Lifecycle | `onMounted`, `onUnmounted`, `onUpdated`, `onBeforeMount`, `onBeforeUnmount`, `onErrorCaptured`, `onRenderTracked`, `onRenderTriggered` |
|
|
56
|
+
| Scheduling | `nextTick` |
|
|
57
|
+
| DI | `provide`, `inject` |
|
|
58
|
+
| Components | `defineComponent`, `defineAsyncComponent`, `createApp(App, props?)` |
|
|
59
|
+
| Scope | `effectScope`, `getCurrentScope`, `onScopeDispose` |
|
|
60
|
+
| Built-ins | `Teleport`, `KeepAlive`, `Transition` |
|
|
61
|
+
| JSX | `h`, `Fragment` |
|
|
62
|
+
|
|
63
|
+
## Drop-in compat mode
|
|
64
|
+
|
|
65
|
+
`@pyreon/vite-plugin` can alias every `vue` import to this package:
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
// vite.config.ts
|
|
69
|
+
import pyreon from '@pyreon/vite-plugin'
|
|
70
|
+
export default { plugins: [pyreon({ compat: 'vue' })] }
|
|
92
71
|
```
|
|
93
72
|
|
|
94
|
-
|
|
73
|
+
`tsconfig.json`:
|
|
95
74
|
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
75
|
+
```jsonc
|
|
76
|
+
{
|
|
77
|
+
"compilerOptions": {
|
|
78
|
+
"jsx": "react-jsx",
|
|
79
|
+
"jsxImportSource": "@pyreon/vue-compat"
|
|
80
|
+
}
|
|
102
81
|
}
|
|
103
|
-
|
|
104
|
-
const app = createApp(App)
|
|
105
|
-
app.mount('#app')
|
|
106
82
|
```
|
|
107
83
|
|
|
108
|
-
##
|
|
109
|
-
|
|
110
|
-
- **No virtual DOM.** Pyreon uses fine-grained reactivity -- no diffing, no re-renders.
|
|
111
|
-
- **Components run once** (setup phase only).
|
|
112
|
-
- **`reactive()` uses Pyreon's store proxy** with deep signal wrapping.
|
|
113
|
-
- **`readonly()` is strict** -- setting any property (including symbols) throws an error.
|
|
114
|
-
- **`provide` / `inject` uses Pyreon's context system** -- fully isolated per component tree.
|
|
115
|
-
|
|
116
|
-
## API
|
|
117
|
-
|
|
118
|
-
### Reactivity: Refs
|
|
119
|
-
|
|
120
|
-
- **`ref(value)`** -- returns `{ value }` with reactive `.value`.
|
|
121
|
-
- **`shallowRef(value)`** -- shallow reactive ref (no deep tracking).
|
|
122
|
-
- **`triggerRef(ref)`** -- force subscribers to re-run.
|
|
123
|
-
- **`isRef(val)`** -- type guard.
|
|
124
|
-
- **`unref(val)`** -- unwrap a ref or return value as-is.
|
|
125
|
-
- **`toRef(obj, key)`** -- create a ref bound to an object property.
|
|
126
|
-
- **`toRefs(obj)`** -- convert all properties to refs.
|
|
127
|
-
|
|
128
|
-
### Reactivity: Computed
|
|
129
|
-
|
|
130
|
-
- **`computed(fn)`** -- read-only computed ref.
|
|
131
|
-
- **`computed({ get, set })`** -- writable computed ref.
|
|
132
|
-
|
|
133
|
-
### Reactivity: Objects
|
|
134
|
-
|
|
135
|
-
- **`reactive(obj)`** -- deep reactive proxy (Pyreon store).
|
|
136
|
-
- **`shallowReactive(obj)`** -- shallow reactive proxy.
|
|
137
|
-
- **`readonly(obj)`** -- read-only proxy that throws on writes.
|
|
138
|
-
- **`toRaw(proxy)`** -- unwrap to the original object.
|
|
84
|
+
## Scope
|
|
139
85
|
|
|
140
|
-
|
|
86
|
+
This is a **runtime** shim. It covers what code imports at runtime — the same boundary `@pyreon/solid-compat` draws around Solid's compiler.
|
|
141
87
|
|
|
142
|
-
-
|
|
143
|
-
-
|
|
88
|
+
- ✅ Composition API — `ref`, `computed`, `reactive`, `watch`, lifecycle, `provide` / `inject`, `effectScope`
|
|
89
|
+
- ✅ `defineComponent` (typed) + `defineAsyncComponent`
|
|
90
|
+
- ✅ `createApp(component, props)` — mount via `.mount(selector)`
|
|
91
|
+
- ✅ Built-in components — `Teleport`, `KeepAlive`, `Transition`
|
|
92
|
+
- ❌ `.vue` Single-File Component compiler
|
|
93
|
+
- ❌ `<script setup>` syntax (compiler construct)
|
|
94
|
+
- ❌ Options API class-component lifecycle (`data`, `methods`, `computed` block, `created`, `beforeDestroy`, …)
|
|
95
|
+
- ❌ Template directives (`v-if`, `v-for`, `v-model`) — use JSX equivalents
|
|
144
96
|
|
|
145
|
-
|
|
97
|
+
Components are plain functions returning JSX that run on Pyreon's reactive engine.
|
|
146
98
|
|
|
147
|
-
|
|
148
|
-
- **`onUnmounted(fn)`** / **`onBeforeUnmount(fn)`**
|
|
149
|
-
- **`onUpdated(fn)`**
|
|
99
|
+
## Gotchas
|
|
150
100
|
|
|
151
|
-
|
|
101
|
+
- **`.value` reads/writes work** because Pyreon signals wrap a value getter/setter to match Vue's ref shape.
|
|
102
|
+
- **`reactive(obj)` returns a Proxy** that delegates to Pyreon signals — mutating `obj.x = 5` triggers subscribers as Vue does.
|
|
103
|
+
- **`watch` deps are tracked automatically** for function sources. For an array of sources, the watcher fires when any one changes.
|
|
104
|
+
- **`createApp(App).mount(selector)`** maps to Pyreon's `mount()` — returns an app instance with `.unmount()`.
|
|
105
|
+
- **Lifecycle hooks fire ONCE** per component instance (run-once model). Vue's render-driven re-fires (`onUpdated`) translate to per-subscription effects under the hood — the visible effect is the same for most use cases.
|
|
152
106
|
|
|
153
|
-
|
|
154
|
-
- **`inject(key, defaultValue?)`** -- inject a value from an ancestor.
|
|
107
|
+
## Documentation
|
|
155
108
|
|
|
156
|
-
|
|
109
|
+
Full docs: [docs.pyreon.dev/docs/vue-compat](https://docs.pyreon.dev/docs/vue-compat) (or `docs/docs/vue-compat.md` in this repo).
|
|
157
110
|
|
|
158
|
-
|
|
159
|
-
- **`defineComponent(setup)`** -- wrapper for type inference (returns setup as-is).
|
|
160
|
-
- **`nextTick()`** -- wait for the next microtask.
|
|
161
|
-
|
|
162
|
-
### Utilities
|
|
163
|
-
|
|
164
|
-
- **`h` / `Fragment`** -- JSX runtime.
|
|
165
|
-
- **`batch(fn)`** -- coalesce multiple signal writes.
|
|
166
|
-
|
|
167
|
-
## Composing Pyreon framework components inside vue-compat
|
|
168
|
-
|
|
169
|
-
Pyreon's framework components (`RouterView`, `PyreonUI`, `FormProvider`, `QueryClientProvider`, …) ship marked with `nativeCompat()` from `@pyreon/core` — vue-compat's JSX runtime detects the marker and routes them through Pyreon's setup frame instead of the compat wrapper. **You don't need to do anything** for the 24 components shipped marked.
|
|
170
|
-
|
|
171
|
-
If you write your **own** Pyreon-flavored helper that uses `provide()` / `onMount()` / `onUnmount()` / `effect()` at component-body scope and use it in a vue-compat app, mark it explicitly:
|
|
172
|
-
|
|
173
|
-
```tsx
|
|
174
|
-
import { nativeCompat, provide, createContext } from '@pyreon/core'
|
|
175
|
-
|
|
176
|
-
const MyCtx = createContext<string>('default')
|
|
177
|
-
|
|
178
|
-
function MyProvider(props: { value: string; children?: unknown }) {
|
|
179
|
-
provide(MyCtx, props.value)
|
|
180
|
-
return props.children as never
|
|
181
|
-
}
|
|
182
|
-
nativeCompat(MyProvider) // ← required for compat-mode apps
|
|
183
|
-
```
|
|
111
|
+
## License
|
|
184
112
|
|
|
185
|
-
|
|
113
|
+
MIT
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { Fragment, h, isNativeCompat, onUnmount } from "@pyreon/core";
|
|
2
|
+
import { runUntracked, 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
|
+
const _wrapperCache = /* @__PURE__ */ new WeakMap();
|
|
42
|
+
function wrapCompatComponent(vueComponent) {
|
|
43
|
+
let wrapped = _wrapperCache.get(vueComponent);
|
|
44
|
+
if (wrapped) return wrapped;
|
|
45
|
+
wrapped = ((props) => {
|
|
46
|
+
const ctx = {
|
|
47
|
+
hooks: [],
|
|
48
|
+
scheduleRerender: () => {},
|
|
49
|
+
pendingEffects: [],
|
|
50
|
+
pendingLayoutEffects: [],
|
|
51
|
+
unmounted: false,
|
|
52
|
+
unmountCallbacks: [],
|
|
53
|
+
_props: props
|
|
54
|
+
};
|
|
55
|
+
const version = signal(0);
|
|
56
|
+
let updateScheduled = false;
|
|
57
|
+
ctx.scheduleRerender = () => {
|
|
58
|
+
if (ctx.unmounted || updateScheduled) return;
|
|
59
|
+
updateScheduled = true;
|
|
60
|
+
queueMicrotask(() => {
|
|
61
|
+
updateScheduled = false;
|
|
62
|
+
if (!ctx.unmounted) version.set(version.peek() + 1);
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
onUnmount(() => {
|
|
66
|
+
ctx.unmounted = true;
|
|
67
|
+
for (const cb of ctx.unmountCallbacks) cb();
|
|
68
|
+
});
|
|
69
|
+
return () => {
|
|
70
|
+
version();
|
|
71
|
+
beginRender(ctx);
|
|
72
|
+
const result = runUntracked(() => vueComponent(props));
|
|
73
|
+
const layoutEffects = ctx.pendingLayoutEffects;
|
|
74
|
+
const effects = ctx.pendingEffects;
|
|
75
|
+
endRender();
|
|
76
|
+
runLayoutEffects(layoutEffects);
|
|
77
|
+
scheduleEffects(ctx, effects);
|
|
78
|
+
return result;
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
_wrapperCache.set(vueComponent, wrapped);
|
|
82
|
+
return wrapped;
|
|
83
|
+
}
|
|
84
|
+
function jsx(type, props, key) {
|
|
85
|
+
const { children, ...rest } = props;
|
|
86
|
+
const propsWithKey = key != null ? {
|
|
87
|
+
...rest,
|
|
88
|
+
key
|
|
89
|
+
} : rest;
|
|
90
|
+
if (typeof type === "function") {
|
|
91
|
+
const componentProps = children !== void 0 ? {
|
|
92
|
+
...propsWithKey,
|
|
93
|
+
children
|
|
94
|
+
} : propsWithKey;
|
|
95
|
+
if (isNativeCompat(type)) return h(type, componentProps);
|
|
96
|
+
return h(wrapCompatComponent(type), componentProps);
|
|
97
|
+
}
|
|
98
|
+
if (typeof type === "string" && propsWithKey.ref != null) {
|
|
99
|
+
const r = propsWithKey.ref;
|
|
100
|
+
if (typeof r === "object" && r !== null && r[Symbol.for("__v_isRef")] === true) {
|
|
101
|
+
const vueRef = r;
|
|
102
|
+
propsWithKey.ref = (el) => {
|
|
103
|
+
vueRef.value = el;
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return h(type, propsWithKey, ...children === void 0 ? [] : Array.isArray(children) ? children : [children]);
|
|
108
|
+
}
|
|
109
|
+
const jsxs = jsx;
|
|
110
|
+
|
|
111
|
+
//#endregion
|
|
112
|
+
export { jsxs as a, jsx as i, getCurrentCtx as n, getHookIndex as r, Fragment as t };
|
|
113
|
+
//# sourceMappingURL=jsx-runtime-CG6mH_9E.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","
|
|
5389
|
+
const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src/index.ts","uid":"3631272c-1"}]},{"name":"jsx-runtime.js","children":[{"name":"src/jsx-dev-runtime.ts","uid":"3631272c-3"}]},{"name":"_chunks/jsx-runtime-CG6mH_9E.js","children":[{"name":"src/jsx-runtime.ts","uid":"3631272c-5"}]}],"isRoot":true},"nodeParts":{"3631272c-1":{"renderedLength":34103,"gzipLength":8741,"brotliLength":0,"metaUid":"3631272c-0"},"3631272c-3":{"renderedLength":0,"gzipLength":0,"brotliLength":0,"metaUid":"3631272c-2"},"3631272c-5":{"renderedLength":2833,"gzipLength":1012,"brotliLength":0,"metaUid":"3631272c-4"}},"nodeMetas":{"3631272c-0":{"id":"/src/index.ts","moduleParts":{"index.js":"3631272c-1"},"imported":[{"uid":"3631272c-6"},{"uid":"3631272c-7"},{"uid":"3631272c-8"},{"uid":"3631272c-4"}],"importedBy":[],"isEntry":true},"3631272c-2":{"id":"/src/jsx-dev-runtime.ts","moduleParts":{"jsx-runtime.js":"3631272c-3"},"imported":[{"uid":"3631272c-4"}],"importedBy":[],"isEntry":true},"3631272c-4":{"id":"/src/jsx-runtime.ts","moduleParts":{"_chunks/jsx-runtime-CG6mH_9E.js":"3631272c-5"},"imported":[{"uid":"3631272c-6"},{"uid":"3631272c-7"}],"importedBy":[{"uid":"3631272c-0"},{"uid":"3631272c-2"}]},"3631272c-6":{"id":"@pyreon/core","moduleParts":{},"imported":[],"importedBy":[{"uid":"3631272c-0"},{"uid":"3631272c-4"}]},"3631272c-7":{"id":"@pyreon/reactivity","moduleParts":{},"imported":[],"importedBy":[{"uid":"3631272c-0"},{"uid":"3631272c-4"}]},"3631272c-8":{"id":"@pyreon/runtime-dom","moduleParts":{},"imported":[],"importedBy":[{"uid":"3631272c-0"}]}},"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/index.js
CHANGED
|
@@ -1,18 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as getCurrentCtx, r as getHookIndex } from "./_chunks/jsx-runtime-CG6mH_9E.js";
|
|
2
|
+
import { Fragment, Portal, Suspense as Suspense$1, createContext, h as pyreonH, onMount, onUnmount, onUpdate, pushContext, removeContextFrame, useContext } from "@pyreon/core";
|
|
2
3
|
import { batch, computed as computed$1, createStore, effect, nextTick as nextTick$1, signal } from "@pyreon/reactivity";
|
|
3
4
|
import { KeepAlive as KeepAlive$1, Transition as Transition$1, TransitionGroup as TransitionGroup$1, mount } from "@pyreon/runtime-dom";
|
|
4
5
|
|
|
5
|
-
//#region src/jsx-runtime.ts
|
|
6
|
-
let _currentCtx = null;
|
|
7
|
-
let _hookIndex = 0;
|
|
8
|
-
function getCurrentCtx() {
|
|
9
|
-
return _currentCtx;
|
|
10
|
-
}
|
|
11
|
-
function getHookIndex() {
|
|
12
|
-
return _hookIndex++;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
//#endregion
|
|
16
6
|
//#region src/index.ts
|
|
17
7
|
const V_IS_REF = Symbol.for("__v_isRef");
|
|
18
8
|
const V_IS_READONLY = Symbol("__v_isReadonly");
|
|
@@ -624,8 +614,9 @@ function provide(key, value) {
|
|
|
624
614
|
if (idx < ctx.hooks.length) return;
|
|
625
615
|
ctx.hooks[idx] = true;
|
|
626
616
|
const vueCtx = getOrCreateContext(key);
|
|
627
|
-
|
|
628
|
-
|
|
617
|
+
const frame = new Map([[vueCtx.id, value]]);
|
|
618
|
+
pushContext(frame);
|
|
619
|
+
ctx.unmountCallbacks.push(() => removeContextFrame(frame));
|
|
629
620
|
return;
|
|
630
621
|
}
|
|
631
622
|
const vueCtx = getOrCreateContext(key);
|
|
@@ -725,11 +716,21 @@ function createApp(component, props) {
|
|
|
725
716
|
mount(el) {
|
|
726
717
|
const container = typeof el === "string" ? document.querySelector(el) : el;
|
|
727
718
|
if (!container) throw new Error(`Cannot find mount target: ${el}`);
|
|
719
|
+
const pushedFrames = [];
|
|
728
720
|
for (const { key, value } of provisions) {
|
|
729
721
|
const ctx = getOrCreateContext(key);
|
|
730
|
-
|
|
722
|
+
const frame = new Map([[ctx.id, value]]);
|
|
723
|
+
pushContext(frame);
|
|
724
|
+
pushedFrames.push(frame);
|
|
731
725
|
}
|
|
732
|
-
|
|
726
|
+
const unmount = mount(pyreonH(component, props ?? null), container);
|
|
727
|
+
return () => {
|
|
728
|
+
unmount();
|
|
729
|
+
for (let i = pushedFrames.length - 1; i >= 0; i--) {
|
|
730
|
+
const frame = pushedFrames[i];
|
|
731
|
+
if (frame) removeContextFrame(frame);
|
|
732
|
+
}
|
|
733
|
+
};
|
|
733
734
|
},
|
|
734
735
|
use(plugin) {
|
|
735
736
|
plugin.install(app);
|
package/lib/jsx-runtime.js
CHANGED
|
@@ -1,107 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { runUntracked, signal } from "@pyreon/reactivity";
|
|
1
|
+
import { a as jsxs, i as jsx, t as Fragment } from "./_chunks/jsx-runtime-CG6mH_9E.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
|
-
const _wrapperCache = /* @__PURE__ */ new WeakMap();
|
|
36
|
-
function wrapCompatComponent(vueComponent) {
|
|
37
|
-
let wrapped = _wrapperCache.get(vueComponent);
|
|
38
|
-
if (wrapped) return wrapped;
|
|
39
|
-
wrapped = ((props) => {
|
|
40
|
-
const ctx = {
|
|
41
|
-
hooks: [],
|
|
42
|
-
scheduleRerender: () => {},
|
|
43
|
-
pendingEffects: [],
|
|
44
|
-
pendingLayoutEffects: [],
|
|
45
|
-
unmounted: false,
|
|
46
|
-
unmountCallbacks: [],
|
|
47
|
-
_props: props
|
|
48
|
-
};
|
|
49
|
-
const version = signal(0);
|
|
50
|
-
let updateScheduled = false;
|
|
51
|
-
ctx.scheduleRerender = () => {
|
|
52
|
-
if (ctx.unmounted || updateScheduled) return;
|
|
53
|
-
updateScheduled = true;
|
|
54
|
-
queueMicrotask(() => {
|
|
55
|
-
updateScheduled = false;
|
|
56
|
-
if (!ctx.unmounted) version.set(version.peek() + 1);
|
|
57
|
-
});
|
|
58
|
-
};
|
|
59
|
-
onUnmount(() => {
|
|
60
|
-
ctx.unmounted = true;
|
|
61
|
-
for (const cb of ctx.unmountCallbacks) cb();
|
|
62
|
-
});
|
|
63
|
-
return () => {
|
|
64
|
-
version();
|
|
65
|
-
beginRender(ctx);
|
|
66
|
-
const result = runUntracked(() => vueComponent(props));
|
|
67
|
-
const layoutEffects = ctx.pendingLayoutEffects;
|
|
68
|
-
const effects = ctx.pendingEffects;
|
|
69
|
-
endRender();
|
|
70
|
-
runLayoutEffects(layoutEffects);
|
|
71
|
-
scheduleEffects(ctx, effects);
|
|
72
|
-
return result;
|
|
73
|
-
};
|
|
74
|
-
});
|
|
75
|
-
_wrapperCache.set(vueComponent, wrapped);
|
|
76
|
-
return wrapped;
|
|
77
|
-
}
|
|
78
|
-
function jsx(type, props, key) {
|
|
79
|
-
const { children, ...rest } = props;
|
|
80
|
-
const propsWithKey = key != null ? {
|
|
81
|
-
...rest,
|
|
82
|
-
key
|
|
83
|
-
} : rest;
|
|
84
|
-
if (typeof type === "function") {
|
|
85
|
-
const componentProps = children !== void 0 ? {
|
|
86
|
-
...propsWithKey,
|
|
87
|
-
children
|
|
88
|
-
} : propsWithKey;
|
|
89
|
-
if (isNativeCompat(type)) return h(type, componentProps);
|
|
90
|
-
return h(wrapCompatComponent(type), componentProps);
|
|
91
|
-
}
|
|
92
|
-
if (typeof type === "string" && propsWithKey.ref != null) {
|
|
93
|
-
const r = propsWithKey.ref;
|
|
94
|
-
if (typeof r === "object" && r !== null && r[Symbol.for("__v_isRef")] === true) {
|
|
95
|
-
const vueRef = r;
|
|
96
|
-
propsWithKey.ref = (el) => {
|
|
97
|
-
vueRef.value = el;
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
return h(type, propsWithKey, ...children === void 0 ? [] : Array.isArray(children) ? children : [children]);
|
|
102
|
-
}
|
|
103
|
-
const jsxs = jsx;
|
|
104
|
-
|
|
105
|
-
//#endregion
|
|
106
|
-
export { Fragment, jsx, jsxs };
|
|
107
|
-
//# sourceMappingURL=jsx-runtime.js.map
|
|
3
|
+
export { Fragment, jsx, jsxs };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pyreon/vue-compat",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"description": "Vue 3-compatible Composition API shim for Pyreon — write Vue-style code that runs on Pyreon's reactive engine",
|
|
5
5
|
"homepage": "https://github.com/pyreon/pyreon/tree/main/packages/vue-compat#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -54,13 +54,13 @@
|
|
|
54
54
|
"prepublishOnly": "bun run build"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@pyreon/core": "^0.
|
|
58
|
-
"@pyreon/reactivity": "^0.
|
|
59
|
-
"@pyreon/runtime-dom": "^0.
|
|
57
|
+
"@pyreon/core": "^0.23.0",
|
|
58
|
+
"@pyreon/reactivity": "^0.23.0",
|
|
59
|
+
"@pyreon/runtime-dom": "^0.23.0"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
62
|
"@happy-dom/global-registrator": "^20.8.9",
|
|
63
|
-
"@pyreon/test-utils": "^0.13.
|
|
63
|
+
"@pyreon/test-utils": "^0.13.10",
|
|
64
64
|
"@vitest/browser-playwright": "^4.1.4",
|
|
65
65
|
"happy-dom": "^20.8.3"
|
|
66
66
|
}
|
package/src/index.ts
CHANGED
|
@@ -28,11 +28,11 @@ import {
|
|
|
28
28
|
onMount,
|
|
29
29
|
onUnmount,
|
|
30
30
|
onUpdate,
|
|
31
|
-
popContext,
|
|
32
31
|
Portal,
|
|
33
32
|
pushContext,
|
|
34
33
|
h as pyreonH,
|
|
35
34
|
Suspense as PyreonSuspense,
|
|
35
|
+
removeContextFrame,
|
|
36
36
|
useContext,
|
|
37
37
|
} from '@pyreon/core'
|
|
38
38
|
import {
|
|
@@ -885,8 +885,16 @@ export function provide<T>(key: string | symbol, value: T): void {
|
|
|
885
885
|
if (idx < ctx.hooks.length) return // Already provided
|
|
886
886
|
ctx.hooks[idx] = true
|
|
887
887
|
const vueCtx = getOrCreateContext<T>(key)
|
|
888
|
-
|
|
889
|
-
|
|
888
|
+
// Identity-based push/pop pair — capture the frame reference at push
|
|
889
|
+
// time, remove it by identity (not position) on unmount. Position-based
|
|
890
|
+
// `popContext()` here would pop the WRONG frame whenever sibling
|
|
891
|
+
// components unmount out-of-order (renderer-driven `<For>` removal,
|
|
892
|
+
// `<Show>` flipping a non-last sibling, route nav unmounting an outer
|
|
893
|
+
// of nested provider chains). Same root cause + same fix shape as
|
|
894
|
+
// `@pyreon/core` #725's `provide()` and #729's `_errorBoundaryStack`.
|
|
895
|
+
const frame = new Map([[vueCtx.id, value]])
|
|
896
|
+
pushContext(frame)
|
|
897
|
+
ctx.unmountCallbacks.push(() => removeContextFrame(frame))
|
|
890
898
|
return
|
|
891
899
|
}
|
|
892
900
|
// Outside component — use Pyreon's provide directly
|
|
@@ -1070,13 +1078,32 @@ export function createApp(component: ComponentFn, props?: Props): App {
|
|
|
1070
1078
|
if (!container) {
|
|
1071
1079
|
throw new Error(`Cannot find mount target: ${el}`)
|
|
1072
1080
|
}
|
|
1073
|
-
// Push app-level provisions before mounting
|
|
1081
|
+
// Push app-level provisions before mounting AND track each pushed
|
|
1082
|
+
// frame so the returned unmount callback can remove them by IDENTITY.
|
|
1083
|
+
// Pre-fix the pushes were unmatched — every `createApp(...).provide(k,v).mount(el)`
|
|
1084
|
+
// call leaked one Map reference per provision onto the global context
|
|
1085
|
+
// stack permanently. Mount/unmount cycles compound this.
|
|
1086
|
+
const pushedFrames: Map<symbol, unknown>[] = []
|
|
1074
1087
|
for (const { key, value } of provisions) {
|
|
1075
1088
|
const ctx = getOrCreateContext(key)
|
|
1076
|
-
|
|
1089
|
+
const frame = new Map([[ctx.id, value]])
|
|
1090
|
+
pushContext(frame)
|
|
1091
|
+
pushedFrames.push(frame)
|
|
1077
1092
|
}
|
|
1078
1093
|
const vnode = pyreonH(component, props ?? null)
|
|
1079
|
-
|
|
1094
|
+
const unmount = pyreonMount(vnode, container)
|
|
1095
|
+
return () => {
|
|
1096
|
+
unmount()
|
|
1097
|
+
// Remove app-level provisions by identity (reverse order to match
|
|
1098
|
+
// LIFO push order if the same frame ref appears multiple times,
|
|
1099
|
+
// though that's structurally impossible here — each frame is a
|
|
1100
|
+
// fresh Map). Identity-based so other mounts pushing in between
|
|
1101
|
+
// can't accidentally remove our frames or have theirs removed.
|
|
1102
|
+
for (let i = pushedFrames.length - 1; i >= 0; i--) {
|
|
1103
|
+
const frame = pushedFrames[i]
|
|
1104
|
+
if (frame) removeContextFrame(frame)
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1080
1107
|
},
|
|
1081
1108
|
use(plugin: { install: (app: App) => void }): App {
|
|
1082
1109
|
plugin.install(app)
|