@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.
@@ -8,9 +8,55 @@ import {
8
8
  onCleanup,
9
9
  onMount,
10
10
  untrack as untrack3,
11
- batch
11
+ batch,
12
+ setProfilerSink,
13
+ beginTurn,
14
+ endTurn,
15
+ __bfReportOutput as __bfReportOutput2
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);
@@ -1640,7 +1686,7 @@ function createItemScope(item, index, renderItem, existingPrimary, existingExtra
1640
1686
  });
1641
1687
  return { startMarker, primaryEl, extras, dispose, setItem };
1642
1688
  }
1643
- function mapArray(accessor, container, getKey, renderItem, markerId) {
1689
+ function mapArray(accessor, container, getKey, renderItem, markerId, bfId) {
1644
1690
  if (!container)
1645
1691
  return;
1646
1692
  const scopes = new Map;
@@ -1760,7 +1806,7 @@ function mapArray(accessor, container, getKey, renderItem, markerId) {
1760
1806
  insertScope(scope, container, anchor);
1761
1807
  }
1762
1808
  }
1763
- });
1809
+ }, bfId);
1764
1810
  }
1765
1811
  var ITEM_PREFIX = `${BF_LOOP_ITEM}:`;
1766
1812
  function isItemAnchor(node) {
@@ -1823,7 +1869,7 @@ function createAnchorScope(item, index, key, renderItem, existingAnchor) {
1823
1869
  }
1824
1870
  return { anchor, pending: frag, dispose, setItem };
1825
1871
  }
1826
- function mapArrayAnchored(accessor, container, getKey, renderItem, markerId) {
1872
+ function mapArrayAnchored(accessor, container, getKey, renderItem, markerId, bfId) {
1827
1873
  if (!container)
1828
1874
  return;
1829
1875
  const scopes = new Map;
@@ -1904,7 +1950,7 @@ function mapArrayAnchored(accessor, container, getKey, renderItem, markerId) {
1904
1950
  placeAnchorScope(scope, container, end, end);
1905
1951
  }
1906
1952
  }
1907
- });
1953
+ }, bfId);
1908
1954
  }
1909
1955
  // src/runtime/apply-rest-attrs.ts
1910
1956
  import { createEffect as createEffect2 } from "@barefootjs/client/reactive";
@@ -2075,7 +2121,7 @@ function normalizeTemplate(value) {
2075
2121
  function evalBranchTemplate(branch) {
2076
2122
  return untrack2(() => normalizeTemplate(branch.template()));
2077
2123
  }
2078
- function insert(scope, id, conditionFn, whenTrue, whenFalse) {
2124
+ function insert(scope, id, conditionFn, whenTrue, whenFalse, bfId) {
2079
2125
  if (!scope)
2080
2126
  return;
2081
2127
  const region = makeRegion(scope);
@@ -2163,7 +2209,7 @@ function insert(scope, id, conditionFn, whenTrue, whenFalse) {
2163
2209
  const cleanup = branch.bindEvents(region.bindScope, { isFirstRun: false });
2164
2210
  branchCleanup = typeof cleanup === "function" ? cleanup : null;
2165
2211
  autoFocusConditionalElement(region, id);
2166
- });
2212
+ }, bfId);
2167
2213
  }
