@barefootjs/client 0.10.1 → 0.11.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/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +53 -2
- package/dist/profiler-events.d.ts +36 -0
- package/dist/profiler-events.d.ts.map +1 -0
- package/dist/reactive.d.ts +84 -4
- package/dist/reactive.d.ts.map +1 -1
- package/dist/reactive.js +87 -10
- package/dist/runtime/dynamic-text.d.ts +5 -0
- package/dist/runtime/dynamic-text.d.ts.map +1 -1
- package/dist/runtime/index.d.ts +2 -1
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +72 -11
- package/dist/runtime/insert.d.ts +1 -1
- package/dist/runtime/insert.d.ts.map +1 -1
- package/dist/runtime/map-array.d.ts +2 -2
- package/dist/runtime/map-array.d.ts.map +1 -1
- package/dist/runtime/standalone.js +148 -19
- package/package.json +2 -2
- package/src/index.ts +13 -0
- package/src/profiler-events.ts +89 -0
- package/src/reactive.ts +196 -8
- package/src/runtime/dynamic-text.ts +30 -5
- package/src/runtime/index.ts +13 -0
- package/src/runtime/insert.ts +3 -2
- package/src/runtime/map-array.ts +4 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export { createSignal, createEffect, createDisposableEffect, createMemo, createRoot, onCleanup, onMount, untrack, batch, type Reactive, type Signal, type Memo, type CleanupFn, type EffectFn, } from '@barefootjs/client/reactive';
|
|
1
|
+
export { createSignal, createEffect, createDisposableEffect, createMemo, createRoot, onCleanup, onMount, untrack, batch, setProfilerSink, beginTurn, endTurn, __bfReportOutput, type Reactive, type Signal, type Memo, type CleanupFn, type EffectFn, type ProfilerEventSink, type SubscriberKind, } from '@barefootjs/client/reactive';
|
|
2
|
+
export { createRecordingSink, type ProfilerEvent, type ProfilerEventType, type RecordingSink, } from './profiler-events.ts';
|
|
2
3
|
export { splitProps } from './split-props.ts';
|
|
3
4
|
export { __slot, type SlotMarker } from './slot.ts';
|
|
4
5
|
export { forwardProps } from './forward-props.ts';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,sBAAsB,EACtB,UAAU,EACV,UAAU,EACV,SAAS,EACT,OAAO,EACP,OAAO,EACP,KAAK,EACL,KAAK,QAAQ,EACb,KAAK,MAAM,EACX,KAAK,IAAI,EACT,KAAK,SAAS,EACd,KAAK,QAAQ,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,sBAAsB,EACtB,UAAU,EACV,UAAU,EACV,SAAS,EACT,OAAO,EACP,OAAO,EACP,KAAK,EACL,eAAe,EACf,SAAS,EACT,OAAO,EACP,gBAAgB,EAChB,KAAK,QAAQ,EACb,KAAK,MAAM,EACX,KAAK,IAAI,EACT,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,iBAAiB,EACtB,KAAK,cAAc,GACpB,MAAM,6BAA6B,CAAA;AAEpC,OAAO,EACL,mBAAmB,EACnB,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,aAAa,GACnB,MAAM,sBAAsB,CAAA;AAE7B,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAE7C,OAAO,EAAE,MAAM,EAAE,KAAK,UAAU,EAAE,MAAM,WAAW,CAAA;AAEnD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEjD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEpC,OAAO,EAAE,aAAa,EAAE,KAAK,OAAO,EAAE,MAAM,cAAc,CAAA;AAE1D,OAAO,EACL,UAAU,EACV,cAAc,EACd,YAAY,EACZ,WAAW,EACX,eAAe,EACf,wBAAwB,EACxB,KAAK,MAAM,EACX,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,UAAU,GAChB,MAAM,YAAY,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -8,9 +8,55 @@ import {
|
|
|
8
8
|
onCleanup,
|
|
9
9
|
onMount,
|
|
10
10
|
untrack,
|
|
11
|
-
batch
|
|
11
|
+
batch,
|
|
12
|
+
setProfilerSink,
|
|
13
|
+
beginTurn,
|
|
14
|
+
endTurn,
|
|
15
|
+
__bfReportOutput
|
|
12
16
|
} from "@barefootjs/client/reactive";
|
|
13
17
|
|
|
18
|
+
// src/profiler-events.ts
|
|
19
|
+
function createRecordingSink() {
|
|
20
|
+
const events = [];
|
|
21
|
+
const turnStack = [];
|
|
22
|
+
let seq = 0;
|
|
23
|
+
let turnCounter = 0;
|
|
24
|
+
const top = () => turnStack.length > 0 ? turnStack[turnStack.length - 1] : null;
|
|
25
|
+
const push = (e) => {
|
|
26
|
+
const t = top();
|
|
27
|
+
events.push({ seq: seq++, turn: t?.handlerId ?? null, turnSeq: t?.turnSeq ?? null, ...e });
|
|
28
|
+
};
|
|
29
|
+
const sink = {
|
|
30
|
+
signalSet: (id, batched) => push({ type: "signalSet", signal: id, batched }),
|
|
31
|
+
subscribeAdd: (signal, subscriber) => push({ type: "subscribeAdd", signal, subscriber }),
|
|
32
|
+
subscribeRemove: (signal, subscriber) => push({ type: "subscribeRemove", signal, subscriber }),
|
|
33
|
+
effectCreate: (id, kind) => push({ type: "effectCreate", subscriber: id, kind }),
|
|
34
|
+
effectEnter: (id) => push({ type: "effectEnter", subscriber: id }),
|
|
35
|
+
effectExit: (id, dur) => push({ type: "effectExit", subscriber: id, dur }),
|
|
36
|
+
effectOutput: (id, changed) => push({ type: "effectOutput", subscriber: id, changed }),
|
|
37
|
+
effectDispose: (id) => push({ type: "effectDispose", subscriber: id }),
|
|
38
|
+
batchBegin: (depth) => push({ type: "batchBegin", depth }),
|
|
39
|
+
batchFlush: (flushed) => push({ type: "batchFlush", flushed }),
|
|
40
|
+
turnBegin: (handlerId, loc) => {
|
|
41
|
+
push({ type: "turnBegin", handlerId, loc });
|
|
42
|
+
turnStack.push({ handlerId, turnSeq: ++turnCounter });
|
|
43
|
+
},
|
|
44
|
+
turnEnd: () => {
|
|
45
|
+
turnStack.pop();
|
|
46
|
+
push({ type: "turnEnd" });
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
return {
|
|
50
|
+
sink,
|
|
51
|
+
events,
|
|
52
|
+
reset() {
|
|
53
|
+
events.length = 0;
|
|
54
|
+
turnStack.length = 0;
|
|
55
|
+
seq = 0;
|
|
56
|
+
turnCounter = 0;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
14
60
|
// src/split-props.ts
|
|
15
61
|
function splitProps(props, keys) {
|
|
16
62
|
const keySet = new Set(keys);
|
|
@@ -135,20 +181,25 @@ export {
|
|
|
135
181
|
unwrap,
|
|
136
182
|
untrack,
|
|
137
183
|
splitProps,
|
|
184
|
+
setProfilerSink,
|
|
138
185
|
provideContext,
|
|
139
186
|
onMount,
|
|
140
187
|
onCleanup,
|
|
141
188
|
isSSRPortal,
|
|
142
189
|
forwardProps,
|
|
143
190
|
findSiblingSlot,
|
|
191
|
+
endTurn,
|
|
144
192
|
createSignal,
|
|
145
193
|
createRoot,
|
|
194
|
+
createRecordingSink,
|
|
146
195
|
createPortal,
|
|
147
196
|
createMemo,
|
|
148
197
|
createEffect,
|
|
149
198
|
createDisposableEffect,
|
|
150
199
|
createContext,
|
|
151
200
|
cleanupPortalPlaceholder,
|
|
201
|
+
beginTurn,
|
|
152
202
|
batch,
|
|
153
|
-
__slot
|
|
203
|
+
__slot,
|
|
204
|
+
__bfReportOutput
|
|
154
205
|
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Profiler event collection (#1690, SR2).
|
|
3
|
+
*
|
|
4
|
+
* The reactive runtime (`reactive.ts`) reports raw instrumentation through a
|
|
5
|
+
* `ProfilerEventSink` (SR1). This module turns that callback stream into a
|
|
6
|
+
* flat, ordered, **turn-stamped** event log — the normalized SR2 contract the
|
|
7
|
+
* analyses (hot subscribers / wasted re-runs / batch advisor) and the SR4 IR
|
|
8
|
+
* join consume.
|
|
9
|
+
*
|
|
10
|
+
* The collector is the one place that knows the *current turn*: it tracks the
|
|
11
|
+
* `beginTurn`/`endTurn` stack (SR3) and stamps every event with the handler id
|
|
12
|
+
* in scope when it fired, so per-turn metrics (batch savings, runs/turn) need
|
|
13
|
+
* no microtask guesswork.
|
|
14
|
+
*
|
|
15
|
+
* Dev-only (SR8): nothing here runs unless `setProfilerSink` is handed a
|
|
16
|
+
* recording sink, which only the instrumented/profile build does.
|
|
17
|
+
*/
|
|
18
|
+
import type { ProfilerEventSink } from "./reactive.ts";
|
|
19
|
+
import type { ProfilerEvent } from '@barefootjs/shared';
|
|
20
|
+
export type { ProfilerEvent, ProfilerEventType } from '@barefootjs/shared';
|
|
21
|
+
export interface RecordingSink {
|
|
22
|
+
/** The sink to hand to `setProfilerSink(...)`. */
|
|
23
|
+
sink: ProfilerEventSink;
|
|
24
|
+
/** The collected event log, in emission order. */
|
|
25
|
+
events: ProfilerEvent[];
|
|
26
|
+
/** Clear the log and the internal turn stack (start a fresh scenario). */
|
|
27
|
+
reset(): void;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Build a recording sink (SR2). Hand `.sink` to `setProfilerSink`, drive a
|
|
31
|
+
* scenario, then read `.events` — a turn-stamped, ordered log ready for the
|
|
32
|
+
* SR4 join and the analyses. Turns may nest (a handler that dispatches another
|
|
33
|
+
* handler); the stack's top is the attributed turn.
|
|
34
|
+
*/
|
|
35
|
+
export declare function createRecordingSink(): RecordingSink;
|
|
36
|
+
//# sourceMappingURL=profiler-events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"profiler-events.d.ts","sourceRoot":"","sources":["../src/profiler-events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAA;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAIvD,YAAY,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAE1E,MAAM,WAAW,aAAa;IAC5B,kDAAkD;IAClD,IAAI,EAAE,iBAAiB,CAAA;IACvB,kDAAkD;IAClD,MAAM,EAAE,aAAa,EAAE,CAAA;IACvB,0EAA0E;IAC1E,KAAK,IAAI,IAAI,CAAA;CACd;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,IAAI,aAAa,CAgDnD"}
|
package/dist/reactive.d.ts
CHANGED
|
@@ -21,6 +21,86 @@ export type Signal<T> = [
|
|
|
21
21
|
export type CleanupFn = () => void;
|
|
22
22
|
export type EffectFn = () => void | CleanupFn;
|
|
23
23
|
export type Memo<T> = Reactive<() => T>;
|
|
24
|
+
export type SubscriberKind = 'effect' | 'memo' | 'root';
|
|
25
|
+
/**
|
|
26
|
+
* Reactive measurement hooks. Every method is a measurement-only notification —
|
|
27
|
+
* implementations MUST NOT mutate reactive state or throw (a throw would change
|
|
28
|
+
* `set()`'s synchronous semantics). Ids are stable handles; `''` means the
|
|
29
|
+
* node was created while profiling was off. The compiler will later emit
|
|
30
|
+
* IR-aligned ids (SR3); until then ids are runtime-assigned counters.
|
|
31
|
+
*/
|
|
32
|
+
export interface ProfilerEventSink {
|
|
33
|
+
/** A signal's value changed (post `Object.is` bail). `batched` = inside `batch()`. */
|
|
34
|
+
signalSet(signalId: string, batched: boolean): void;
|
|
35
|
+
/** A subscriber began reading a signal (dependency edge added). */
|
|
36
|
+
subscribeAdd(signalId: string, subscriberId: string): void;
|
|
37
|
+
/** A subscriber stopped reading a signal (edge removed on re-run / dispose). */
|
|
38
|
+
subscribeRemove(signalId: string, subscriberId: string): void;
|
|
39
|
+
/** An effect / memo / root scope was created. */
|
|
40
|
+
effectCreate(subscriberId: string, kind: SubscriberKind): void;
|
|
41
|
+
/** An effect/memo body is about to run. */
|
|
42
|
+
effectEnter(subscriberId: string): void;
|
|
43
|
+
/** An effect/memo body finished. `durationMs` is wall time. */
|
|
44
|
+
effectExit(subscriberId: string, durationMs: number): void;
|
|
45
|
+
/**
|
|
46
|
+
* An effect/memo run produced an output fingerprint (#1690, §4.2.2).
|
|
47
|
+
* `changed` is `true` when the run produced new output (a memo value that
|
|
48
|
+
* differs by `Object.is`, or a DOM write that changed the node) and `false`
|
|
49
|
+
* when it recomputed but produced output identical to its previous run — a
|
|
50
|
+
* *wasted* re-run. Emitted at most once per run, only for runs whose output
|
|
51
|
+
* is fingerprintable (memo recompute / instrumented DOM write); a run that
|
|
52
|
+
* reports no output emits no event and isn't counted as wasted.
|
|
53
|
+
*
|
|
54
|
+
* Optional: it was added after the initial sink contract, so a pre-existing
|
|
55
|
+
* custom sink that omits it stays valid (the call site guards with `?.`). A
|
|
56
|
+
* sink without it simply opts out of the wasted-re-runs analysis.
|
|
57
|
+
*/
|
|
58
|
+
effectOutput?(subscriberId: string, changed: boolean): void;
|
|
59
|
+
/** An effect / memo / root scope was disposed. */
|
|
60
|
+
effectDispose(subscriberId: string): void;
|
|
61
|
+
/** A `batch()` block opened at the given (post-increment) depth. */
|
|
62
|
+
batchBegin(depth: number): void;
|
|
63
|
+
/** A batch flush ran `flushed` effects. */
|
|
64
|
+
batchFlush(flushed: number): void;
|
|
65
|
+
/**
|
|
66
|
+
* A user interaction (one event handler invocation) began. Compiler-emitted
|
|
67
|
+
* `beginTurn(...)` wraps the handler body so subsequent events in this turn
|
|
68
|
+
* are attributed to `handlerId`. `loc` is the optional source location.
|
|
69
|
+
*/
|
|
70
|
+
turnBegin(handlerId: string, loc?: string): void;
|
|
71
|
+
/** The current turn ended (handler returned). */
|
|
72
|
+
turnEnd(): void;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Install (or clear) the dev-only reactive measurement sink. Pass `null` to
|
|
76
|
+
* disable. Calling this before a scenario runs lets `bf debug profile` collect
|
|
77
|
+
* the event stream; production code never calls it, so the sink stays null and
|
|
78
|
+
* the choke points stay free (dev-only instrumentation, #1690).
|
|
79
|
+
*/
|
|
80
|
+
export declare function setProfilerSink(sink: ProfilerEventSink | null): void;
|
|
81
|
+
/**
|
|
82
|
+
* Mark the start of a user-interaction turn (#1690, SR3). Compiler-emitted in
|
|
83
|
+
* profile mode at every event-handler boundary as
|
|
84
|
+
* `beginTurn(handlerId, loc); try { … } finally { endTurn() }`. Measurement
|
|
85
|
+
* only — it does not change `set()`'s synchronous semantics; it just stamps a
|
|
86
|
+
* turn onto the events emitted between begin and end. No-op when profiling is
|
|
87
|
+
* off.
|
|
88
|
+
*/
|
|
89
|
+
export declare function beginTurn(handlerId: string, loc?: string): void;
|
|
90
|
+
/** Mark the end of the current interaction turn (#1690, SR3). */
|
|
91
|
+
export declare function endTurn(): void;
|
|
92
|
+
/**
|
|
93
|
+
* Report the current run's output fingerprint (#1690, §4.2.2). Called from the
|
|
94
|
+
* places that produce an effect's observable output — a memo recompute (the
|
|
95
|
+
* written value's `Object.is` identity) and instrumented DOM writes (whether the
|
|
96
|
+
* node actually changed). Accumulated onto the running effect (`Owner`) and
|
|
97
|
+
* flushed as one `effectOutput` event at run exit, so several writes in one run
|
|
98
|
+
* collapse to a single "did this run change anything" verdict.
|
|
99
|
+
*
|
|
100
|
+
* No-op when profiling is off or called outside a run — the wasted-re-runs
|
|
101
|
+
* analysis is the only consumer and it lives behind the dev-only sink (SR8).
|
|
102
|
+
*/
|
|
103
|
+
export declare function __bfReportOutput(changed: boolean): void;
|
|
24
104
|
/**
|
|
25
105
|
* Create a reactive value
|
|
26
106
|
*
|
|
@@ -33,7 +113,7 @@ export type Memo<T> = Reactive<() => T>;
|
|
|
33
113
|
* setCount(5) // Update to 5
|
|
34
114
|
* setCount(n => n + 1) // Update with function (becomes 6)
|
|
35
115
|
*/
|
|
36
|
-
export declare function createSignal<T>(initialValue: T): Signal<T>;
|
|
116
|
+
export declare function createSignal<T>(initialValue: T, __bfId?: string): Signal<T>;
|
|
37
117
|
/**
|
|
38
118
|
* Side effect that runs automatically when signals change
|
|
39
119
|
*
|
|
@@ -46,7 +126,7 @@ export declare function createSignal<T>(initialValue: T): Signal<T>;
|
|
|
46
126
|
* })
|
|
47
127
|
* setCount(1) // Logs "count changed: 1"
|
|
48
128
|
*/
|
|
49
|
-
export declare function createEffect(fn: EffectFn): void;
|
|
129
|
+
export declare function createEffect(fn: EffectFn, __bfId?: string, __bfKind?: SubscriberKind): void;
|
|
50
130
|
/**
|
|
51
131
|
* Create an isolated reactive scope with explicit disposal.
|
|
52
132
|
* All effects/memos created inside run within this root and are
|
|
@@ -64,7 +144,7 @@ export declare function createRoot<T>(fn: (dispose: () => void) => T): T;
|
|
|
64
144
|
*
|
|
65
145
|
* @returns A dispose function that stops the effect and removes it from all signal dependencies.
|
|
66
146
|
*/
|
|
67
|
-
export declare function createDisposableEffect(fn: EffectFn): () => void;
|
|
147
|
+
export declare function createDisposableEffect(fn: EffectFn, __bfId?: string): () => void;
|
|
68
148
|
/**
|
|
69
149
|
* Register cleanup function for effects
|
|
70
150
|
*
|
|
@@ -146,5 +226,5 @@ export declare function onMount(fn: () => void): void;
|
|
|
146
226
|
* setCount(5)
|
|
147
227
|
* doubled() // 10
|
|
148
228
|
*/
|
|
149
|
-
export declare function createMemo<T>(fn: () => T): Memo<T>;
|
|
229
|
+
export declare function createMemo<T>(fn: () => T, __bfId?: string): Memo<T>;
|
|
150
230
|
//# sourceMappingURL=reactive.d.ts.map
|
package/dist/reactive.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reactive.d.ts","sourceRoot":"","sources":["../src/reactive.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;GAIG;AACH,MAAM,MAAM,QAAQ,CAAC,CAAC,IAAI,CAAC,GAAG;IAAE,QAAQ,CAAC,UAAU,EAAE,IAAI,CAAA;CAAE,CAAA;AAE3D,MAAM,MAAM,MAAM,CAAC,CAAC,IAAI;IACtB,yEAAyE;IACzE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACjB,uDAAuD;IACvD,CAAC,SAAS,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI;CAC1C,CAAA;AAED,MAAM,MAAM,SAAS,GAAG,MAAM,IAAI,CAAA;AAClC,MAAM,MAAM,QAAQ,GAAG,MAAM,IAAI,GAAG,SAAS,CAAA;AAC7C,MAAM,MAAM,IAAI,CAAC,CAAC,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"reactive.d.ts","sourceRoot":"","sources":["../src/reactive.ts"],"names":[],"mappings":"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"}
|
package/dist/reactive.js
CHANGED
|
@@ -1,16 +1,41 @@
|
|
|
1
1
|
// src/reactive.ts
|
|
2
|
+
var profilerSink = null;
|
|
3
|
+
var signalSeq = 0;
|
|
4
|
+
var subscriberSeq = 0;
|
|
5
|
+
function setProfilerSink(sink) {
|
|
6
|
+
profilerSink = sink;
|
|
7
|
+
}
|
|
8
|
+
function beginTurn(handlerId, loc) {
|
|
9
|
+
if (profilerSink)
|
|
10
|
+
profilerSink.turnBegin(handlerId, loc);
|
|
11
|
+
}
|
|
12
|
+
function endTurn() {
|
|
13
|
+
if (profilerSink)
|
|
14
|
+
profilerSink.turnEnd();
|
|
15
|
+
}
|
|
16
|
+
function __bfReportOutput(changed) {
|
|
17
|
+
if (!profilerSink || !Owner)
|
|
18
|
+
return;
|
|
19
|
+
Owner.outputReported = true;
|
|
20
|
+
if (changed)
|
|
21
|
+
Owner.outputChanged = true;
|
|
22
|
+
}
|
|
2
23
|
var Owner = null;
|
|
3
24
|
var Listener = null;
|
|
4
25
|
var MAX_EFFECT_RUNS = 100;
|
|
5
26
|
var BatchDepth = 0;
|
|
6
27
|
var PendingEffects = new Set;
|
|
7
|
-
function createSignal(initialValue) {
|
|
28
|
+
function createSignal(initialValue, __bfId) {
|
|
8
29
|
let value = initialValue;
|
|
9
30
|
const subscribers = new Set;
|
|
31
|
+
const id = __bfId ?? (profilerSink ? `s${++signalSeq}` : "");
|
|
32
|
+
subscribers.__bfSignalId = id;
|
|
10
33
|
const get = () => {
|
|
11
34
|
if (Listener) {
|
|
12
35
|
subscribers.add(Listener);
|
|
13
36
|
Listener.dependencies.add(subscribers);
|
|
37
|
+
if (profilerSink)
|
|
38
|
+
profilerSink.subscribeAdd(id, Listener.id);
|
|
14
39
|
}
|
|
15
40
|
return value;
|
|
16
41
|
};
|
|
@@ -20,6 +45,8 @@ function createSignal(initialValue) {
|
|
|
20
45
|
return;
|
|
21
46
|
}
|
|
22
47
|
value = newValue;
|
|
48
|
+
if (profilerSink)
|
|
49
|
+
profilerSink.signalSet(id, BatchDepth > 0);
|
|
23
50
|
if (BatchDepth > 0) {
|
|
24
51
|
for (const effect of subscribers) {
|
|
25
52
|
PendingEffects.add(effect);
|
|
@@ -33,7 +60,7 @@ function createSignal(initialValue) {
|
|
|
33
60
|
};
|
|
34
61
|
return [get, set];
|
|
35
62
|
}
|
|
36
|
-
function createEffect(fn) {
|
|
63
|
+
function createEffect(fn, __bfId, __bfKind = "effect") {
|
|
37
64
|
const effect = {
|
|
38
65
|
fn,
|
|
39
66
|
cleanup: null,
|
|
@@ -41,8 +68,14 @@ function createEffect(fn) {
|
|
|
41
68
|
owner: Owner,
|
|
42
69
|
children: [],
|
|
43
70
|
disposed: false,
|
|
44
|
-
runCount: 0
|
|
71
|
+
runCount: 0,
|
|
72
|
+
id: __bfId ?? (profilerSink ? `e${++subscriberSeq}` : ""),
|
|
73
|
+
kind: __bfKind,
|
|
74
|
+
outputReported: false,
|
|
75
|
+
outputChanged: false
|
|
45
76
|
};
|
|
77
|
+
if (profilerSink)
|
|
78
|
+
profilerSink.effectCreate(effect.id, effect.kind);
|
|
46
79
|
if (Owner)
|
|
47
80
|
Owner.children.push(effect);
|
|
48
81
|
runEffect(effect);
|
|
@@ -55,18 +88,25 @@ function runEffect(effect) {
|
|
|
55
88
|
effect.runCount = 0;
|
|
56
89
|
throw new Error(`Circular dependency detected: effect re-entered itself ${MAX_EFFECT_RUNS} times.`);
|
|
57
90
|
}
|
|
91
|
+
if (profilerSink)
|
|
92
|
+
profilerSink.effectEnter(effect.id);
|
|
58
93
|
if (effect.cleanup) {
|
|
59
94
|
effect.cleanup();
|
|
60
95
|
effect.cleanup = null;
|
|
61
96
|
}
|
|
62
97
|
for (const dep of effect.dependencies) {
|
|
63
98
|
dep.delete(effect);
|
|
99
|
+
if (profilerSink)
|
|
100
|
+
profilerSink.subscribeRemove(dep.__bfSignalId ?? "", effect.id);
|
|
64
101
|
}
|
|
65
102
|
effect.dependencies.clear();
|
|
66
103
|
const prevOwner = Owner;
|
|
67
104
|
const prevListener = Listener;
|
|
68
105
|
Owner = effect;
|
|
69
106
|
Listener = effect;
|
|
107
|
+
effect.outputReported = false;
|
|
108
|
+
effect.outputChanged = false;
|
|
109
|
+
const start = profilerSink ? performance.now() : 0;
|
|
70
110
|
try {
|
|
71
111
|
const result = effect.fn();
|
|
72
112
|
if (typeof result === "function") {
|
|
@@ -76,6 +116,11 @@ function runEffect(effect) {
|
|
|
76
116
|
Owner = prevOwner;
|
|
77
117
|
Listener = prevListener;
|
|
78
118
|
effect.runCount--;
|
|
119
|
+
if (profilerSink) {
|
|
120
|
+
profilerSink.effectExit(effect.id, performance.now() - start);
|
|
121
|
+
if (effect.outputReported)
|
|
122
|
+
profilerSink.effectOutput?.(effect.id, effect.outputChanged);
|
|
123
|
+
}
|
|
79
124
|
}
|
|
80
125
|
}
|
|
81
126
|
function disposeSubtree(effect) {
|
|
@@ -92,8 +137,12 @@ function disposeSubtree(effect) {
|
|
|
92
137
|
}
|
|
93
138
|
for (const dep of effect.dependencies) {
|
|
94
139
|
dep.delete(effect);
|
|
140
|
+
if (profilerSink)
|
|
141
|
+
profilerSink.subscribeRemove(dep.__bfSignalId ?? "", effect.id);
|
|
95
142
|
}
|
|
96
143
|
effect.dependencies.clear();
|
|
144
|
+
if (profilerSink)
|
|
145
|
+
profilerSink.effectDispose(effect.id);
|
|
97
146
|
effect.owner = null;
|
|
98
147
|
}
|
|
99
148
|
function disposeEffect(effect) {
|
|
@@ -114,8 +163,14 @@ function createRoot(fn) {
|
|
|
114
163
|
owner: Owner,
|
|
115
164
|
children: [],
|
|
116
165
|
disposed: false,
|
|
117
|
-
runCount: 0
|
|
166
|
+
runCount: 0,
|
|
167
|
+
id: profilerSink ? `r${++subscriberSeq}` : "",
|
|
168
|
+
kind: "root",
|
|
169
|
+
outputReported: false,
|
|
170
|
+
outputChanged: false
|
|
118
171
|
};
|
|
172
|
+
if (profilerSink)
|
|
173
|
+
profilerSink.effectCreate(root.id, "root");
|
|
119
174
|
if (Owner)
|
|
120
175
|
Owner.children.push(root);
|
|
121
176
|
const prevOwner = Owner;
|
|
@@ -129,7 +184,7 @@ function createRoot(fn) {
|
|
|
129
184
|
Listener = prevListener;
|
|
130
185
|
}
|
|
131
186
|
}
|
|
132
|
-
function createDisposableEffect(fn) {
|
|
187
|
+
function createDisposableEffect(fn, __bfId) {
|
|
133
188
|
let disposed = false;
|
|
134
189
|
const effect = {
|
|
135
190
|
fn: () => {
|
|
@@ -142,8 +197,14 @@ function createDisposableEffect(fn) {
|
|
|
142
197
|
owner: Owner,
|
|
143
198
|
children: [],
|
|
144
199
|
disposed: false,
|
|
145
|
-
runCount: 0
|
|
200
|
+
runCount: 0,
|
|
201
|
+
id: __bfId ?? (profilerSink ? `e${++subscriberSeq}` : ""),
|
|
202
|
+
kind: "effect",
|
|
203
|
+
outputReported: false,
|
|
204
|
+
outputChanged: false
|
|
146
205
|
};
|
|
206
|
+
if (profilerSink)
|
|
207
|
+
profilerSink.effectCreate(effect.id, effect.kind);
|
|
147
208
|
if (Owner)
|
|
148
209
|
Owner.children.push(effect);
|
|
149
210
|
runEffect(effect);
|
|
@@ -173,6 +234,8 @@ function untrack(fn) {
|
|
|
173
234
|
}
|
|
174
235
|
function batch(fn) {
|
|
175
236
|
BatchDepth++;
|
|
237
|
+
if (profilerSink)
|
|
238
|
+
profilerSink.batchBegin(BatchDepth);
|
|
176
239
|
try {
|
|
177
240
|
return fn();
|
|
178
241
|
} finally {
|
|
@@ -186,6 +249,8 @@ function flushEffects() {
|
|
|
186
249
|
while (PendingEffects.size > 0) {
|
|
187
250
|
const effects = [...PendingEffects];
|
|
188
251
|
PendingEffects.clear();
|
|
252
|
+
if (profilerSink)
|
|
253
|
+
profilerSink.batchFlush(effects.length);
|
|
189
254
|
for (const effect of effects) {
|
|
190
255
|
runEffect(effect);
|
|
191
256
|
}
|
|
@@ -194,22 +259,34 @@ function flushEffects() {
|
|
|
194
259
|
function onMount(fn) {
|
|
195
260
|
createEffect(() => untrack(fn));
|
|
196
261
|
}
|
|
197
|
-
function createMemo(fn) {
|
|
198
|
-
const
|
|
262
|
+
function createMemo(fn, __bfId) {
|
|
263
|
+
const id = __bfId ?? (profilerSink ? `m${++subscriberSeq}` : "");
|
|
264
|
+
const [value, setValue] = createSignal(undefined, id);
|
|
265
|
+
let prev;
|
|
266
|
+
let hasPrev = false;
|
|
199
267
|
createEffect(() => {
|
|
200
268
|
const result = fn();
|
|
269
|
+
if (profilerSink) {
|
|
270
|
+
__bfReportOutput(!hasPrev || !Object.is(prev, result));
|
|
271
|
+
prev = result;
|
|
272
|
+
hasPrev = true;
|
|
273
|
+
}
|
|
201
274
|
setValue(() => result);
|
|
202
|
-
});
|
|
275
|
+
}, id, "memo");
|
|
203
276
|
return value;
|
|
204
277
|
}
|
|
205
278
|
export {
|
|
206
279
|
untrack,
|
|
280
|
+
setProfilerSink,
|
|
207
281
|
onMount,
|
|
208
282
|
onCleanup,
|
|
283
|
+
endTurn,
|
|
209
284
|
createSignal,
|
|
210
285
|
createRoot,
|
|
211
286
|
createMemo,
|
|
212
287
|
createEffect,
|
|
213
288
|
createDisposableEffect,
|
|
214
|
-
|
|
289
|
+
beginTurn,
|
|
290
|
+
batch,
|
|
291
|
+
__bfReportOutput
|
|
215
292
|
};
|
|
@@ -10,6 +10,11 @@
|
|
|
10
10
|
* returned by `createComponent`. Stringifying it produced
|
|
11
11
|
* `"[object HTMLElement]"` (and clobbered the server-rendered subtree).
|
|
12
12
|
*
|
|
13
|
+
* Profiler note (#1690, §4.2.2): each write reports an output fingerprint via
|
|
14
|
+
* `__bfReportOutput` — `false` when the slot already held the same text/node, so
|
|
15
|
+
* the wasted-re-runs analysis can flag a text binding that re-ran without
|
|
16
|
+
* changing the DOM. Dev-only: `__bfReportOutput` is a no-op when profiling is off.
|
|
17
|
+
*
|
|
13
18
|
* `__bfText` mirrors `__bfSlot` (the branch-template equivalent): when the
|
|
14
19
|
* value is a `Node`, it replaces the slot region with that node by identity;
|
|
15
20
|
* otherwise it behaves exactly like the previous text assignment. It returns
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-text.d.ts","sourceRoot":"","sources":["../../src/runtime/dynamic-text.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"dynamic-text.d.ts","sourceRoot":"","sources":["../../src/runtime/dynamic-text.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAsCH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,IAAI,GAAG,IAAI,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,GAAG,IAAI,CAoD1E"}
|
package/dist/runtime/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export { createSignal, createEffect, createDisposableEffect, createMemo, createRoot, onCleanup, onMount, untrack, batch, type Reactive, type Signal, type Memo, type CleanupFn, type EffectFn, } from '@barefootjs/client/reactive';
|
|
1
|
+
export { createSignal, createEffect, createDisposableEffect, createMemo, createRoot, onCleanup, onMount, untrack, batch, setProfilerSink, beginTurn, endTurn, __bfReportOutput, type Reactive, type Signal, type Memo, type CleanupFn, type EffectFn, type ProfilerEventSink, type SubscriberKind, } from '@barefootjs/client/reactive';
|
|
2
|
+
export { createRecordingSink, type ProfilerEvent, type ProfilerEventType, type RecordingSink, } from '../profiler-events.ts';
|
|
2
3
|
export { splitProps } from '../split-props.ts';
|
|
3
4
|
export { __slot, type SlotMarker } from '../slot.ts';
|
|
4
5
|
export { forwardProps } from '../forward-props.ts';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"AAUA,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,sBAAsB,EACtB,UAAU,EACV,UAAU,EACV,SAAS,EACT,OAAO,EACP,OAAO,EACP,KAAK,EACL,KAAK,QAAQ,EACb,KAAK,MAAM,EACX,KAAK,IAAI,EACT,KAAK,SAAS,EACd,KAAK,QAAQ,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"AAUA,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,sBAAsB,EACtB,UAAU,EACV,UAAU,EACV,SAAS,EACT,OAAO,EACP,OAAO,EACP,KAAK,EACL,eAAe,EACf,SAAS,EACT,OAAO,EACP,gBAAgB,EAChB,KAAK,QAAQ,EACb,KAAK,MAAM,EACX,KAAK,IAAI,EACT,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,iBAAiB,EACtB,KAAK,cAAc,GACpB,MAAM,6BAA6B,CAAA;AAEpC,OAAO,EACL,mBAAmB,EACnB,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,aAAa,GACnB,MAAM,uBAAuB,CAAA;AAE9B,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAC9C,OAAO,EAAE,MAAM,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAA;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAIrC,OAAO,EACL,aAAa,EACb,UAAU,EACV,cAAc,EACd,eAAe,EACf,KAAK,OAAO,GACb,MAAM,cAAc,CAAA;AAGrB,OAAO,EACL,YAAY,EACZ,WAAW,EACX,eAAe,EACf,wBAAwB,EACxB,KAAK,MAAM,EACX,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,KAAK,cAAc,GACpB,MAAM,aAAa,CAAA;AAGpB,OAAO,EAAE,aAAa,EAAE,KAAK,YAAY,EAAE,MAAM,WAAW,CAAA;AAC5D,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAC1F,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AACxD,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAG3D,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,WAAW,EAAE,KAAK,UAAU,EAAE,MAAM,eAAe,CAAA;AAG3F,OAAO,EACL,eAAe,EACf,WAAW,EACX,gBAAgB,EAChB,iBAAiB,EACjB,SAAS,EACT,UAAU,EACV,UAAU,GACX,MAAM,gBAAgB,CAAA;AAGvB,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAGvC,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,aAAa,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AACtG,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AACtF,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC3F,OAAO,EAAE,MAAM,EAAE,KAAK,YAAY,EAAE,KAAK,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAClF,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAC5C,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAGvD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAGrD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAGpC,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAG1D,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA"}
|