@barefootjs/jsx 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.
Files changed (137) hide show
  1. package/dist/compiler.d.ts.map +1 -1
  2. package/dist/debug-profile.d.ts +115 -0
  3. package/dist/debug-profile.d.ts.map +1 -0
  4. package/dist/debug.d.ts +4 -3
  5. package/dist/debug.d.ts.map +1 -1
  6. package/dist/expression-parser.d.ts +31 -0
  7. package/dist/expression-parser.d.ts.map +1 -1
  8. package/dist/index.d.ts +8 -1
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +1872 -207
  11. package/dist/ir-to-client-js/control-flow/plan/branch-loop.d.ts +6 -0
  12. package/dist/ir-to-client-js/control-flow/plan/branch-loop.d.ts.map +1 -1
  13. package/dist/ir-to-client-js/control-flow/plan/build-branch-loop.d.ts +1 -1
  14. package/dist/ir-to-client-js/control-flow/plan/build-branch-loop.d.ts.map +1 -1
  15. package/dist/ir-to-client-js/control-flow/plan/build-component-loop.d.ts +1 -1
  16. package/dist/ir-to-client-js/control-flow/plan/build-component-loop.d.ts.map +1 -1
  17. package/dist/ir-to-client-js/control-flow/plan/build-composite-loop.d.ts +2 -2
  18. package/dist/ir-to-client-js/control-flow/plan/build-composite-loop.d.ts.map +1 -1
  19. package/dist/ir-to-client-js/control-flow/plan/build-event-delegation.d.ts +3 -3
  20. package/dist/ir-to-client-js/control-flow/plan/build-event-delegation.d.ts.map +1 -1
  21. package/dist/ir-to-client-js/control-flow/plan/build-inner-loop.d.ts.map +1 -1
  22. package/dist/ir-to-client-js/control-flow/plan/build-insert.d.ts +2 -0
  23. package/dist/ir-to-client-js/control-flow/plan/build-insert.d.ts.map +1 -1
  24. package/dist/ir-to-client-js/control-flow/plan/build-loop-child-arm.d.ts +2 -0
  25. package/dist/ir-to-client-js/control-flow/plan/build-loop-child-arm.d.ts.map +1 -1
  26. package/dist/ir-to-client-js/control-flow/plan/build-loop.d.ts +4 -2
  27. package/dist/ir-to-client-js/control-flow/plan/build-loop.d.ts.map +1 -1
  28. package/dist/ir-to-client-js/control-flow/plan/build-reactive-effects.d.ts +3 -1
  29. package/dist/ir-to-client-js/control-flow/plan/build-reactive-effects.d.ts.map +1 -1
  30. package/dist/ir-to-client-js/control-flow/plan/event-delegation.d.ts +7 -0
  31. package/dist/ir-to-client-js/control-flow/plan/event-delegation.d.ts.map +1 -1
  32. package/dist/ir-to-client-js/control-flow/plan/inner-loop.d.ts +7 -0
  33. package/dist/ir-to-client-js/control-flow/plan/inner-loop.d.ts.map +1 -1
  34. package/dist/ir-to-client-js/control-flow/plan/insert.d.ts +8 -0
  35. package/dist/ir-to-client-js/control-flow/plan/insert.d.ts.map +1 -1
  36. package/dist/ir-to-client-js/control-flow/plan/loop-child-arm.d.ts +8 -0
  37. package/dist/ir-to-client-js/control-flow/plan/loop-child-arm.d.ts.map +1 -1
  38. package/dist/ir-to-client-js/control-flow/plan/loop.d.ts +28 -0
  39. package/dist/ir-to-client-js/control-flow/plan/loop.d.ts.map +1 -1
  40. package/dist/ir-to-client-js/control-flow/plan/reactive-effects.d.ts +7 -0
  41. package/dist/ir-to-client-js/control-flow/plan/reactive-effects.d.ts.map +1 -1
  42. package/dist/ir-to-client-js/control-flow/stringify/component-loop.d.ts.map +1 -1
  43. package/dist/ir-to-client-js/control-flow/stringify/composite-loop.d.ts.map +1 -1
  44. package/dist/ir-to-client-js/control-flow/stringify/event-delegation.d.ts.map +1 -1
  45. package/dist/ir-to-client-js/control-flow/stringify/event-listener.d.ts +1 -1
  46. package/dist/ir-to-client-js/control-flow/stringify/event-listener.d.ts.map +1 -1
  47. package/dist/ir-to-client-js/control-flow/stringify/inner-loop.d.ts +1 -1
  48. package/dist/ir-to-client-js/control-flow/stringify/inner-loop.d.ts.map +1 -1
  49. package/dist/ir-to-client-js/control-flow/stringify/insert.d.ts.map +1 -1
  50. package/dist/ir-to-client-js/control-flow/stringify/loop-child-arm.d.ts +2 -2
  51. package/dist/ir-to-client-js/control-flow/stringify/loop-child-arm.d.ts.map +1 -1
  52. package/dist/ir-to-client-js/control-flow/stringify/loop.d.ts.map +1 -1
  53. package/dist/ir-to-client-js/control-flow/stringify/reactive-effects.d.ts.map +1 -1
  54. package/dist/ir-to-client-js/control-flow.d.ts.map +1 -1
  55. package/dist/ir-to-client-js/emit-reactive.d.ts.map +1 -1
  56. package/dist/ir-to-client-js/imports.d.ts +2 -2
  57. package/dist/ir-to-client-js/imports.d.ts.map +1 -1
  58. package/dist/ir-to-client-js/index.d.ts +2 -2
  59. package/dist/ir-to-client-js/index.d.ts.map +1 -1
  60. package/dist/ir-to-client-js/phases/effects-and-on-mounts.d.ts.map +1 -1
  61. package/dist/ir-to-client-js/phases/event-handlers.d.ts.map +1 -1
  62. package/dist/ir-to-client-js/plan/build-declaration-emit.d.ts.map +1 -1
  63. package/dist/ir-to-client-js/plan/declaration-emit.d.ts +6 -0
  64. package/dist/ir-to-client-js/plan/declaration-emit.d.ts.map +1 -1
  65. package/dist/ir-to-client-js/types.d.ts +5 -0
  66. package/dist/ir-to-client-js/types.d.ts.map +1 -1
  67. package/dist/ir-to-client-js/utils.d.ts +29 -0
  68. package/dist/ir-to-client-js/utils.d.ts.map +1 -1
  69. package/dist/loop-destructure.d.ts +26 -0
  70. package/dist/loop-destructure.d.ts.map +1 -0
  71. package/dist/profiler.d.ts +492 -0
  72. package/dist/profiler.d.ts.map +1 -0
  73. package/dist/types.d.ts +8 -0
  74. package/dist/types.d.ts.map +1 -1
  75. package/package.json +2 -2
  76. package/src/__tests__/debug-profile.test.ts +405 -0
  77. package/src/__tests__/expression-parser.test.ts +44 -1
  78. package/src/__tests__/profile-bfid-emission.test.ts +63 -0
  79. package/src/__tests__/profile-binding-ids.test.ts +123 -0
  80. package/src/__tests__/profile-cond-binding-ids.test.ts +80 -0
  81. package/src/__tests__/profile-loop-binding-ids.test.ts +106 -0
  82. package/src/__tests__/profile-nested-binding-ids.test.ts +153 -0
  83. package/src/__tests__/profile-turn-markers-branch.test.ts +83 -0
  84. package/src/__tests__/profile-turn-markers-delegation.test.ts +63 -0
  85. package/src/__tests__/profile-turn-markers.test.ts +54 -0
  86. package/src/__tests__/profiler-batch-advisor.test.ts +198 -0
  87. package/src/__tests__/profiler-coverage-conformance.test.ts +360 -0
  88. package/src/__tests__/profiler-e2e.test.ts +104 -0
  89. package/src/__tests__/profiler-hot-subscribers.test.ts +263 -0
  90. package/src/__tests__/profiler-wasted-re-runs.test.ts +147 -0
  91. package/src/__tests__/profiler.test.ts +408 -0
  92. package/src/compiler.ts +3 -0
  93. package/src/debug-profile.ts +543 -0
  94. package/src/debug.ts +192 -28
  95. package/src/expression-parser.ts +53 -0
  96. package/src/index.ts +72 -1
  97. package/src/ir-to-client-js/control-flow/plan/branch-loop.ts +6 -0
  98. package/src/ir-to-client-js/control-flow/plan/build-branch-loop.ts +5 -3
  99. package/src/ir-to-client-js/control-flow/plan/build-component-loop.ts +3 -1
  100. package/src/ir-to-client-js/control-flow/plan/build-composite-loop.ts +8 -2
  101. package/src/ir-to-client-js/control-flow/plan/build-event-delegation.ts +19 -3
  102. package/src/ir-to-client-js/control-flow/plan/build-inner-loop.ts +2 -0
  103. package/src/ir-to-client-js/control-flow/plan/build-insert.ts +9 -2
  104. package/src/ir-to-client-js/control-flow/plan/build-loop-child-arm.ts +9 -1
  105. package/src/ir-to-client-js/control-flow/plan/build-loop.ts +12 -8
  106. package/src/ir-to-client-js/control-flow/plan/build-reactive-effects.ts +10 -4
  107. package/src/ir-to-client-js/control-flow/plan/event-delegation.ts +7 -0
  108. package/src/ir-to-client-js/control-flow/plan/inner-loop.ts +7 -0
  109. package/src/ir-to-client-js/control-flow/plan/insert.ts +8 -0
  110. package/src/ir-to-client-js/control-flow/plan/loop-child-arm.ts +8 -0
  111. package/src/ir-to-client-js/control-flow/plan/loop.ts +28 -0
  112. package/src/ir-to-client-js/control-flow/plan/reactive-effects.ts +7 -0
  113. package/src/ir-to-client-js/control-flow/stringify/branch-loop.ts +5 -3
  114. package/src/ir-to-client-js/control-flow/stringify/component-loop.ts +4 -2
  115. package/src/ir-to-client-js/control-flow/stringify/composite-loop.ts +6 -3
  116. package/src/ir-to-client-js/control-flow/stringify/event-delegation.ts +14 -2
  117. package/src/ir-to-client-js/control-flow/stringify/event-listener.ts +5 -2
  118. package/src/ir-to-client-js/control-flow/stringify/inner-loop.ts +13 -11
  119. package/src/ir-to-client-js/control-flow/stringify/insert.ts +19 -7
  120. package/src/ir-to-client-js/control-flow/stringify/loop-child-arm.ts +18 -13
  121. package/src/ir-to-client-js/control-flow/stringify/loop.ts +9 -7
  122. package/src/ir-to-client-js/control-flow/stringify/reactive-effects.ts +18 -14
  123. package/src/ir-to-client-js/control-flow.ts +12 -6
  124. package/src/ir-to-client-js/emit-reactive.ts +18 -5
  125. package/src/ir-to-client-js/imports.ts +2 -0
  126. package/src/ir-to-client-js/index.ts +6 -1
  127. package/src/ir-to-client-js/phases/effects-and-on-mounts.ts +10 -4
  128. package/src/ir-to-client-js/phases/event-handlers.ts +6 -2
  129. package/src/ir-to-client-js/plan/build-declaration-emit.ts +7 -1
  130. package/src/ir-to-client-js/plan/declaration-emit.ts +6 -0
  131. package/src/ir-to-client-js/stringify/declaration-emit.ts +12 -6
  132. package/src/ir-to-client-js/types.ts +5 -0
  133. package/src/ir-to-client-js/utils.ts +37 -0
  134. package/src/jsx-to-ir.ts +2 -2
  135. package/src/loop-destructure.ts +170 -0
  136. package/src/profiler.ts +1488 -0
  137. package/src/types.ts +8 -0
