@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 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/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,GACd,MAAM,6BAA6B,CAAA;AAEpC,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"}
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"}
@@ -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
@@ -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;AAmBvC;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAoC1D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAmB/C;AAsFD;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,OAAO,EAAE,MAAM,IAAI,KAAK,CAAC,GAAG,CAAC,CAwB/D;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,QAAQ,GAAG,MAAM,IAAI,CAuB/D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI,CAS7C;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAQzC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,KAAK,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAUvC;AAYD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,OAAO,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI,CAE5C;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CASlD"}
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 [value, setValue] = createSignal(undefined);
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
- batch
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;;;;;;;;;;;;;;;;;GAiBG;AA4BH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,IAAI,GAAG,IAAI,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,GAAG,IAAI,CA0C1E"}
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"}
@@ -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,GACd,MAAM,6BAA6B,CAAA;AAEpC,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"}
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"}