2168
2214
  function autoFocusConditionalElement(region, id) {
2169
2215
  requestAnimationFrame(() => {
@@ -2282,15 +2328,20 @@ function __bfSlot(value, slots) {
2282
2328
  return escapeText(value);
2283
2329
  }
2284
2330
  // src/runtime/dynamic-text.ts
2331
+ import { __bfReportOutput } from "@barefootjs/client/reactive";
2285
2332
  var END_MARKER = "/";
2286
2333
  function clearSlotRegion(start, keep) {
2334
+ let removed = false;
2287
2335
  let n = start.nextSibling;
2288
2336
  while (n && !(n.nodeType === Node.COMMENT_NODE && n.nodeValue === END_MARKER)) {
2289
2337
  const next = n.nextSibling;
2290
- if (n !== keep)
2338
+ if (n !== keep) {
2291
2339
  n.parentNode?.removeChild(n);
2340
+ removed = true;
2341
+ }
2292
2342
  n = next;
2293
2343
  }
2344
+ return removed;
2294
2345
  }
2295
2346
  function slotStart(node) {
2296
2347
  let n = node.previousSibling;
@@ -2304,9 +2355,12 @@ function __bfText(current, value) {
2304
2355
  if (value != null && value.__isSlot)
2305
2356
  return current;
2306
2357
  if (typeof Node !== "undefined" && value instanceof Node) {
2307
- if (value === current)
2358
+ if (value === current) {
2359
+ __bfReportOutput(false);
2308
2360
  return current;
2361
+ }
2309
2362
  const start2 = current.previousSibling;
2363
+ __bfReportOutput(true);
2310
2364
  if (start2 && start2.nodeType === Node.COMMENT_NODE) {
2311
2365
  clearSlotRegion(start2);
2312
2366
  start2.parentNode?.insertBefore(value, start2.nextSibling);
@@ -2317,12 +2371,14 @@ function __bfText(current, value) {
2317
2371
  }
2318
2372
  const text = String(value ?? "");
2319
2373
  if (current.nodeType === Node.TEXT_NODE) {
2374
+ const textChanged = current.nodeValue !== text;
2320
2375
  current.nodeValue = text;
2321
2376
  const start2 = slotStart(current);
2322
- if (start2 && start2.nodeType === Node.COMMENT_NODE)
2323
- clearSlotRegion(start2, current);
2377
+ const clearedStale = start2 != null && start2.nodeType === Node.COMMENT_NODE && clearSlotRegion(start2, current);
2378
+ __bfReportOutput(textChanged || clearedStale);
2324
2379
  return current;
2325
2380
  }
2381
+ __bfReportOutput(true);
2326
2382
  const start = current.previousSibling;
2327
2383
  const textNode = (current.ownerDocument ?? document).createTextNode(text);
2328
2384
  if (start && start.nodeType === Node.COMMENT_NODE) {
@@ -2437,6 +2493,7 @@ export {
2437
2493
  spreadAttrs,
2438
2494
  splitProps,
2439
2495
  setupStreaming,
2496
+ setProfilerSink,
2440
2497
  setCurrentScope,
2441
2498
  renderChild,
2442
2499
  render,
@@ -2475,9 +2532,11 @@ export {
2475
2532
  find,
2476
2533
  escapeText,
2477
2534
  escapeAttr,
2535
+ endTurn,
2478
2536
  cssEscape,
2479
2537
  createSignal2 as createSignal,
2480
2538
  createRoot2 as createRoot,
2539
+ createRecordingSink,
2481
2540
  createPortal,
2482
2541
  createMemo,
2483
2542
  createEffect4 as createEffect,
@@ -2485,12 +2544,14 @@ export {
2485
2544
  createContext,
2486
2545
  createComponent,
2487
2546
  cleanupPortalPlaceholder,
2547
+ beginTurn,
2488
2548
  batch,
2489
2549
  applyRestAttrs,
2490
2550
  __slot,
2491
2551
  __bf_swap,
2492
2552
  __bfText,
2493
2553
  __bfSlot,
2554
+ __bfReportOutput2 as __bfReportOutput,
2494
2555
  $t,
2495
2556
  $c,
2496
2557
  $
@@ -71,5 +71,5 @@ export interface BranchConfig {
71
71
  * @param whenTrue - Branch config for when condition is true
72
72
  * @param whenFalse - Branch config for when condition is false
73
73
  */
74
- export declare function insert(scope: Element | Comment | null, id: string, conditionFn: () => boolean, whenTrue: BranchConfig, whenFalse: BranchConfig): void;
74
+ export declare function insert(scope: Element | Comment | null, id: string, conditionFn: () => boolean, whenTrue: BranchConfig, whenFalse: BranchConfig, bfId?: string): void;
75
75
  //# sourceMappingURL=insert.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"insert.d.ts","sourceRoot":"","sources":["../../src/runtime/insert.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAmFH;;;;;GAKG;AACH,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,IAAI,EAAE,CAAA;CACd;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,QAAQ,EAAE,MAAM,MAAM,GAAG,oBAAoB,CAAA;IAE7C;;;;;;OAMG;IACH,UAAU,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAA;CACrF;AAyBD;;;;;;;;;;;;GAYG;AACH,wBAAgB,MAAM,CACpB,KAAK,EAAE,OAAO,GAAG,OAAO,GAAG,IAAI,EAC/B,EAAE,EAAE,MAAM,EACV,WAAW,EAAE,MAAM,OAAO,EAC1B,QAAQ,EAAE,YAAY,EACtB,SAAS,EAAE,YAAY,GACtB,IAAI,CA+IN"}
1
+ {"version":3,"file":"insert.d.ts","sourceRoot":"","sources":["../../src/runtime/insert.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAmFH;;;;;GAKG;AACH,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,IAAI,EAAE,CAAA;CACd;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,QAAQ,EAAE,MAAM,MAAM,GAAG,oBAAoB,CAAA;IAE7C;;;;;;OAMG;IACH,UAAU,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAA;CACrF;AAyBD;;;;;;;;;;;;GAYG;AACH,wBAAgB,MAAM,CACpB,KAAK,EAAE,OAAO,GAAG,OAAO,GAAG,IAAI,EAC/B,EAAE,EAAE,MAAM,EACV,WAAW,EAAE,MAAM,OAAO,EAC1B,QAAQ,EAAE,YAAY,EACtB,SAAS,EAAE,YAAY,EACvB,IAAI,CAAC,EAAE,MAAM,GACZ,IAAI,CA+IN"}
@@ -28,7 +28,7 @@
28
28
  * When `existing` is passed, initializes the SSR-rendered element and returns it.
29
29
  * When `existing` is undefined, creates a new element and returns it.
30
30
  */
31
- export declare function mapArray<T>(accessor: () => T[], container: HTMLElement | null, getKey: ((item: T, index: number) => string) | null, renderItem: (item: () => T, index: number, existing?: HTMLElement) => HTMLElement, markerId?: string): void;
31
+ export declare function mapArray<T>(accessor: () => T[], container: HTMLElement | null, getKey: ((item: T, index: number) => string) | null, renderItem: (item: () => T, index: number, existing?: HTMLElement) => HTMLElement, markerId?: string, bfId?: string): void;
32
32
  /**
33
33
  * Per-item scoped list rendering for whole-item conditionals (#1665).
34
34
  *
@@ -37,5 +37,5 @@ export declare function mapArray<T>(accessor: () => T[], container: HTMLElement
37
37
  * (hydration). Items may render zero elements; the anchor is the stable
38
38
  * identity and position.
39
39
  */
40
- export declare function mapArrayAnchored<T>(accessor: () => T[], container: HTMLElement | null, getKey: ((item: T, index: number) => string) | null, renderItem: (item: () => T, index: number, existing?: Comment) => DocumentFragment | Comment, markerId?: string): void;
40
+ export declare function mapArrayAnchored<T>(accessor: () => T[], container: HTMLElement | null, getKey: ((item: T, index: number) => string) | null, renderItem: (item: () => T, index: number, existing?: Comment) => DocumentFragment | Comment, markerId?: string, bfId?: string): void;
41
41
  //# sourceMappingURL=map-array.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"map-array.d.ts","sourceRoot":"","sources":["../../src/runtime/map-array.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AA6LH;;;;;;;;;;GAUG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EACxB,QAAQ,EAAE,MAAM,CAAC,EAAE,EACnB,SAAS,EAAE,WAAW,GAAG,IAAI,EAC7B,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,GAAG,IAAI,EACnD,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,WAAW,KAAK,WAAW,EACjF,QAAQ,CAAC,EAAE,MAAM,GAChB,IAAI,CAwKN;AAqHD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAChC,QAAQ,EAAE,MAAM,CAAC,EAAE,EACnB,SAAS,EAAE,WAAW,GAAG,IAAI,EAC7B,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,GAAG,IAAI,EACnD,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,KAAK,gBAAgB,GAAG,OAAO,EAC5F,QAAQ,CAAC,EAAE,MAAM,GAChB,IAAI,CA+FN"}
1
+ {"version":3,"file":"map-array.d.ts","sourceRoot":"","sources":["../../src/runtime/map-array.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AA6LH;;;;;;;;;;GAUG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EACxB,QAAQ,EAAE,MAAM,CAAC,EAAE,EACnB,SAAS,EAAE,WAAW,GAAG,IAAI,EAC7B,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,GAAG,IAAI,EACnD,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,WAAW,KAAK,WAAW,EACjF,QAAQ,CAAC,EAAE,MAAM,EACjB,IAAI,CAAC,EAAE,MAAM,GACZ,IAAI,CAwKN;AAqHD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAChC,QAAQ,EAAE,MAAM,CAAC,EAAE,EACnB,SAAS,EAAE,WAAW,GAAG,IAAI,EAC7B,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,GAAG,IAAI,EACnD,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,KAAK,gBAAgB,GAAG,OAAO,EAC5F,QAAQ,CAAC,EAAE,MAAM,EACjB,IAAI,CAAC,EAAE,MAAM,GACZ,IAAI,CA+FN"}
@@ -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,14 +259,64 @@ 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
  }
278
+ // src/profiler-events.ts
279
+ function createRecordingSink() {
280
+ const events = [];
281
+ const turnStack = [];
282
+ let seq = 0;
283
+ let turnCounter = 0;
284
+ const top = () => turnStack.length > 0 ? turnStack[turnStack.length - 1] : null;
285
+ const push = (e) => {
286
+ const t = top();
287
+ events.push({ seq: seq++, turn: t?.handlerId ?? null, turnSeq: t?.turnSeq ?? null, ...e });
288
+ };
289
+ const sink = {
290
+ signalSet: (id, batched) => push({ type: "signalSet", signal: id, batched }),
291
+ subscribeAdd: (signal, subscriber) => push({ type: "subscribeAdd", signal, subscriber }),
292
+ subscribeRemove: (signal, subscriber) => push({ type: "subscribeRemove", signal, subscriber }),
293
+ effectCreate: (id, kind) => push({ type: "effectCreate", subscriber: id, kind }),
294
+ effectEnter: (id) => push({ type: "effectEnter", subscriber: id }),
295
+ effectExit: (id, dur) => push({ type: "effectExit", subscriber: id, dur }),
296
+ effectOutput: (id, changed) => push({ type: "effectOutput", subscriber: id, changed }),
297
+ effectDispose: (id) => push({ type: "effectDispose", subscriber: id }),
298
+ batchBegin: (depth) => push({ type: "batchBegin", depth }),
299
+ batchFlush: (flushed) => push({ type: "batchFlush", flushed }),
300
+ turnBegin: (handlerId, loc) => {
301
+ push({ type: "turnBegin", handlerId, loc });
302
+ turnStack.push({ handlerId, turnSeq: ++turnCounter });
303
+ },
304
+ turnEnd: () => {
305
+ turnStack.pop();
306
+ push({ type: "turnEnd" });
307
+ }
308
+ };
309
+ return {
310
+ sink,
311
+ events,
312
+ reset() {
313
+ events.length = 0;
314
+ turnStack.length = 0;
315
+ seq = 0;
316
+ turnCounter = 0;
317
+ }
318
+ };
319
+ }
205
320
  // src/split-props.ts
206
321
  function splitProps(props, keys) {
207
322
  const keySet = new Set(keys);
@@ -1829,7 +1944,7 @@ function createItemScope(item, index, renderItem, existingPrimary, existingExtra
1829
1944
  });
1830
1945
  return { startMarker, primaryEl, extras, dispose, setItem };
1831
1946
  }
1832
- function mapArray(accessor, container, getKey, renderItem, markerId) {
1947
+ function mapArray(accessor, container, getKey, renderItem, markerId, bfId) {
1833
1948
  if (!container)
1834
1949
  return;
1835
1950
  const scopes = new Map;
@@ -1949,7 +2064,7 @@ function mapArray(accessor, container, getKey, renderItem, markerId) {
1949
2064
  insertScope(scope, container, anchor);
1950
2065
  }
1951
2066
  }
1952
- });
2067
+ }, bfId);
1953
2068
  }
1954
2069
  var ITEM_PREFIX = `${BF_LOOP_ITEM}:`;
1955
2070
  function isItemAnchor(node) {
@@ -2012,7 +2127,7 @@ function createAnchorScope(item, index, key, renderItem, existingAnchor) {
2012
2127
  }
2013
2128
  return { anchor, pending: frag, dispose, setItem };
2014
2129
  }
2015
- function mapArrayAnchored(accessor, container, getKey, renderItem, markerId) {
2130
+ function mapArrayAnchored(accessor, container, getKey, renderItem, markerId, bfId) {
2016
2131
  if (!container)
2017
2132
  return;
2018
2133
  const scopes = new Map;
@@ -2093,7 +2208,7 @@ function mapArrayAnchored(accessor, container, getKey, renderItem, markerId) {
2093
2208
  placeAnchorScope(scope, container, end, end);
2094
2209
  }
2095
2210
  }
2096
- });
2211
+ }, bfId);
2097
2212
  }
2098
2213
  // src/runtime/style.ts
2099
2214
  function styleToCss(value) {
@@ -2260,7 +2375,7 @@ function normalizeTemplate(value) {
2260
2375
  function evalBranchTemplate(branch) {
2261
2376
  return untrack(() => normalizeTemplate(branch.template()));
2262
2377
  }
2263
- function insert(scope, id, conditionFn, whenTrue, whenFalse) {
2378
+ function insert(scope, id, conditionFn, whenTrue, whenFalse, bfId) {
2264
2379
  if (!scope)
2265
2380
  return;
2266
2381
  const region = makeRegion(scope);
@@ -2348,7 +2463,7 @@ function insert(scope, id, conditionFn, whenTrue, whenFalse) {
2348
2463
  const cleanup = branch.bindEvents(region.bindScope, { isFirstRun: false });
2349
2464
  branchCleanup = typeof cleanup === "function" ? cleanup : null;
2350
2465
  autoFocusConditionalElement(region, id);
2351
- });
2466
+ }, bfId);
2352
2467
  }
2353
2468
  function autoFocusConditionalElement(region, id) {
2354
2469
  requestAnimationFrame(() => {
@@ -2469,13 +2584,17 @@ function __bfSlot(value, slots) {
2469
2584
  // src/runtime/dynamic-text.ts
2470
2585
  var END_MARKER = "/";
2471
2586
  function clearSlotRegion(start, keep) {
2587
+ let removed = false;
2472
2588
  let n = start.nextSibling;
2473
2589
  while (n && !(n.nodeType === Node.COMMENT_NODE && n.nodeValue === END_MARKER)) {
2474
2590
  const next = n.nextSibling;
2475
- if (n !== keep)
2591
+ if (n !== keep) {
2476
2592
  n.parentNode?.removeChild(n);
2593
+ removed = true;
2594
+ }
2477
2595
  n = next;
2478
2596
  }
2597
+ return removed;
2479
2598
  }
2480
2599
  function slotStart(node) {
2481
2600
  let n = node.previousSibling;
@@ -2489,9 +2608,12 @@ function __bfText(current, value) {
2489
2608
  if (value != null && value.__isSlot)
2490
2609
  return current;
2491
2610
  if (typeof Node !== "undefined" && value instanceof Node) {
2492
- if (value === current)
2611
+ if (value === current) {
2612
+ __bfReportOutput(false);
2493
2613
  return current;
2614
+ }
2494
2615
  const start2 = current.previousSibling;
2616
+ __bfReportOutput(true);
2495
2617
  if (start2 && start2.nodeType === Node.COMMENT_NODE) {
2496
2618
  clearSlotRegion(start2);
2497
2619
  start2.parentNode?.insertBefore(value, start2.nextSibling);
@@ -2502,12 +2624,14 @@ function __bfText(current, value) {
2502
2624
  }
2503
2625
  const text = String(value ?? "");
2504
2626
  if (current.nodeType === Node.TEXT_NODE) {
2627
+ const textChanged = current.nodeValue !== text;
2505
2628
  current.nodeValue = text;
2506
2629
  const start2 = slotStart(current);
2507
- if (start2 && start2.nodeType === Node.COMMENT_NODE)
2508
- clearSlotRegion(start2, current);
2630
+ const clearedStale = start2 != null && start2.nodeType === Node.COMMENT_NODE && clearSlotRegion(start2, current);
2631
+ __bfReportOutput(textChanged || clearedStale);
2509
2632
  return current;
2510
2633
  }
2634
+ __bfReportOutput(true);
2511
2635
  const start = current.previousSibling;
2512
2636
  const textNode = (current.ownerDocument ?? document).createTextNode(text);
2513
2637
  if (start && start.nodeType === Node.COMMENT_NODE) {
@@ -2622,6 +2746,7 @@ export {
2622
2746
  spreadAttrs,
2623
2747
  splitProps,
2624
2748
  setupStreaming,
2749
+ setProfilerSink,
2625
2750
  setCurrentScope,
2626
2751
  renderChild,
2627
2752
  render,
@@ -2660,9 +2785,11 @@ export {
2660
2785
  find,
2661
2786
  escapeText,
2662
2787
  escapeAttr,
2788
+ endTurn,
2663
2789
  cssEscape,
2664
2790
  createSignal,
2665
2791
  createRoot,
2792
+ createRecordingSink,
2666
2793
  createPortal,
2667
2794
  createMemo,
2668
2795
  createEffect,
@@ -2670,12 +2797,14 @@ export {
2670
2797
  createContext,
2671
2798
  createComponent,
2672
2799
  cleanupPortalPlaceholder,
2800
+ beginTurn,
2673
2801
  batch,
2674
2802
  applyRestAttrs,
2675
2803
  __slot,
2676
2804
  __bf_swap,
2677
2805
  __bfText,
2678
2806
  __bfSlot,
2807
+ __bfReportOutput,
2679
2808
  $t,
2680
2809
  $c,
2681
2810
  $
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/client",
3
- "version": "0.10.1",
3
+ "version": "0.11.0",
4
4
  "description": "BarefootJS client package: reactive primitives (SSR-safe) plus browser runtime under the `/runtime` subpath (compiler target)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -55,7 +55,7 @@
55
55
  "directory": "packages/client"
56
56
  },
57
57
  "dependencies": {
58
- "@barefootjs/shared": "0.10.1"
58
+ "@barefootjs/shared": "0.11.0"
59
59
  },
60
60
  "peerDependencies": {
61
61
  "@barefootjs/jsx": ">=0.2.0"
package/src/index.ts CHANGED
@@ -11,13 +11,26 @@ export {
11
11
  onMount,
12
12
  untrack,
13
13
  batch,
14
+ setProfilerSink,
15
+ beginTurn,
16
+ endTurn,
17
+ __bfReportOutput,
14
18
  type Reactive,
15
19
  type Signal,
16
20
  type Memo,
17
21
  type CleanupFn,
18
22
  type EffectFn,
23
+ type ProfilerEventSink,
24
+ type SubscriberKind,
19
25
  } from '@barefootjs/client/reactive'
20
26
 
27
+ export {
28
+ createRecordingSink,
29
+ type ProfilerEvent,
30
+ type ProfilerEventType,
31
+ type RecordingSink,
32
+ } from './profiler-events.ts'
33
+
21
34
  export { splitProps } from './split-props.ts'
22
35
 
23
36
  export { __slot, type SlotMarker } from './slot.ts'