@@ -16,11 +16,15 @@ import type {
16
16
  ItemLookup,
17
17
  } from './types.ts'
18
18
 
19
- export function buildDynamicLoopDelegationPlan(elem: TopLevelLoop): EventDelegationPlan {
19
+ export function buildDynamicLoopDelegationPlan(
20
+ elem: TopLevelLoop,
21
+ profileComponentName?: string,
22
+ ): EventDelegationPlan {
20
23
  return {
21
24
  kind: 'event-delegation',
22
25
  containerVar: `_${varSlotId(elem.slotId)}`,
23
26
  events: elem.bindings.events,
27
+ profileComponentName,
24
28
  itemLookup: buildKeyedOrIndexLookup({
25
29
  // Chain `.filter()` / `.toSorted()` so the index-based lookup walks
26
30
  // the same array shape mapArray reconciled into the DOM (#1434).
@@ -35,11 +39,19 @@ export function buildDynamicLoopDelegationPlan(elem: TopLevelLoop): EventDelegat
35
39
  }
36
40
  }
37
41
 
38
- export function buildBranchLoopDelegationPlan(loop: BranchLoop, cv: string): EventDelegationPlan {
42
+ // Branch-scoped delegation (a dynamic loop nested inside a conditional branch):
43
+ // `profileComponentName` is threaded from `buildInsertPlan` → `buildBranchLoopPlan`
44
+ // so its delegated handlers get turn markers like every other path (#1786).
45
+ export function buildBranchLoopDelegationPlan(
46
+ loop: BranchLoop,
47
+ cv: string,
48
+ profileComponentName?: string,
49
+ ): EventDelegationPlan {
39
50
  return {
40
51
  kind: 'event-delegation',
41
52
  containerVar: `__loop_${cv}`,
42
53
  events: loop.bindings.events,
54
+ profileComponentName,
43
55
  itemLookup: buildKeyedOrIndexLookup({
44
56
  // See note on `buildDynamicLoopDelegationPlan` above (#1434).
45
57
  array: buildChainedArrayExpr(loop),
@@ -56,11 +68,15 @@ export function buildBranchLoopDelegationPlan(loop: BranchLoop, cv: string): Eve
56
68
  * no `data-key` markers, so the lookup walks up to the container's direct
57
69
  * child and uses `indexOf` (with optional sibling offset).
58
70
  */
59
- export function buildStaticArrayDelegationPlan(elem: TopLevelLoop): EventDelegationPlan {
71
+ export function buildStaticArrayDelegationPlan(
72
+ elem: TopLevelLoop,
73
+ profileComponentName?: string,
74
+ ): EventDelegationPlan {
60
75
  return {
61
76
  kind: 'event-delegation',
62
77
  containerVar: `_${varSlotId(elem.slotId)}`,
63
78
  events: elem.bindings.events,
79
+ profileComponentName,
64
80
  itemLookup: {
65
81
  kind: 'static-index',
66
82
  // Static arrays render through the same `.filter()`/`.toSorted()`
@@ -131,6 +131,8 @@ export function buildInnerLoopsPlan(args: BuildInnerLoopsArgs): InnerLoopsPlan {
131
131
  plan.push({
132
132
  uidSuffix,
133
133
  markerId: inner.markerId,
134
+ // The loop node shares its container element's slot (#1795 Phase 3).
135
+ slotId: inner.containerSlotId ?? '?',
134
136
  containerExpr,
135
137
  arrayExpr,
136
138
  arraySrc: inner.array,
@@ -24,6 +24,8 @@ import type {
24
24
  export interface BuildInsertOptions {
25
25
  scope: ScopeRef
26
26
  eventNameMode: 'dom' | 'raw'
27
+ /** Owning component name in profile mode (#1690, SR3) — else undefined. */
28
+ profileComponentName?: string
27
29
  }
28
30
 
29
31
  export function buildInsertPlan(
@@ -36,6 +38,7 @@ export function buildInsertPlan(
36
38
  slotId: elem.slotId,
37
39
  condition: elem.condition,
38
40
  eventNameMode: options.eventNameMode,
41
+ profileComponentName: options.profileComponentName,
39
42
  arms: [
40
43
  buildArm(elem.whenTrueHtml, elem.slotId, elem.whenTrue, options),
41
44
  buildArm(elem.whenFalseHtml, elem.slotId, elem.whenFalse, options),
@@ -56,11 +59,15 @@ function buildArm(
56
59
  }
57
60
 
58
61
  function buildArmBody(branch: BranchSummary, options: BuildInsertOptions): ArmBody {
62
+ const pc = options.profileComponentName
59
63
  return {
60
64
  events: branch.events.map(e => ({
61
65
  slotId: e.slotId,
62
66
  eventName: e.eventName,
63
67
  handler: e.handler,
68
+ // Profile mode (#1690, SR3): turn id so the arm listener is wrapped with
69
+ // beginTurn/endTurn, matching the top-level/delegation paths.
70
+ turnId: pc ? `${pc}#handler:${e.slotId}:${e.eventName}` : undefined,
64
71
  })),
65
72
  refs: branch.refs.map(r => ({
66
73
  slotId: r.slotId,
@@ -81,13 +88,13 @@ function buildArmBody(branch: BranchSummary, options: BuildInsertOptions): ArmBo
81
88
  expression: t.expression,
82
89
  })),
83
90
  // Branch-scoped loops, fully Plan-built (Item 2 final migration).
84
- loops: branch.loops.map(buildBranchLoopPlan),
91
+ loops: branch.loops.map(l => buildBranchLoopPlan(l, pc)),
85
92
  // Nested conditionals are themselves InsertPlans — built recursively so
86
93
  // the same stringifier handles arbitrary depth. Their scope is always
87
94
  // `__branchScope` (the parent arm's bindEvents argument), regardless of
88
95
  // the outer scope; only the eventNameMode is inherited.
89
96
  conditionals: branch.conditionals.map(c =>
90
- buildInsertPlan(c, { scope: { kind: 'branchScope' }, eventNameMode: options.eventNameMode }),
97
+ buildInsertPlan(c, { scope: { kind: 'branchScope' }, eventNameMode: options.eventNameMode, profileComponentName: pc }),
91
98
  ),
92
99
  }
93
100
  }
@@ -76,6 +76,8 @@ export interface BuildBranchEventBindingsArgs {
76
76
  events: readonly ConditionalBranchEvent[] | undefined
77
77
  /** Loop-param wrap closure. Identity (`x => x`) when no loop param applies. */
78
78
  wrap: (expr: string) => string
79
+ /** Owning component name in profile mode (#1690, SR3) — else undefined. */
80
+ profileComponentName?: string
79
81
  }
80
82
 
81
83
  /**
@@ -87,7 +89,7 @@ export interface BuildBranchEventBindingsArgs {
87
89
  export function buildBranchEventBindingsPlan(
88
90
  args: BuildBranchEventBindingsArgs,
89
91
  ): BranchEventBindingsPlan {
90
- const { events, wrap } = args
92
+ const { events, wrap, profileComponentName: pc } = args
91
93
  if (!events || events.length === 0) return []
92
94
 
93
95
  const eventsBySlot = new Map<string, BranchEventListener[]>()
@@ -100,6 +102,9 @@ export function buildBranchEventBindingsPlan(
100
102
  bucket.push({
101
103
  eventName: ev.eventName,
102
104
  wrappedHandler: wrap(ev.handler),
105
+ // Profile mode (#1690, SR3): turn id so the arm listener is wrapped with
106
+ // beginTurn/endTurn, matching every other handler path.
107
+ turnId: pc ? `${pc}#handler:${ev.slotId}:${ev.eventName}` : undefined,
103
108
  })
104
109
  }
105
110
 
@@ -273,6 +278,9 @@ export function buildBranchInnerLoopsPlan(
273
278
  plan.push({
274
279
  uidSuffix: `br_${i}`,
275
280
  markerId: inner.markerId,
281
+ // The loop node shares its container element's slot, so `containerSlotId`
282
+ // is the IR slot the analyzer's `loop` domBinding uses (#1795 Phase 3).
283
+ slotId: csl ?? '?',
276
284
  containerExpr,
277
285
  arrayExpr: wrapOuter(inner.array),
278
286
  keyFn: loopKeyFn(inner),
@@ -50,6 +50,8 @@ import type {
50
50
  export interface BuildLoopPlanOptions {
51
51
  /** Local names whose CSR-time substitution forces the static-array self-heal path (#1247). */
52
52
  unsafeLocalNames: Set<string>
53
+ /** Owning component name when compiling in profile mode (#1690) — else undefined. */
54
+ profileComponentName?: string
53
55
  }
54
56
 
55
57
  /**
@@ -64,24 +66,24 @@ export function buildLoopPlan(elem: TopLevelLoop, opts: BuildLoopPlanOptions): L
64
66
  // array's per-item conditional still toggles reactively instead of freezing
65
67
  // in the SSR-time `forEach` (which has no conditional handling at all).
66
68
  if (elem.bodyIsItemConditional) {
67
- return buildPlainLoopPlan(elem)
69
+ return buildPlainLoopPlan(elem, opts.profileComponentName)
68
70
  }
69
71
  if (elem.isStaticArray) {
70
- return buildStaticLoopPlan(elem, opts.unsafeLocalNames)
72
+ return buildStaticLoopPlan(elem, opts.unsafeLocalNames, opts.profileComponentName)
71
73
  }
72
74
  const hasInnerStructure = (elem.nestedComponents?.length ?? 0) > 0
73
75
  || (elem.innerLoops?.length ?? 0) > 0
74
76
  if (elem.useElementReconciliation && hasInnerStructure) {
75
- return buildTopLevelCompositePlan(elem)
77
+ return buildTopLevelCompositePlan(elem, opts.profileComponentName)
76
78
  }
77
79
  if (elem.childComponent) {
78
- return buildComponentLoopPlan(elem)
80
+ return buildComponentLoopPlan(elem, opts.profileComponentName)
79
81
  }
80
- return buildPlainLoopPlan(elem)
82
+ return buildPlainLoopPlan(elem, opts.profileComponentName)
81
83
  }
82
84
 
83
85
  /** @internal — prefer `buildLoopPlan`. Exported for the branch-loop wrapper only. */
84
- export function buildPlainLoopPlan(elem: TopLevelLoop): PlainLoopPlan {
86
+ export function buildPlainLoopPlan(elem: TopLevelLoop, profileComponentName?: string): PlainLoopPlan {
85
87
  const wrap = (expr: string) => wrapLoopParamAsAccessor(expr, elem.param, elem.paramBindings)
86
88
  const { head: paramHead, unwrap: paramUnwrap } = destructureLoopParam(elem.param, elem.paramBindings)
87
89
  const hasReactive = elem.bindings.reactiveAttrs.length > 0
@@ -92,6 +94,7 @@ export function buildPlainLoopPlan(elem: TopLevelLoop): PlainLoopPlan {
92
94
  kind: 'plain',
93
95
  containerVar: `_${varSlotId(elem.slotId)}`,
94
96
  markerId: elem.markerId,
97
+ profileLoopId: profileComponentName ? `${profileComponentName}#binding:${elem.slotId}` : undefined,
95
98
  arrayExpr: buildChainedArrayExpr(elem),
96
99
  keyFn: loopKeyFn(elem),
97
100
  paramHead,
@@ -99,7 +102,7 @@ export function buildPlainLoopPlan(elem: TopLevelLoop): PlainLoopPlan {
99
102
  indexParam: elem.index || '__idx',
100
103
  mapPreambleWrapped: elem.mapPreamble ? wrap(elem.mapPreamble) : '',
101
104
  template: elem.template,
102
- reactiveEffects: hasReactive ? buildLoopReactiveEffectsPlan(elem) : null,
105
+ reactiveEffects: hasReactive ? buildLoopReactiveEffectsPlan(elem, profileComponentName) : null,
103
106
  childRefs: buildChildRefBindings(elem.bindings.refs, elem.param, elem.paramBindings),
104
107
  bodyIsMultiRoot: elem.bodyIsMultiRoot ?? false,
105
108
  anchored: elem.bodyIsItemConditional ?? false,
@@ -114,7 +117,7 @@ export function buildPlainLoopPlan(elem: TopLevelLoop): PlainLoopPlan {
114
117
  }
115
118
 
116
119
  /** @internal — prefer `buildLoopPlan`. */
117
- export function buildStaticLoopPlan(elem: TopLevelLoop, unsafeLocalNames: Set<string>): StaticLoopPlan {
120
+ export function buildStaticLoopPlan(elem: TopLevelLoop, unsafeLocalNames: Set<string>, profileComponentName?: string): StaticLoopPlan {
118
121
  // Group reactive attrs by their child slot id, preserving the legacy
119
122
  // declaration-order Map-iteration semantics.
120
123
  const attrsBySlotMap = new Map<string, LoopChildReactiveAttr[]>()
@@ -139,6 +142,7 @@ export function buildStaticLoopPlan(elem: TopLevelLoop, unsafeLocalNames: Set<st
139
142
  param: elem.param,
140
143
  indexParam,
141
144
  childIndexExpr,
145
+ profileComponentName,
142
146
  attrsBySlot: [...attrsBySlotMap].map(([slotId, attrs]) => [slotId, attrs] as const),
143
147
  texts: elem.bindings.reactiveTexts,
144
148
  // Static path: forEach binds `param` as the raw value. Passing through
@@ -50,13 +50,15 @@ export interface BuildReactiveEffectsArgs {
50
50
  conditionals: readonly LoopChildConditional[] | undefined
51
51
  loopParam: string
52
52
  loopParamBindings?: readonly LoopParamBinding[]
53
+ /** Owning component name in profile mode (#1690, SR3) — else undefined. */
54
+ profileComponentName?: string
53
55
  }
54
56
 
55
57
  /** Build a fully-resolved `ReactiveEffectsPlan` from the IR slice. */
56
58
  export function buildReactiveEffectsPlan(
57
59
  args: BuildReactiveEffectsArgs,
58
60
  ): ReactiveEffectsPlan {
59
- const { attrs, texts, conditionals, loopParam, loopParamBindings } = args
61
+ const { attrs, texts, conditionals, loopParam, loopParamBindings, profileComponentName } = args
60
62
  const wrap = (expr: string) => wrapLoopParamAsAccessor(expr, loopParam, loopParamBindings)
61
63
 
62
64
  // 1. Group attrs by slot, preserving declaration order (Map insertion order
@@ -131,8 +133,8 @@ export function buildReactiveEffectsPlan(
131
133
  wrappedCondition: wrap(cond.condition),
132
134
  whenTrueTemplateHtml: addCondAttrToTemplate(wrap(cond.whenTrueHtml), cond.slotId),
133
135
  whenFalseTemplateHtml: addCondAttrToTemplate(wrap(cond.whenFalseHtml), cond.slotId),
134
- whenTrueArm: buildOuterArm(cond.whenTrue, trueTexts, wrap, loopParam, loopParamBindings),
135
- whenFalseArm: buildOuterArm(cond.whenFalse, falseTexts, wrap, loopParam, loopParamBindings),
136
+ whenTrueArm: buildOuterArm(cond.whenTrue, trueTexts, wrap, loopParam, loopParamBindings, profileComponentName),
137
+ whenFalseArm: buildOuterArm(cond.whenFalse, falseTexts, wrap, loopParam, loopParamBindings, profileComponentName),
136
138
  })
137
139
  }
138
140
  }
@@ -141,6 +143,7 @@ export function buildReactiveEffectsPlan(
141
143
  attrSlots,
142
144
  outerTexts,
143
145
  conditionals: conditionalPlans,
146
+ profileComponentName,
144
147
  }
145
148
  }
146
149
 
@@ -150,11 +153,13 @@ function buildOuterArm(
150
153
  wrap: (expr: string) => string,
151
154
  loopParam: string,
152
155
  loopParamBindings: readonly LoopParamBinding[] | undefined,
156
+ profileComponentName?: string,
153
157
  ): LoopChildArmPlan {
154
158
  return {
155
159
  events: buildBranchEventBindingsPlan({
156
160
  events: branch.events,
157
161
  wrap,
162
+ profileComponentName,
158
163
  }),
159
164
  childComponents: buildBranchChildComponentInitsPlan({
160
165
  components: branch.childComponents,
@@ -182,12 +187,13 @@ function buildOuterArm(
182
187
  * Convenience: build a ReactiveEffectsPlan directly from a `TopLevelLoop`,
183
188
  * pulling the same IR slice the legacy emitter consumed.
184
189
  */
185
- export function buildLoopReactiveEffectsPlan(elem: TopLevelLoop): ReactiveEffectsPlan {
190
+ export function buildLoopReactiveEffectsPlan(elem: TopLevelLoop, profileComponentName?: string): ReactiveEffectsPlan {
186
191
  return buildReactiveEffectsPlan({
187
192
  attrs: elem.bindings.reactiveAttrs,
188
193
  texts: elem.bindings.reactiveTexts,
189
194
  conditionals: elem.bindings.conditionals,
190
195
  loopParam: elem.param,
191
196
  loopParamBindings: elem.paramBindings,
197
+ profileComponentName,
192
198
  })
193
199
  }
@@ -26,6 +26,13 @@ export interface EventDelegationPlan {
26
26
  containerVar: string
27
27
  events: LoopChildEvent[]
28
28
  itemLookup: ItemLookup
29
+ /**
30
+ * Profile mode (#1690, SR3): the owning component name. When set, the
31
+ * stringifier brackets each delegated handler call with `beginTurn`/`endTurn`
32
+ * (id `<Component>#handler:<childSlotId>:<eventName>`). Undefined when
33
+ * profiling is off, so the emitted dispatcher is unchanged (SR8).
34
+ */
35
+ profileComponentName?: string
29
36
  }
30
37
 
31
38
  /**
@@ -83,6 +83,13 @@ export interface InnerLoopPlan {
83
83
  uidSuffix: string
84
84
  /** Loop marker id — passed to mapArray so sibling loops disambiguate (#1087). */
85
85
  markerId: string
86
+ /**
87
+ * IR slot id for this inner loop (#1690, #1795 Phase 3). The loop node shares
88
+ * its container element's slot, so this is `containerSlotId`. Used to emit the
89
+ * `<Component>#binding:<slotId>` profile id on the inner `mapArray`; resolves
90
+ * via the `loop` `domBinding`. `'?'` when the IR carried no slot.
91
+ */
92
+ slotId: string
86
93
  /** Container resolution expression — already includes scope / selectors. */
87
94
  containerExpr: string
88
95
  /** Array expression as wrapped (reactive) or as written in source (static). */
@@ -29,6 +29,12 @@ export interface InsertPlan {
29
29
  * keeps the original event name (used by `@client` conditionals).
30
30
  */
31
31
  eventNameMode: 'dom' | 'raw'
32
+ /**
33
+ * Owning component name in profile mode (#1690, SR4). When set, the
34
+ * conditional's `insert()` effect and its branch binding effects carry a
35
+ * `<Component>#binding:<slotId>` id so the profiler attributes them.
36
+ */
37
+ profileComponentName?: string
32
38
  }
33
39
 
34
40
  /** A single branch arm of an insert(). */
@@ -79,6 +85,8 @@ export interface ArmEventBind {
79
85
  eventName: string
80
86
  /** Handler source expression (already trimmed). The stringifier wraps it. */
81
87
  handler: string
88
+ /** Profile-mode turn id (#1690, SR3); when set the listener is turn-wrapped. */
89
+ turnId?: string
82
90
  }
83
91
 
84
92
  export interface ArmRefBind {
@@ -20,6 +20,8 @@ export interface BranchEventListener {
20
20
  eventName: string
21
21
  /** Already wrapped via wrapLoopParamAsAccessor at build time. */
22
22
  wrappedHandler: string
23
+ /** Profile-mode turn id (#1690, SR3); when set the listener is turn-wrapped. */
24
+ turnId?: string
23
25
  }
24
26
 
25
27
  /** Listeners grouped by slot (one qsa() lookup per slot). */
@@ -90,6 +92,12 @@ export interface BranchInnerLoop {
90
92
  uidSuffix: string
91
93
  /** Loop marker id — passed to mapArray so sibling loops disambiguate (#1087). */
92
94
  markerId: string
95
+ /**
96
+ * IR slot id for the inner loop (#1690, #1795 Phase 3). Used to emit the
97
+ * `<Component>#binding:<slotId>` profile id on the inner `mapArray`; resolves
98
+ * via the `loop` `domBinding`. `'?'` when the IR carried no slot.
99
+ */
100
+ slotId: string
93
101
  /** Container resolution expression — already includes scope and selectors. */
94
102
  containerExpr: string
95
103
  /** Wrapped array expression (`wrapOuter(inner.array)`). */
@@ -50,6 +50,13 @@ interface LoopPlanCommon {
50
50
  interface DynamicLoopCommon extends LoopPlanCommon {
51
51
  /** Loop marker id — passed to mapArray so sibling loops disambiguate (#1087). */
52
52
  markerId: string
53
+ /**
54
+ * Profile mode (#1690, SR4): the loop's binding id
55
+ * (`<Component>#binding:<slotId>`), passed to `mapArray` as the `bfId` so its
56
+ * internal reconcile effect — typically the costliest subscriber in a list —
57
+ * is attributed to source. Undefined when profiling is off.
58
+ */
59
+ profileLoopId?: string
53
60
  /** Key function source — `null` when the loop has no explicit key. */
54
61
  keyFn: string
55
62
  /** renderItem param identifier (after destructure unwrap rename). */
@@ -134,6 +141,11 @@ interface ComponentLoopVariant extends DynamicLoopCommon {
134
141
  nestedComps: NestedComponentInit[]
135
142
  /** Reactive-effects plan for `childConditionals` inside the loop body. */
136
143
  childConditionalEffects: ReactiveEffectsPlan | null
144
+ /**
145
+ * Profile-mode loop id (#1690, #1795 Phase 3) for the component loop's
146
+ * `mapArray`. Undefined off → byte-identical (SR8).
147
+ */
148
+ profileLoopId?: string
137
149
  }
138
150
 
139
151
  /**
@@ -177,6 +189,16 @@ interface CompositeLoopVariant extends DynamicLoopCommon {
177
189
  * `qsa(__el, ...)` → `qsaItem(__el, ...)` for reactive lookups (#1212).
178
190
  */
179
191
  bodyIsMultiRoot: boolean
192
+ /**
193
+ * Profile-mode loop id (#1690, #1795 Phase 3): `<Component>#binding:<slotId>`
194
+ * for the composite `mapArray`. Undefined off → byte-identical (SR8).
195
+ */
196
+ profileLoopId?: string
197
+ /**
198
+ * Owning component name in profile mode — threaded to `stringifyInnerLoops`
199
+ * so per-item inner-loop / text / attribute effects carry binding ids.
200
+ */
201
+ profileComponentName?: string
180
202
  }
181
203
 
182
204
  /**
@@ -201,6 +223,12 @@ interface StaticLoopVariant extends LoopPlanCommon {
201
223
  * Null for the SSR-then-hydrate-only common case.
202
224
  */
203
225
  csrMaterialize: StaticLoopMaterializePlan | null
226
+ /**
227
+ * Owning component name in profile mode (#1690, #1795 Phase 3). When set,
228
+ * each static-loop child attribute / text `createEffect` carries a
229
+ * `<Component>#binding:<slotId>` id. Undefined off → byte-identical (SR8).
230
+ */
231
+ profileComponentName?: string
204
232
  }
205
233
 
206
234
  /**
@@ -62,4 +62,11 @@ export interface ReactiveEffectsPlan {
62
62
  /** Text effects scoped to the outer renderItem (not inside any conditional). */
63
63
  outerTexts: readonly ReactiveTextEffect[]
64
64
  conditionals: readonly NestedConditionalPlan[]
65
+ /**
66
+ * Owning component name in profile mode (#1690, SR4, #1795 Phase 2). When
67
+ * set, each loop-child attribute / outer-text `createEffect` carries a
68
+ * `<Component>#binding:<slotId>` id so the profiler attributes its per-item
69
+ * re-runs to source. Undefined off → byte-identical (SR8).
70
+ */
71
+ profileComponentName?: string
65
72
  }
@@ -57,8 +57,10 @@ function emitPlain(lines: string[], plan: BranchPlainLoopPlan): void {
57
57
  eventDelegation,
58
58
  childRefs,
59
59
  bodyIsMultiRoot,
60
+ profileLoopId,
60
61
  } = plan
61
62
 
63
+ const loopBfId = profileLoopId ? `, ${JSON.stringify(profileLoopId)}` : ''
62
64
  const unwrapInline = paramUnwrap ? `${paramUnwrap} ` : ''
63
65
 
64
66
  // Wrap the mapArray() in a disposable effect so the inner createEffect
@@ -73,9 +75,9 @@ function emitPlain(lines: string[], plan: BranchPlainLoopPlan): void {
73
75
  // Simple case: single-line renderItem (single root, no reactive effects).
74
76
  const cloneExpr = emitTemplateCloneInline(template)
75
77
  if (mapPreambleWrapped) {
76
- lines.push(` if (${containerVar}) mapArray(() => ${arrayExpr}, ${containerVar}, ${keyFn}, (${paramHead}, ${indexParam}, __existing) => { ${unwrapInline}if (__existing) return __existing; ${mapPreambleWrapped}; ${cloneExpr} }, '${markerId}')`)
78
+ lines.push(` if (${containerVar}) mapArray(() => ${arrayExpr}, ${containerVar}, ${keyFn}, (${paramHead}, ${indexParam}, __existing) => { ${unwrapInline}if (__existing) return __existing; ${mapPreambleWrapped}; ${cloneExpr} }, '${markerId}'${loopBfId})`)
77
79
  } else {
78
- lines.push(` if (${containerVar}) mapArray(() => ${arrayExpr}, ${containerVar}, ${keyFn}, (${paramHead}, ${indexParam}, __existing) => { ${unwrapInline}if (__existing) return __existing; ${cloneExpr} }, '${markerId}')`)
80
+ lines.push(` if (${containerVar}) mapArray(() => ${arrayExpr}, ${containerVar}, ${keyFn}, (${paramHead}, ${indexParam}, __existing) => { ${unwrapInline}if (__existing) return __existing; ${cloneExpr} }, '${markerId}'${loopBfId})`)
79
81
  }
80
82
  } else {
81
83
  // Multi-line renderItem (reactive effects and/or multi-root and/or refs).
@@ -97,7 +99,7 @@ function emitPlain(lines: string[], plan: BranchPlainLoopPlan): void {
97
99
  }
98
100
  emitLoopChildRefs(lines, childRefs, { indent: ' ', elVar: '__el', bodyIsMultiRoot })
99
101
  lines.push(` return __el`)
100
- lines.push(` }, '${markerId}')`)
102
+ lines.push(` }, '${markerId}'${loopBfId})`)
101
103
  }
102
104
  lines.push(` }))`)
103
105
 
@@ -48,8 +48,10 @@ export function stringifyComponentLoop(lines: string[], plan: ComponentLoopPlan)
48
48
  keyExpr,
49
49
  nestedComps,
50
50
  childConditionalEffects,
51
+ profileLoopId,
51
52
  } = plan
52
53
 
54
+ const loopBfId = profileLoopId ? `, ${JSON.stringify(profileLoopId)}` : ''
53
55
  lines.push(` mapArray(() => ${arrayExpr}, ${containerVar}, ${keyFn}, (${paramHead}, ${indexParam}, __existing) => {`)
54
56
  if (paramUnwrap) lines.push(` ${paramUnwrap}`)
55
57
 
@@ -58,7 +60,7 @@ export function stringifyComponentLoop(lines: string[], plan: ComponentLoopPlan)
58
60
  if (nestedComps.length === 0) {
59
61
  lines.push(` if (__existing) { initChild('${scopedComp}', __existing, ${componentPropsExpr}); return __existing }`)
60
62
  lines.push(` return createComponent('${scopedComp}', ${componentPropsExpr}, ${keyExpr})`)
61
- lines.push(` }, '${markerId}')`)
63
+ lines.push(` }, '${markerId}'${loopBfId})`)
62
64
  return
63
65
  }
64
66
 
@@ -79,7 +81,7 @@ export function stringifyComponentLoop(lines: string[], plan: ComponentLoopPlan)
79
81
  stringifyReactiveEffects(lines, childConditionalEffects, { indent: ' ', elVar: '__csrEl' })
80
82
  }
81
83
  lines.push(` return __csrEl`)
82
- lines.push(` }, '${markerId}')`)
84
+ lines.push(` }, '${markerId}'${loopBfId})`)
83
85
  }
84
86
 
85
87
  function emitNestedInit(lines: string[], indent: string, parentVar: string, nc: NestedComponentInit): void {
@@ -59,6 +59,8 @@ export function stringifyCompositeLoop(lines: string[], plan: CompositeLoopPlan)
59
59
  topIndent,
60
60
  bodyIndent: rawBodyIndent,
61
61
  bodyIsMultiRoot,
62
+ profileComponentName: pc,
63
+ profileLoopId,
62
64
  } = plan
63
65
 
64
66
  // When wrapping the mapArray in createDisposableEffect (branch case), the
@@ -120,7 +122,7 @@ export function stringifyCompositeLoop(lines: string[], plan: CompositeLoopPlan)
120
122
  })
121
123
  emitComponentAndEventSetup(lines, bodyIndent, '__el', compsArr, eventsArr, loopParam, loopParamBindings, bodyIsMultiRoot)
122
124
  if (innerLoops.length > 0) {
123
- stringifyInnerLoops(lines, innerLoops, bodyIndent)
125
+ stringifyInnerLoops(lines, innerLoops, bodyIndent, pc)
124
126
  }
125
127
 
126
128
  if (reactiveEffects) {
@@ -130,11 +132,12 @@ export function stringifyCompositeLoop(lines: string[], plan: CompositeLoopPlan)
130
132
  emitLoopChildRefs(lines, childRefs, { indent: bodyIndent, elVar: '__el', bodyIsMultiRoot })
131
133
 
132
134
  lines.push(`${bodyIndent}return __el`)
135
+ const loopBfId = profileLoopId ? `, ${JSON.stringify(profileLoopId)}` : ''
133
136
  if (branchClearChildren) {
134
137
  // Close inner mapArray + createDisposableEffect wrapper.
135
- lines.push(`${mapArrayIndent}}, '${markerId}')`)
138
+ lines.push(`${mapArrayIndent}}, '${markerId}'${loopBfId})`)
136
139
  lines.push(`${topIndent}}))`)
137
140
  } else {
138
- lines.push(`${topIndent}}, '${markerId}')`)
141
+ lines.push(`${topIndent}}, '${markerId}'${loopBfId})`)
139
142
  }
140
143
  }
@@ -48,8 +48,20 @@ const NON_BUBBLING_EVENTS = new Set([
48
48
  'pointerenter', 'pointerleave',
49
49
  ])
50
50
 
51
+ /**
52
+ * Profile mode (#1690, SR3): bracket a delegated handler call with turn
53
+ * markers so a profiling run attributes the reactive work it triggers to one
54
+ * turn. `call` is the inline invocation (e.g. `(handler)(__bfEvt)`); the
55
+ * wrapper keeps it a single statement so it drops into every lookup shape.
56
+ */
57
+ function withTurn(call: string, componentName: string | undefined, childSlotId: string, eventName: string): string {
58
+ if (!componentName) return call
59
+ const id = JSON.stringify(`${componentName}#handler:${childSlotId}:${eventName}`)
60
+ return `beginTurn(${id}); try { ${call} } finally { endTurn() }`
61
+ }
62
+
51
63
  export function stringifyEventDelegation(lines: string[], plan: EventDelegationPlan): void {
52
- const { containerVar, events, itemLookup } = plan
64
+ const { containerVar, events, itemLookup, profileComponentName } = plan
53
65
  const eventsByName = new Map<string, LoopChildEvent[]>()
54
66
  for (const ev of events) {
55
67
  if (!eventsByName.has(ev.eventName)) eventsByName.set(ev.eventName, [])
@@ -70,7 +82,7 @@ export function stringifyEventDelegation(lines: string[], plan: EventDelegationP
70
82
  const childVar = varSlotId(ev.childSlotId)
71
83
  lines.push(` const ${childVar}El = target.closest('[bf="${ev.childSlotId}"]')`)
72
84
  lines.push(` if (${childVar}El) {`)
73
- const handlerCall = `(${ev.handler.trim()})(__bfEvt)`
85
+ const handlerCall = withTurn(`(${ev.handler.trim()})(__bfEvt)`, profileComponentName, ev.childSlotId, ev.eventName)
74
86
  switch (itemLookup.kind) {
75
87
  case 'keyed':
76
88
  emitKeyedLookup(lines, ev, handlerCall, itemLookup)
@@ -13,7 +13,7 @@
13
13
  * by `@client` conditionals).
14
14
  */
15
15
 
16
- import { toDomEventName, wrapHandlerInBlock } from '../../utils.ts'
16
+ import { toDomEventName, wrapHandlerInBlock, wrapHandlerForTurn } from '../../utils.ts'
17
17
 
18
18
  export type EventNameMode = 'dom' | 'raw'
19
19
 
@@ -31,8 +31,11 @@ export function emitListenerLine(
31
31
  eventName: string,
32
32
  handler: string,
33
33
  mode: EventNameMode = 'dom',
34
+ turnId?: string,
34
35
  ): void {
35
- const wrapped = wrapHandlerInBlock(handler)
36
+ // Profile mode (#1690, SR3): when a turn id is supplied the handler is
37
+ // bracketed with beginTurn/endTurn instead of the plain block wrap.
38
+ const wrapped = turnId ? wrapHandlerForTurn(handler, turnId) : wrapHandlerInBlock(handler)
36
39
  const name = mode === 'dom' ? toDomEventName(eventName) : eventName
37
40
  lines.push(`${indent}if (${elementVar}) ${elementVar}.addEventListener('${name}', ${wrapped})`)
38
41
  }