@pyreon/core 0.16.0 → 0.19.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 +1 -0
- package/lib/analysis/index.js.html +1 -1
- package/lib/analysis/jsx-dev-runtime.js.html +1 -1
- package/lib/analysis/jsx-runtime.js.html +1 -1
- package/lib/index.js +278 -16
- package/lib/jsx-dev-runtime.js +29 -9
- package/lib/jsx-runtime.js +29 -9
- package/lib/types/index.d.ts +171 -18
- package/package.json +2 -2
- package/src/compat-shared.ts +80 -0
- package/src/defer.ts +279 -0
- package/src/index.ts +13 -2
- package/src/jsx-runtime.ts +46 -8
- package/src/lifecycle.ts +4 -2
- package/src/props.ts +59 -0
- package/src/telemetry.ts +37 -0
- package/src/tests/compat-shared.test.ts +99 -0
- package/src/tests/defer.test.ts +359 -0
- package/src/tests/reactive-props.test.ts +71 -1
- package/src/tests/telemetry.test.ts +94 -0
package/README.md
CHANGED
|
@@ -50,6 +50,7 @@ Lifecycle hook arrays are lazy-allocated -- `LifecycleHooks.mount`/`.unmount`/`.
|
|
|
50
50
|
|
|
51
51
|
- **`makeReactiveProps(raw)`** -- Converts compiler-emitted `_rp()` wrappers into getter properties. Uses a scan-first strategy: checks for any branded reactive prop before allocating the result object. Static-only components return `raw` immediately with no allocation.
|
|
52
52
|
- **`_rp(fn)`** -- Brands a function as a reactive prop wrapper (compiler-emitted, not user-facing).
|
|
53
|
+
- **`_wrapSpread(source)`** -- Compiler-emitted helper that makes JSX spread on a component reactivity-safe. For `<Comp {...source}>`, the compiler emits `<Comp {..._wrapSpread(source)}>`. `_wrapSpread` walks `source`'s own keys without firing getters and re-brands each getter-shaped value as an `_rp` thunk pointing back at the live source. JS spread then carries the brands as plain data properties; `makeReactiveProps` converts them back to getters on the consumer side -- so reactive props survive the spread end-to-end. Fast path: when `source` has no getter descriptors, returns the source unchanged (zero cost). Not user-facing; emitted automatically by `@pyreon/compiler` for any component JSX with a spread. See `docs/patterns/reactive-spread.md` for the full contract.
|
|
53
54
|
|
|
54
55
|
### Context
|
|
55
56
|
|
|
@@ -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","children":[{"uid":"
|
|
5389
|
+
const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src","children":[{"uid":"d1de8643-1","name":"lifecycle.ts"},{"uid":"d1de8643-3","name":"component.ts"},{"uid":"d1de8643-5","name":"compat-marker.ts"},{"uid":"d1de8643-7","name":"compat-shared.ts"},{"uid":"d1de8643-9","name":"context.ts"},{"uid":"d1de8643-11","name":"h.ts"},{"uid":"d1de8643-13","name":"dynamic.ts"},{"uid":"d1de8643-15","name":"telemetry.ts"},{"uid":"d1de8643-17","name":"error-boundary.ts"},{"uid":"d1de8643-19","name":"for.ts"},{"uid":"d1de8643-21","name":"ref.ts"},{"uid":"d1de8643-23","name":"defer.ts"},{"uid":"d1de8643-25","name":"lazy.ts"},{"uid":"d1de8643-27","name":"map-array.ts"},{"uid":"d1de8643-29","name":"portal.ts"},{"uid":"d1de8643-31","name":"props.ts"},{"uid":"d1de8643-33","name":"show.ts"},{"uid":"d1de8643-35","name":"style.ts"},{"uid":"d1de8643-37","name":"suspense.ts"},{"uid":"d1de8643-39","name":"index.ts"}]}]}],"isRoot":true},"nodeParts":{"d1de8643-1":{"renderedLength":3090,"gzipLength":1316,"brotliLength":0,"metaUid":"d1de8643-0"},"d1de8643-3":{"renderedLength":1471,"gzipLength":693,"brotliLength":0,"metaUid":"d1de8643-2"},"d1de8643-5":{"renderedLength":3173,"gzipLength":1409,"brotliLength":0,"metaUid":"d1de8643-4"},"d1de8643-7":{"renderedLength":2346,"gzipLength":1033,"brotliLength":0,"metaUid":"d1de8643-6"},"d1de8643-9":{"renderedLength":3600,"gzipLength":1542,"brotliLength":0,"metaUid":"d1de8643-8"},"d1de8643-11":{"renderedLength":1813,"gzipLength":957,"brotliLength":0,"metaUid":"d1de8643-10"},"d1de8643-13":{"renderedLength":490,"gzipLength":292,"brotliLength":0,"metaUid":"d1de8643-12"},"d1de8643-15":{"renderedLength":1990,"gzipLength":950,"brotliLength":0,"metaUid":"d1de8643-14"},"d1de8643-17":{"renderedLength":1659,"gzipLength":843,"brotliLength":0,"metaUid":"d1de8643-16"},"d1de8643-19":{"renderedLength":700,"gzipLength":478,"brotliLength":0,"metaUid":"d1de8643-18"},"d1de8643-21":{"renderedLength":86,"gzipLength":98,"brotliLength":0,"metaUid":"d1de8643-20"},"d1de8643-23":{"renderedLength":4387,"gzipLength":1891,"brotliLength":0,"metaUid":"d1de8643-22"},"d1de8643-25":{"renderedLength":461,"gzipLength":273,"brotliLength":0,"metaUid":"d1de8643-24"},"d1de8643-27":{"renderedLength":1018,"gzipLength":571,"brotliLength":0,"metaUid":"d1de8643-26"},"d1de8643-29":{"renderedLength":818,"gzipLength":491,"brotliLength":0,"metaUid":"d1de8643-28"},"d1de8643-31":{"renderedLength":6310,"gzipLength":2344,"brotliLength":0,"metaUid":"d1de8643-30"},"d1de8643-33":{"renderedLength":2022,"gzipLength":854,"brotliLength":0,"metaUid":"d1de8643-32"},"d1de8643-35":{"renderedLength":1858,"gzipLength":825,"brotliLength":0,"metaUid":"d1de8643-34"},"d1de8643-37":{"renderedLength":1104,"gzipLength":614,"brotliLength":0,"metaUid":"d1de8643-36"},"d1de8643-39":{"renderedLength":0,"gzipLength":0,"brotliLength":0,"metaUid":"d1de8643-38"}},"nodeMetas":{"d1de8643-0":{"id":"/src/lifecycle.ts","moduleParts":{"index.js":"d1de8643-1"},"imported":[],"importedBy":[{"uid":"d1de8643-38"},{"uid":"d1de8643-2"},{"uid":"d1de8643-8"},{"uid":"d1de8643-16"},{"uid":"d1de8643-22"}]},"d1de8643-2":{"id":"/src/component.ts","moduleParts":{"index.js":"d1de8643-3"},"imported":[{"uid":"d1de8643-0"}],"importedBy":[{"uid":"d1de8643-38"},{"uid":"d1de8643-16"}]},"d1de8643-4":{"id":"/src/compat-marker.ts","moduleParts":{"index.js":"d1de8643-5"},"imported":[],"importedBy":[{"uid":"d1de8643-38"},{"uid":"d1de8643-16"}]},"d1de8643-6":{"id":"/src/compat-shared.ts","moduleParts":{"index.js":"d1de8643-7"},"imported":[],"importedBy":[{"uid":"d1de8643-38"}]},"d1de8643-8":{"id":"/src/context.ts","moduleParts":{"index.js":"d1de8643-9"},"imported":[{"uid":"d1de8643-40"},{"uid":"d1de8643-0"}],"importedBy":[{"uid":"d1de8643-38"}]},"d1de8643-10":{"id":"/src/h.ts","moduleParts":{"index.js":"d1de8643-11"},"imported":[],"importedBy":[{"uid":"d1de8643-38"},{"uid":"d1de8643-12"},{"uid":"d1de8643-22"},{"uid":"d1de8643-24"},{"uid":"d1de8643-36"}]},"d1de8643-12":{"id":"/src/dynamic.ts","moduleParts":{"index.js":"d1de8643-13"},"imported":[{"uid":"d1de8643-10"}],"importedBy":[{"uid":"d1de8643-38"}]},"d1de8643-14":{"id":"/src/telemetry.ts","moduleParts":{"index.js":"d1de8643-15"},"imported":[{"uid":"d1de8643-40"}],"importedBy":[{"uid":"d1de8643-38"},{"uid":"d1de8643-16"}]},"d1de8643-16":{"id":"/src/error-boundary.ts","moduleParts":{"index.js":"d1de8643-17"},"imported":[{"uid":"d1de8643-40"},{"uid":"d1de8643-4"},{"uid":"d1de8643-2"},{"uid":"d1de8643-0"},{"uid":"d1de8643-14"}],"importedBy":[{"uid":"d1de8643-38"}]},"d1de8643-18":{"id":"/src/for.ts","moduleParts":{"index.js":"d1de8643-19"},"imported":[],"importedBy":[{"uid":"d1de8643-38"}]},"d1de8643-20":{"id":"/src/ref.ts","moduleParts":{"index.js":"d1de8643-21"},"imported":[],"importedBy":[{"uid":"d1de8643-38"},{"uid":"d1de8643-22"}]},"d1de8643-22":{"id":"/src/defer.ts","moduleParts":{"index.js":"d1de8643-23"},"imported":[{"uid":"d1de8643-40"},{"uid":"d1de8643-10"},{"uid":"d1de8643-0"},{"uid":"d1de8643-20"}],"importedBy":[{"uid":"d1de8643-38"}]},"d1de8643-24":{"id":"/src/lazy.ts","moduleParts":{"index.js":"d1de8643-25"},"imported":[{"uid":"d1de8643-40"},{"uid":"d1de8643-10"}],"importedBy":[{"uid":"d1de8643-38"}]},"d1de8643-26":{"id":"/src/map-array.ts","moduleParts":{"index.js":"d1de8643-27"},"imported":[],"importedBy":[{"uid":"d1de8643-38"}]},"d1de8643-28":{"id":"/src/portal.ts","moduleParts":{"index.js":"d1de8643-29"},"imported":[],"importedBy":[{"uid":"d1de8643-38"}]},"d1de8643-30":{"id":"/src/props.ts","moduleParts":{"index.js":"d1de8643-31"},"imported":[],"importedBy":[{"uid":"d1de8643-38"}]},"d1de8643-32":{"id":"/src/show.ts","moduleParts":{"index.js":"d1de8643-33"},"imported":[],"importedBy":[{"uid":"d1de8643-38"}]},"d1de8643-34":{"id":"/src/style.ts","moduleParts":{"index.js":"d1de8643-35"},"imported":[],"importedBy":[{"uid":"d1de8643-38"}]},"d1de8643-36":{"id":"/src/suspense.ts","moduleParts":{"index.js":"d1de8643-37"},"imported":[{"uid":"d1de8643-10"}],"importedBy":[{"uid":"d1de8643-38"}]},"d1de8643-38":{"id":"/src/index.ts","moduleParts":{"index.js":"d1de8643-39"},"imported":[{"uid":"d1de8643-2"},{"uid":"d1de8643-4"},{"uid":"d1de8643-6"},{"uid":"d1de8643-8"},{"uid":"d1de8643-12"},{"uid":"d1de8643-16"},{"uid":"d1de8643-18"},{"uid":"d1de8643-10"},{"uid":"d1de8643-22"},{"uid":"d1de8643-24"},{"uid":"d1de8643-0"},{"uid":"d1de8643-26"},{"uid":"d1de8643-28"},{"uid":"d1de8643-30"},{"uid":"d1de8643-20"},{"uid":"d1de8643-32"},{"uid":"d1de8643-34"},{"uid":"d1de8643-36"},{"uid":"d1de8643-14"}],"importedBy":[],"isEntry":true},"d1de8643-40":{"id":"@pyreon/reactivity","moduleParts":{},"imported":[],"importedBy":[{"uid":"d1de8643-8"},{"uid":"d1de8643-16"},{"uid":"d1de8643-22"},{"uid":"d1de8643-24"},{"uid":"d1de8643-14"}]}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
|
|
5390
5390
|
|
|
5391
5391
|
const run = () => {
|
|
5392
5392
|
const width = window.innerWidth;
|
|
@@ -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":"jsx-dev-runtime.js","children":[{"name":"src","children":[{"uid":"
|
|
5389
|
+
const data = {"version":2,"tree":{"name":"root","children":[{"name":"jsx-dev-runtime.js","children":[{"name":"src","children":[{"uid":"430b4b1b-1","name":"h.ts"},{"uid":"430b4b1b-3","name":"jsx-runtime.ts"},{"uid":"430b4b1b-5","name":"jsx-dev-runtime.ts"}]}]}],"isRoot":true},"nodeParts":{"430b4b1b-1":{"renderedLength":1813,"gzipLength":957,"brotliLength":0,"metaUid":"430b4b1b-0"},"430b4b1b-3":{"renderedLength":1789,"gzipLength":834,"brotliLength":0,"metaUid":"430b4b1b-2"},"430b4b1b-5":{"renderedLength":0,"gzipLength":0,"brotliLength":0,"metaUid":"430b4b1b-4"}},"nodeMetas":{"430b4b1b-0":{"id":"/src/h.ts","moduleParts":{"jsx-dev-runtime.js":"430b4b1b-1"},"imported":[],"importedBy":[{"uid":"430b4b1b-2"}]},"430b4b1b-2":{"id":"/src/jsx-runtime.ts","moduleParts":{"jsx-dev-runtime.js":"430b4b1b-3"},"imported":[{"uid":"430b4b1b-0"}],"importedBy":[{"uid":"430b4b1b-4"}]},"430b4b1b-4":{"id":"/src/jsx-dev-runtime.ts","moduleParts":{"jsx-dev-runtime.js":"430b4b1b-5"},"imported":[{"uid":"430b4b1b-2"}],"importedBy":[],"isEntry":true}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
|
|
5390
5390
|
|
|
5391
5391
|
const run = () => {
|
|
5392
5392
|
const width = window.innerWidth;
|
|
@@ -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":"jsx-runtime.js","children":[{"name":"src","children":[{"uid":"
|
|
5389
|
+
const data = {"version":2,"tree":{"name":"root","children":[{"name":"jsx-runtime.js","children":[{"name":"src","children":[{"uid":"36dca003-1","name":"h.ts"},{"uid":"36dca003-3","name":"jsx-runtime.ts"}]}]}],"isRoot":true},"nodeParts":{"36dca003-1":{"renderedLength":1813,"gzipLength":957,"brotliLength":0,"metaUid":"36dca003-0"},"36dca003-3":{"renderedLength":1789,"gzipLength":834,"brotliLength":0,"metaUid":"36dca003-2"}},"nodeMetas":{"36dca003-0":{"id":"/src/h.ts","moduleParts":{"jsx-runtime.js":"36dca003-1"},"imported":[],"importedBy":[{"uid":"36dca003-2"}]},"36dca003-2":{"id":"/src/jsx-runtime.ts","moduleParts":{"jsx-runtime.js":"36dca003-3"},"imported":[{"uid":"36dca003-0"}],"importedBy":[],"isEntry":true}},"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,7 +1,7 @@
|
|
|
1
|
-
import { setSnapshotCapture, signal } from "@pyreon/reactivity";
|
|
1
|
+
import { effect, getReactiveTrace, setSnapshotCapture, signal } from "@pyreon/reactivity";
|
|
2
2
|
|
|
3
3
|
//#region src/lifecycle.ts
|
|
4
|
-
const __DEV__$
|
|
4
|
+
const __DEV__$5 = process.env.NODE_ENV !== "production";
|
|
5
5
|
let _current = null;
|
|
6
6
|
function setCurrentHooks(hooks) {
|
|
7
7
|
_current = hooks;
|
|
@@ -40,10 +40,10 @@ function captureCallSite() {
|
|
|
40
40
|
return "";
|
|
41
41
|
}
|
|
42
42
|
function warnOutsideSetup(hookName) {
|
|
43
|
-
if (__DEV__$
|
|
43
|
+
if (__DEV__$5 && !_current) {
|
|
44
44
|
const callSite = captureCallSite();
|
|
45
|
-
const
|
|
46
|
-
console.warn(`[Pyreon] ${hookName}() called outside component setup. Lifecycle hooks must be called synchronously during a component's setup function.` +
|
|
45
|
+
const callSiteSuffix = callSite ? `\n Called from: ${callSite}` : "";
|
|
46
|
+
console.warn(`[Pyreon] ${hookName}() called outside component setup. Lifecycle hooks must be called synchronously during a component's setup function.` + callSiteSuffix + (hookName === "onUnmount" ? "\n Hint: `provide()` internally calls onUnmount(). If you use provide(), ensure it runs during synchronous component setup — not inside effects, callbacks, or after awaits." : ""));
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
/**
|
|
@@ -231,6 +231,71 @@ function isNativeCompat(fn) {
|
|
|
231
231
|
return typeof fn === "function" && fn[NATIVE_COMPAT_MARKER] === true;
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
+
//#endregion
|
|
235
|
+
//#region src/compat-shared.ts
|
|
236
|
+
/**
|
|
237
|
+
* Code shared by the framework-compat JSX runtimes
|
|
238
|
+
* (`@pyreon/react-compat`, `@pyreon/preact-compat`).
|
|
239
|
+
*
|
|
240
|
+
* These helpers were previously copy-pasted byte-for-byte into both
|
|
241
|
+
* packages. `@pyreon/core` is the correct single home — it's already a
|
|
242
|
+
* dependency of every compat package and already hosts the sibling
|
|
243
|
+
* cross-compat module `compat-marker.ts` (`nativeCompat` / `isNativeCompat`).
|
|
244
|
+
*/
|
|
245
|
+
/**
|
|
246
|
+
* Shallow props comparison used by compat `memo()` / `useState` bailout.
|
|
247
|
+
* Same-length key sets with `Object.is`-equal values → equal.
|
|
248
|
+
*/
|
|
249
|
+
function shallowEqualProps(a, b) {
|
|
250
|
+
const keysA = Object.keys(a);
|
|
251
|
+
const keysB = Object.keys(b);
|
|
252
|
+
if (keysA.length !== keysB.length) return false;
|
|
253
|
+
for (const k of keysA) if (!Object.is(a[k], b[k])) return false;
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Map React/Preact-style DOM attributes to standard HTML attributes,
|
|
258
|
+
* mutating `props` in place. No-op when `type` is not a host string
|
|
259
|
+
* (component vnodes keep their props untouched).
|
|
260
|
+
*
|
|
261
|
+
* The React and Preact variants were identical apart from React also
|
|
262
|
+
* stripping `suppressContentEditableWarning`. Both keys are React/Preact
|
|
263
|
+
* authoring-only and never valid DOM attributes, so always stripping
|
|
264
|
+
* both is behavior-preserving for Preact (the key is never set there;
|
|
265
|
+
* `delete` of an absent key is a no-op) and removes the only divergence.
|
|
266
|
+
*/
|
|
267
|
+
function mapCompatDomProps(props, type) {
|
|
268
|
+
if (typeof type !== "string") return;
|
|
269
|
+
if (props.className !== void 0) {
|
|
270
|
+
props.class = props.className;
|
|
271
|
+
delete props.className;
|
|
272
|
+
}
|
|
273
|
+
if (props.htmlFor !== void 0) {
|
|
274
|
+
props.for = props.htmlFor;
|
|
275
|
+
delete props.htmlFor;
|
|
276
|
+
}
|
|
277
|
+
if ((type === "input" || type === "textarea" || type === "select") && props.onChange !== void 0) {
|
|
278
|
+
if (props.onInput === void 0) props.onInput = props.onChange;
|
|
279
|
+
delete props.onChange;
|
|
280
|
+
}
|
|
281
|
+
if (props.autoFocus !== void 0) {
|
|
282
|
+
props.autofocus = props.autoFocus;
|
|
283
|
+
delete props.autoFocus;
|
|
284
|
+
}
|
|
285
|
+
if (type === "input" || type === "textarea") {
|
|
286
|
+
if (props.defaultValue !== void 0 && props.value === void 0) {
|
|
287
|
+
props.value = props.defaultValue;
|
|
288
|
+
delete props.defaultValue;
|
|
289
|
+
}
|
|
290
|
+
if (props.defaultChecked !== void 0 && props.checked === void 0) {
|
|
291
|
+
props.checked = props.defaultChecked;
|
|
292
|
+
delete props.defaultChecked;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
delete props.suppressHydrationWarning;
|
|
296
|
+
delete props.suppressContentEditableWarning;
|
|
297
|
+
}
|
|
298
|
+
|
|
234
299
|
//#endregion
|
|
235
300
|
//#region src/context.ts
|
|
236
301
|
/**
|
|
@@ -396,10 +461,10 @@ function flattenChildren(children) {
|
|
|
396
461
|
|
|
397
462
|
//#endregion
|
|
398
463
|
//#region src/dynamic.ts
|
|
399
|
-
const __DEV__$
|
|
464
|
+
const __DEV__$4 = process.env.NODE_ENV !== "production";
|
|
400
465
|
function Dynamic(props) {
|
|
401
466
|
const { component, children, ...rest } = props;
|
|
402
|
-
if (__DEV__$
|
|
467
|
+
if (__DEV__$4 && !component) console.warn("[Pyreon] <Dynamic> received a falsy `component` prop. Nothing will be rendered.");
|
|
403
468
|
if (!component) return null;
|
|
404
469
|
if (children === void 0) return h(component, rest);
|
|
405
470
|
if (Array.isArray(children)) return h(component, rest, ...children);
|
|
@@ -408,6 +473,24 @@ function Dynamic(props) {
|
|
|
408
473
|
|
|
409
474
|
//#endregion
|
|
410
475
|
//#region src/telemetry.ts
|
|
476
|
+
/**
|
|
477
|
+
* Error telemetry — hook into Pyreon's error reporting for Sentry, Datadog, etc.
|
|
478
|
+
*
|
|
479
|
+
* Captures errors from ALL lifecycle phases including reactive effects.
|
|
480
|
+
* `effect()` errors thrown by `@pyreon/reactivity` are bridged through a
|
|
481
|
+
* globalThis sink (no upward import — reactivity doesn't depend on core).
|
|
482
|
+
*
|
|
483
|
+
* @example
|
|
484
|
+
* import { registerErrorHandler } from "@pyreon/core"
|
|
485
|
+
* import * as Sentry from "@sentry/browser"
|
|
486
|
+
*
|
|
487
|
+
* registerErrorHandler(ctx => {
|
|
488
|
+
* Sentry.captureException(ctx.error, {
|
|
489
|
+
* extra: { component: ctx.component, phase: ctx.phase },
|
|
490
|
+
* })
|
|
491
|
+
* })
|
|
492
|
+
*/
|
|
493
|
+
const __DEV__$3 = process.env.NODE_ENV !== "production";
|
|
411
494
|
let _handlers = [];
|
|
412
495
|
/**
|
|
413
496
|
* Register a global error handler. Called whenever a component throws in any
|
|
@@ -431,6 +514,10 @@ function registerErrorHandler(handler) {
|
|
|
431
514
|
* Existing console.error calls are preserved; this is additive.
|
|
432
515
|
*/
|
|
433
516
|
function reportError(ctx) {
|
|
517
|
+
if (__DEV__$3 && ctx.reactiveTrace === void 0) try {
|
|
518
|
+
const trace = getReactiveTrace();
|
|
519
|
+
if (trace.length > 0) ctx.reactiveTrace = trace;
|
|
520
|
+
} catch {}
|
|
434
521
|
for (const h of _handlers) try {
|
|
435
522
|
h(ctx);
|
|
436
523
|
} catch {}
|
|
@@ -450,7 +537,7 @@ function _installReactivityBridge() {
|
|
|
450
537
|
|
|
451
538
|
//#endregion
|
|
452
539
|
//#region src/error-boundary.ts
|
|
453
|
-
const __DEV__$
|
|
540
|
+
const __DEV__$2 = process.env.NODE_ENV !== "production";
|
|
454
541
|
/**
|
|
455
542
|
* ErrorBoundary — catches errors thrown by child components and renders a
|
|
456
543
|
* fallback UI instead of crashing the whole tree.
|
|
@@ -475,7 +562,7 @@ const __DEV__$1 = process.env.NODE_ENV !== "production";
|
|
|
475
562
|
* </ErrorBoundary>
|
|
476
563
|
*/
|
|
477
564
|
function ErrorBoundary(props) {
|
|
478
|
-
if (__DEV__$
|
|
565
|
+
if (__DEV__$2 && typeof props.fallback !== "function") console.warn(`[Pyreon] <ErrorBoundary> expects \`fallback\` to be a function: (err, reset) => VNode. Received ${typeof props.fallback}.`);
|
|
479
566
|
const error = signal(null);
|
|
480
567
|
const reset = () => error.set(null);
|
|
481
568
|
const handler = (err) => {
|
|
@@ -527,6 +614,139 @@ function For(props) {
|
|
|
527
614
|
};
|
|
528
615
|
}
|
|
529
616
|
|
|
617
|
+
//#endregion
|
|
618
|
+
//#region src/ref.ts
|
|
619
|
+
function createRef() {
|
|
620
|
+
return { current: null };
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
//#endregion
|
|
624
|
+
//#region src/defer.ts
|
|
625
|
+
const __DEV__$1 = process.env.NODE_ENV !== "production";
|
|
626
|
+
/**
|
|
627
|
+
* Set up the `on="idle"` trigger. Returns a teardown function the
|
|
628
|
+
* caller must invoke on unmount. Browser-API access is gated by
|
|
629
|
+
* `typeof` checks so SSR / jsdom environments fall back to a
|
|
630
|
+
* `setTimeout(1)` shim. Extracted as a standalone helper so it's
|
|
631
|
+
* directly testable without going through `onMount` (core tests
|
|
632
|
+
* don't run in happy-dom; runtime-dom is where the lifecycle hooks
|
|
633
|
+
* live).
|
|
634
|
+
*
|
|
635
|
+
* @internal Exported for tests; not part of the stable public API.
|
|
636
|
+
*/
|
|
637
|
+
function _setupIdleTrigger(startLoad) {
|
|
638
|
+
const ric = globalThis.requestIdleCallback;
|
|
639
|
+
const cic = globalThis.cancelIdleCallback;
|
|
640
|
+
if (typeof ric === "function") {
|
|
641
|
+
const id = ric(startLoad);
|
|
642
|
+
return () => cic?.(id);
|
|
643
|
+
}
|
|
644
|
+
const t = setTimeout(startLoad, 1);
|
|
645
|
+
return () => clearTimeout(t);
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Set up the `on="visible"` trigger. Observes `el` via an
|
|
649
|
+
* `IntersectionObserver` and fires `startLoad` once on the first
|
|
650
|
+
* intersection. If `IntersectionObserver` is unavailable (jsdom)
|
|
651
|
+
* or `el` is null (SSR), falls back to loading immediately.
|
|
652
|
+
*
|
|
653
|
+
* Returns a teardown function — call to disconnect the observer.
|
|
654
|
+
*
|
|
655
|
+
* @internal Exported for tests; not part of the stable public API.
|
|
656
|
+
*/
|
|
657
|
+
function _setupVisibleTrigger(el, startLoad, rootMargin) {
|
|
658
|
+
if (!el || typeof IntersectionObserver === "undefined") {
|
|
659
|
+
startLoad();
|
|
660
|
+
return () => {};
|
|
661
|
+
}
|
|
662
|
+
const obs = new IntersectionObserver((entries) => {
|
|
663
|
+
if (entries.some((e) => e.isIntersecting)) {
|
|
664
|
+
startLoad();
|
|
665
|
+
obs.disconnect();
|
|
666
|
+
}
|
|
667
|
+
}, { rootMargin });
|
|
668
|
+
obs.observe(el);
|
|
669
|
+
return () => obs.disconnect();
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* Lazy-load a chunk when a trigger condition is met.
|
|
673
|
+
*
|
|
674
|
+
* Three trigger modes:
|
|
675
|
+
* - `when={() => signal()}` — load when condition flips truthy (modal pattern)
|
|
676
|
+
* - `on="visible"` — load when the wrapper scrolls into view
|
|
677
|
+
* - `on="idle"` — load during browser idle time
|
|
678
|
+
*
|
|
679
|
+
* The chunk fetch is fired exactly once per `Defer` instance — repeated
|
|
680
|
+
* trigger firings after the chunk loads are no-ops.
|
|
681
|
+
*
|
|
682
|
+
* @example
|
|
683
|
+
* // Signal-driven (modal):
|
|
684
|
+
* <Defer chunk={() => import('./ConfirmDeleteModal')} when={open}>
|
|
685
|
+
* {Modal => <Modal onClose={() => setOpen(false)} />}
|
|
686
|
+
* </Defer>
|
|
687
|
+
*
|
|
688
|
+
* @example
|
|
689
|
+
* // Viewport-driven (below-fold):
|
|
690
|
+
* <Defer chunk={() => import('./Comments')} on="visible">
|
|
691
|
+
* {Comments => <Comments postId={id} />}
|
|
692
|
+
* </Defer>
|
|
693
|
+
*
|
|
694
|
+
* @example
|
|
695
|
+
* // Idle-driven (non-critical):
|
|
696
|
+
* <Defer chunk={() => import('./Analytics')} on="idle">
|
|
697
|
+
* {Dashboard => <Dashboard />}
|
|
698
|
+
* </Defer>
|
|
699
|
+
*/
|
|
700
|
+
function Defer(props) {
|
|
701
|
+
const Loaded = signal(null);
|
|
702
|
+
const Failed = signal(null);
|
|
703
|
+
let loadStarted = false;
|
|
704
|
+
const startLoad = () => {
|
|
705
|
+
if (loadStarted) return;
|
|
706
|
+
loadStarted = true;
|
|
707
|
+
if (!props.chunk) {
|
|
708
|
+
const err = /* @__PURE__ */ new Error("[Pyreon] <Defer> has no `chunk` prop. Either pass `chunk={() => import(\"...\")}` (explicit form), or use the inline form `<Defer when={...}><Component /></Defer>` with `@pyreon/vite-plugin` enabled — the compiler rewrites inline JSX to an explicit chunk-prop call.");
|
|
709
|
+
Failed.set(err);
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
props.chunk().then((mod) => {
|
|
713
|
+
const Comp = typeof mod === "function" ? mod : mod.default;
|
|
714
|
+
if (__DEV__$1 && typeof Comp !== "function") {
|
|
715
|
+
console.warn("[Pyreon] <Defer> chunk() resolved without a default-exported component. Make sure your module exports default.");
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
Loaded.set(Comp);
|
|
719
|
+
}).catch((err) => {
|
|
720
|
+
const wrapped = err instanceof Error ? err : new Error(String(err));
|
|
721
|
+
if (__DEV__$1) console.error("[Pyreon] <Defer> chunk() rejected:", wrapped);
|
|
722
|
+
Failed.set(wrapped);
|
|
723
|
+
});
|
|
724
|
+
};
|
|
725
|
+
if ("when" in props) effect(() => {
|
|
726
|
+
if (props.when() && !loadStarted) startLoad();
|
|
727
|
+
});
|
|
728
|
+
else if (props.on === "idle") onMount(() => _setupIdleTrigger(startLoad));
|
|
729
|
+
const renderContent = () => {
|
|
730
|
+
const err = Failed();
|
|
731
|
+
if (err) throw err;
|
|
732
|
+
const Comp = Loaded();
|
|
733
|
+
if (!Comp) return props.fallback ?? null;
|
|
734
|
+
const ch = props.children;
|
|
735
|
+
if (typeof ch === "function") return ch(Comp);
|
|
736
|
+
return h(Comp, {});
|
|
737
|
+
};
|
|
738
|
+
if ("on" in props && props.on === "visible") {
|
|
739
|
+
const containerRef = createRef();
|
|
740
|
+
onMount(() => _setupVisibleTrigger(containerRef.current, startLoad, props.rootMargin ?? "200px"));
|
|
741
|
+
return h("div", {
|
|
742
|
+
"data-pyreon-defer": "visible",
|
|
743
|
+
ref: containerRef,
|
|
744
|
+
style: "display: contents"
|
|
745
|
+
}, renderContent);
|
|
746
|
+
}
|
|
747
|
+
return h(Fragment, null, renderContent);
|
|
748
|
+
}
|
|
749
|
+
|
|
530
750
|
//#endregion
|
|
531
751
|
//#region src/lazy.ts
|
|
532
752
|
function lazy(load) {
|
|
@@ -705,6 +925,54 @@ function _rp(fn) {
|
|
|
705
925
|
return fn;
|
|
706
926
|
}
|
|
707
927
|
/**
|
|
928
|
+
* Wrap a JSX spread source so its getter-shaped reactive props survive
|
|
929
|
+
* the JS-level object spread that esbuild's automatic JSX runtime emits
|
|
930
|
+
* for `<Comp {...source}>`.
|
|
931
|
+
*
|
|
932
|
+
* Without this wrapper, esbuild compiles `<Comp {...source}>` to
|
|
933
|
+
* `jsx(Comp, { ...source })` — and JS spread fires every getter on
|
|
934
|
+
* `source`, storing the resolved values as plain data properties. Any
|
|
935
|
+
* compiler-emitted reactive prop (`_rp(() => signal())` converted to a
|
|
936
|
+
* getter by `makeReactiveProps`) on `source` is collapsed to its
|
|
937
|
+
* initial value before the receiving component ever sees it.
|
|
938
|
+
*
|
|
939
|
+
* `_wrapSpread(source)` walks `source`'s own keys via `Reflect.ownKeys`
|
|
940
|
+
* (no getter firing) and returns a new object whose values are
|
|
941
|
+
* `_rp`-branded thunks `() => source[key]`. When `{ ..._wrapSpread(s) }`
|
|
942
|
+
* is spread by esbuild, the thunks are stored as plain data property
|
|
943
|
+
* values (no getters to fire), then `makeReactiveProps` in `mount.ts`
|
|
944
|
+
* converts the brands back into getters that lazily read from the
|
|
945
|
+
* original `source` — preserving the reactive subscription end-to-end.
|
|
946
|
+
*
|
|
947
|
+
* Fast path: when `source` has no getter descriptors, return the
|
|
948
|
+
* source object unchanged. JS spread will work correctly in that case
|
|
949
|
+
* because there's nothing reactive to preserve. Saves N thunk
|
|
950
|
+
* allocations per component render in the 99% case.
|
|
951
|
+
*
|
|
952
|
+
* Emitted by the compiler — not generally meant for hand-written code.
|
|
953
|
+
*/
|
|
954
|
+
function _wrapSpread(source) {
|
|
955
|
+
if (!source || typeof source !== "object") return source;
|
|
956
|
+
const descriptors = Object.getOwnPropertyDescriptors(source);
|
|
957
|
+
let hasGetter = false;
|
|
958
|
+
for (const k in descriptors) if (descriptors[k].get) {
|
|
959
|
+
hasGetter = true;
|
|
960
|
+
break;
|
|
961
|
+
}
|
|
962
|
+
if (!hasGetter) return source;
|
|
963
|
+
const result = {};
|
|
964
|
+
for (const key of Reflect.ownKeys(source)) {
|
|
965
|
+
const desc = descriptors[key];
|
|
966
|
+
if (!desc) continue;
|
|
967
|
+
if (desc.get) {
|
|
968
|
+
const fn = () => source[key];
|
|
969
|
+
fn[REACTIVE_PROP] = true;
|
|
970
|
+
result[key] = fn;
|
|
971
|
+
} else result[key] = desc.value;
|
|
972
|
+
}
|
|
973
|
+
return result;
|
|
974
|
+
}
|
|
975
|
+
/**
|
|
708
976
|
* Convert compiler-emitted `_rp(() => expr)` prop values into getter properties.
|
|
709
977
|
*
|
|
710
978
|
* Only converts functions branded with REACTIVE_PROP — user-written accessor
|
|
@@ -752,12 +1020,6 @@ function createUniqueId() {
|
|
|
752
1020
|
return `pyreon-${++_idCounter}`;
|
|
753
1021
|
}
|
|
754
1022
|
|
|
755
|
-
//#endregion
|
|
756
|
-
//#region src/ref.ts
|
|
757
|
-
function createRef() {
|
|
758
|
-
return { current: null };
|
|
759
|
-
}
|
|
760
|
-
|
|
761
1023
|
//#endregion
|
|
762
1024
|
//#region src/show.ts
|
|
763
1025
|
function callWhen(when) {
|
|
@@ -936,5 +1198,5 @@ function Suspense(props) {
|
|
|
936
1198
|
}
|
|
937
1199
|
|
|
938
1200
|
//#endregion
|
|
939
|
-
export { CSS_UNITLESS, Dynamic, EMPTY_PROPS, ErrorBoundary, For, ForSymbol, Fragment, Match, MatchSymbol, NATIVE_COMPAT_MARKER, Portal, PortalSymbol, REACTIVE_PROP, Show, Suspense, Switch, _rp, captureContextStack, createContext, createReactiveContext, createRef, createUniqueId, cx, defineComponent, dispatchToErrorBoundary, h, isNativeCompat, lazy, makeReactiveProps, mapArray, mergeProps, nativeCompat, normalizeStyleValue, onErrorCaptured, onMount, onUnmount, onUpdate, popContext, propagateError, provide, pushContext, registerErrorHandler, reportError, restoreContextStack, runWithHooks, setContextStackProvider, splitProps, toKebabCase, useContext, withContext };
|
|
1201
|
+
export { CSS_UNITLESS, Defer, Dynamic, EMPTY_PROPS, ErrorBoundary, For, ForSymbol, Fragment, Match, MatchSymbol, NATIVE_COMPAT_MARKER, Portal, PortalSymbol, REACTIVE_PROP, Show, Suspense, Switch, _rp, _wrapSpread, captureContextStack, createContext, createReactiveContext, createRef, createUniqueId, cx, defineComponent, dispatchToErrorBoundary, h, isNativeCompat, lazy, makeReactiveProps, mapArray, mapCompatDomProps, mergeProps, nativeCompat, normalizeStyleValue, onErrorCaptured, onMount, onUnmount, onUpdate, popContext, propagateError, provide, pushContext, registerErrorHandler, reportError, restoreContextStack, runWithHooks, setContextStackProvider, shallowEqualProps, splitProps, toKebabCase, useContext, withContext };
|
|
940
1202
|
//# sourceMappingURL=index.js.map
|
package/lib/jsx-dev-runtime.js
CHANGED
|
@@ -60,15 +60,35 @@ function flattenChildren(children) {
|
|
|
60
60
|
* jsx-runtime.ts into the consumer's compilation unit.
|
|
61
61
|
*/
|
|
62
62
|
function jsx(type, props, key) {
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
63
|
+
const descriptors = Object.getOwnPropertyDescriptors(props);
|
|
64
|
+
let hasGetter = false;
|
|
65
|
+
for (const k in descriptors) if (descriptors[k].get) {
|
|
66
|
+
hasGetter = true;
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
const children = props.children;
|
|
70
|
+
if (!hasGetter) {
|
|
71
|
+
const { children: _ignored, ...rest } = props;
|
|
72
|
+
const propsWithKey = key != null ? {
|
|
73
|
+
...rest,
|
|
74
|
+
key
|
|
75
|
+
} : rest;
|
|
76
|
+
if (typeof type === "function") return h(type, children !== void 0 ? {
|
|
77
|
+
...propsWithKey,
|
|
78
|
+
children
|
|
79
|
+
} : propsWithKey);
|
|
80
|
+
return h(type, propsWithKey, ...children === void 0 ? [] : Array.isArray(children) ? children : [children]);
|
|
81
|
+
}
|
|
82
|
+
const propsWithKey = {};
|
|
83
|
+
for (const k in descriptors) {
|
|
84
|
+
if (k === "children") continue;
|
|
85
|
+
Object.defineProperty(propsWithKey, k, descriptors[k]);
|
|
86
|
+
}
|
|
87
|
+
if (key != null) propsWithKey.key = key;
|
|
88
|
+
if (typeof type === "function") {
|
|
89
|
+
if (children !== void 0) propsWithKey.children = children;
|
|
90
|
+
return h(type, propsWithKey);
|
|
91
|
+
}
|
|
72
92
|
return h(type, propsWithKey, ...children === void 0 ? [] : Array.isArray(children) ? children : [children]);
|
|
73
93
|
}
|
|
74
94
|
const jsxs = jsx;
|
package/lib/jsx-runtime.js
CHANGED
|
@@ -60,15 +60,35 @@ function flattenChildren(children) {
|
|
|
60
60
|
* jsx-runtime.ts into the consumer's compilation unit.
|
|
61
61
|
*/
|
|
62
62
|
function jsx(type, props, key) {
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
63
|
+
const descriptors = Object.getOwnPropertyDescriptors(props);
|
|
64
|
+
let hasGetter = false;
|
|
65
|
+
for (const k in descriptors) if (descriptors[k].get) {
|
|
66
|
+
hasGetter = true;
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
const children = props.children;
|
|
70
|
+
if (!hasGetter) {
|
|
71
|
+
const { children: _ignored, ...rest } = props;
|
|
72
|
+
const propsWithKey = key != null ? {
|
|
73
|
+
...rest,
|
|
74
|
+
key
|
|
75
|
+
} : rest;
|
|
76
|
+
if (typeof type === "function") return h(type, children !== void 0 ? {
|
|
77
|
+
...propsWithKey,
|
|
78
|
+
children
|
|
79
|
+
} : propsWithKey);
|
|
80
|
+
return h(type, propsWithKey, ...children === void 0 ? [] : Array.isArray(children) ? children : [children]);
|
|
81
|
+
}
|
|
82
|
+
const propsWithKey = {};
|
|
83
|
+
for (const k in descriptors) {
|
|
84
|
+
if (k === "children") continue;
|
|
85
|
+
Object.defineProperty(propsWithKey, k, descriptors[k]);
|
|
86
|
+
}
|
|
87
|
+
if (key != null) propsWithKey.key = key;
|
|
88
|
+
if (typeof type === "function") {
|
|
89
|
+
if (children !== void 0) propsWithKey.children = children;
|
|
90
|
+
return h(type, propsWithKey);
|
|
91
|
+
}
|
|
72
92
|
return h(type, propsWithKey, ...children === void 0 ? [] : Array.isArray(children) ? children : [children]);
|
|
73
93
|
}
|
|
74
94
|
const jsxs = jsx;
|