@barefootjs/jsx 0.10.0 → 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 (140) 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 +1905 -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/ssr-defaults.d.ts.map +1 -1
  74. package/dist/types.d.ts +8 -0
  75. package/dist/types.d.ts.map +1 -1
  76. package/package.json +2 -2
  77. package/src/__tests__/debug-profile.test.ts +405 -0
  78. package/src/__tests__/expression-parser.test.ts +44 -1
  79. package/src/__tests__/profile-bfid-emission.test.ts +63 -0
  80. package/src/__tests__/profile-binding-ids.test.ts +123 -0
  81. package/src/__tests__/profile-cond-binding-ids.test.ts +80 -0
  82. package/src/__tests__/profile-loop-binding-ids.test.ts +106 -0
  83. package/src/__tests__/profile-nested-binding-ids.test.ts +153 -0
  84. package/src/__tests__/profile-turn-markers-branch.test.ts +83 -0
  85. package/src/__tests__/profile-turn-markers-delegation.test.ts +63 -0
  86. package/src/__tests__/profile-turn-markers.test.ts +54 -0
  87. package/src/__tests__/profiler-batch-advisor.test.ts +198 -0
  88. package/src/__tests__/profiler-coverage-conformance.test.ts +360 -0
  89. package/src/__tests__/profiler-e2e.test.ts +104 -0
  90. package/src/__tests__/profiler-hot-subscribers.test.ts +263 -0
  91. package/src/__tests__/profiler-wasted-re-runs.test.ts +147 -0
  92. package/src/__tests__/profiler.test.ts +408 -0
  93. package/src/__tests__/ssr-defaults.test.ts +24 -0
  94. package/src/compiler.ts +3 -0
  95. package/src/debug-profile.ts +543 -0
  96. package/src/debug.ts +192 -28
  97. package/src/expression-parser.ts +53 -0
  98. package/src/index.ts +72 -1
  99. package/src/ir-to-client-js/control-flow/plan/branch-loop.ts +6 -0
  100. package/src/ir-to-client-js/control-flow/plan/build-branch-loop.ts +5 -3
  101. package/src/ir-to-client-js/control-flow/plan/build-component-loop.ts +3 -1
  102. package/src/ir-to-client-js/control-flow/plan/build-composite-loop.ts +8 -2
  103. package/src/ir-to-client-js/control-flow/plan/build-event-delegation.ts +19 -3
  104. package/src/ir-to-client-js/control-flow/plan/build-inner-loop.ts +2 -0
  105. package/src/ir-to-client-js/control-flow/plan/build-insert.ts +9 -2
  106. package/src/ir-to-client-js/control-flow/plan/build-loop-child-arm.ts +9 -1
  107. package/src/ir-to-client-js/control-flow/plan/build-loop.ts +12 -8
  108. package/src/ir-to-client-js/control-flow/plan/build-reactive-effects.ts +10 -4
  109. package/src/ir-to-client-js/control-flow/plan/event-delegation.ts +7 -0
  110. package/src/ir-to-client-js/control-flow/plan/inner-loop.ts +7 -0
  111. package/src/ir-to-client-js/control-flow/plan/insert.ts +8 -0
  112. package/src/ir-to-client-js/control-flow/plan/loop-child-arm.ts +8 -0
  113. package/src/ir-to-client-js/control-flow/plan/loop.ts +28 -0
  114. package/src/ir-to-client-js/control-flow/plan/reactive-effects.ts +7 -0
  115. package/src/ir-to-client-js/control-flow/stringify/branch-loop.ts +5 -3
  116. package/src/ir-to-client-js/control-flow/stringify/component-loop.ts +4 -2
  117. package/src/ir-to-client-js/control-flow/stringify/composite-loop.ts +6 -3
  118. package/src/ir-to-client-js/control-flow/stringify/event-delegation.ts +14 -2
  119. package/src/ir-to-client-js/control-flow/stringify/event-listener.ts +5 -2
  120. package/src/ir-to-client-js/control-flow/stringify/inner-loop.ts +13 -11
  121. package/src/ir-to-client-js/control-flow/stringify/insert.ts +19 -7
  122. package/src/ir-to-client-js/control-flow/stringify/loop-child-arm.ts +18 -13
  123. package/src/ir-to-client-js/control-flow/stringify/loop.ts +9 -7
  124. package/src/ir-to-client-js/control-flow/stringify/reactive-effects.ts +18 -14
  125. package/src/ir-to-client-js/control-flow.ts +12 -6
  126. package/src/ir-to-client-js/emit-reactive.ts +18 -5
  127. package/src/ir-to-client-js/imports.ts +2 -0
  128. package/src/ir-to-client-js/index.ts +6 -1
  129. package/src/ir-to-client-js/phases/effects-and-on-mounts.ts +10 -4
  130. package/src/ir-to-client-js/phases/event-handlers.ts +6 -2
  131. package/src/ir-to-client-js/plan/build-declaration-emit.ts +7 -1
  132. package/src/ir-to-client-js/plan/declaration-emit.ts +6 -0
  133. package/src/ir-to-client-js/stringify/declaration-emit.ts +12 -6
  134. package/src/ir-to-client-js/types.ts +5 -0
  135. package/src/ir-to-client-js/utils.ts +37 -0
  136. package/src/jsx-to-ir.ts +2 -2
  137. package/src/loop-destructure.ts +170 -0
  138. package/src/profiler.ts +1488 -0
  139. package/src/ssr-defaults.ts +65 -0
  140. package/src/types.ts +8 -0
@@ -30,7 +30,7 @@
30
30
  * <indent>}) }
31
31
  */
32
32
 
33
- import { keyAttrName } from '../../utils.ts'
33
+ import { keyAttrName, profileBindingId } from '../../utils.ts'
34
34
  import { emitComponentAndEventSetup } from '../shared.ts'
35
35
  import { emitAttrUpdate } from '../../emit-reactive.ts'
36
36
  import { emitMultiRootTemplateCloneLines } from './template-parse.ts'
@@ -44,17 +44,18 @@ export function stringifyInnerLoops(
44
44
  lines: string[],
45
45
  plan: InnerLoopsPlan,
46
46
  indent: string,
47
+ pc?: string,
47
48
  ): void {
48
49
  for (const inner of plan) {
49
50
  if (inner.emit.mode === 'reactive') {
50
- emitReactive(lines, inner, indent)
51
+ emitReactive(lines, inner, indent, pc)
51
52
  } else {
52
- emitStatic(lines, inner, indent)
53
+ emitStatic(lines, inner, indent, pc)
53
54
  }
54
55
  }
55
56
  }
56
57
 
57
- function emitReactive(lines: string[], inner: InnerLoopPlan, indent: string): void {
58
+ function emitReactive(lines: string[], inner: InnerLoopPlan, indent: string, pc: string | undefined): void {
58
59
  const uid = inner.uidSuffix
59
60
  const emit = inner.emit
60
61
  if (emit.mode !== 'reactive') return // narrow
@@ -98,16 +99,17 @@ function emitReactive(lines: string[], inner: InnerLoopPlan, indent: string): vo
98
99
  )
99
100
  }
100
101
  if (inner.childLevels.length > 0) {
101
- stringifyInnerLoops(lines, inner.childLevels, `${indent} `)
102
+ stringifyInnerLoops(lines, inner.childLevels, `${indent} `, pc)
102
103
  }
103
104
  for (const text of emit.reactiveTexts) {
105
+ const bf = profileBindingId(pc, text.slotId)
104
106
  if (text.insideConditional) {
105
107
  // Re-query $t inside the effect: insert() may swap the text node so a
106
108
  // captured reference would silently stop updating.
107
- lines.push(`${indent} createEffect(() => { const [__rt] = $t(__innerEl${uid}, '${text.slotId}'); if (__rt) __rt.textContent = String(${text.wrappedExpression}) })`)
109
+ lines.push(`${indent} createEffect(() => { const [__rt] = $t(__innerEl${uid}, '${text.slotId}'); if (__rt) __rt.textContent = String(${text.wrappedExpression}) }${bf})`)
108
110
  } else {
109
111
  lines.push(`${indent} { const [__rt] = $t(__innerEl${uid}, '${text.slotId}')`)
110
- lines.push(`${indent} if (__rt) createEffect(() => { __rt.textContent = String(${text.wrappedExpression}) }) }`)
112
+ lines.push(`${indent} if (__rt) createEffect(() => { __rt.textContent = String(${text.wrappedExpression}) }${bf}) }`)
111
113
  }
112
114
  }
113
115
  for (const attr of emit.reactiveAttrs) {
@@ -117,7 +119,7 @@ function emitReactive(lines: string[], inner: InnerLoopPlan, indent: string): vo
117
119
  for (const stmt of emitAttrUpdate(targetVar, attr.attrName, attr.wrappedExpression, attr.meta)) {
118
120
  lines.push(`${indent} ${stmt}`)
119
121
  }
120
- lines.push(`${indent} }) }`)
122
+ lines.push(`${indent} }${profileBindingId(pc, attr.slotId)}) }`)
121
123
  }
122
124
  // Imperative ref callbacks fire on every renderItem invocation, which
123
125
  // means every mount: SSR hydration, initial CSR creation, and same-key
@@ -128,10 +130,10 @@ function emitReactive(lines: string[], inner: InnerLoopPlan, indent: string): vo
128
130
  bodyIsMultiRoot: emit.bodyIsMultiRoot,
129
131
  })
130
132
  lines.push(`${indent} return __innerEl${uid}`)
131
- lines.push(`${indent}}, '${inner.markerId}') }`)
133
+ lines.push(`${indent}}, '${inner.markerId}'${profileBindingId(pc, inner.slotId)}) }`)
132
134
  }
133
135
 
134
- function emitStatic(lines: string[], inner: InnerLoopPlan, indent: string): void {
136
+ function emitStatic(lines: string[], inner: InnerLoopPlan, indent: string, pc: string | undefined): void {
135
137
  const uid = inner.uidSuffix
136
138
  const emit = inner.emit
137
139
  if (emit.mode !== 'static') return
@@ -161,7 +163,7 @@ function emitStatic(lines: string[], inner: InnerLoopPlan, indent: string): void
161
163
  inner.outerLoopParamBindings,
162
164
  )
163
165
  if (inner.childLevels.length > 0) {
164
- stringifyInnerLoops(lines, inner.childLevels, `${indent} `)
166
+ stringifyInnerLoops(lines, inner.childLevels, `${indent} `, pc)
165
167
  }
166
168
  // Imperative ref callbacks for static inner loops — fire once per
167
169
  // forEach iteration (#1244). Static arrays don't reactively re-iterate,
@@ -51,11 +51,17 @@ export function stringifyInsert(
51
51
  const scopeVar = scopeRefToVar(plan.scope)
52
52
  const armIndent = leadingIndent + ' '
53
53
 
54
+ // Profile mode (#1690, SR4): give the conditional's `insert()` effect an id
55
+ // so the profiler attributes its re-runs to source (resolved from the
56
+ // `conditional` domBinding). Empty when off → byte-identical (SR8).
57
+ const condBfId = plan.profileComponentName
58
+ ? `, ${JSON.stringify(`${plan.profileComponentName}#binding:${plan.slotId}`)}`
59
+ : ''
54
60
  lines.push(`${leadingIndent}insert(${scopeVar}, '${plan.slotId}', () => ${plan.condition}, {`)
55
- emitArm(lines, plan.arms[0], plan.eventNameMode, armIndent, bodyIndent)
61
+ emitArm(lines, plan.arms[0], plan.eventNameMode, armIndent, bodyIndent, plan.profileComponentName)
56
62
  lines.push(`${leadingIndent}}, {`)
57
- emitArm(lines, plan.arms[1], plan.eventNameMode, armIndent, bodyIndent)
58
- lines.push(`${leadingIndent}})`)
63
+ emitArm(lines, plan.arms[1], plan.eventNameMode, armIndent, bodyIndent, plan.profileComponentName)
64
+ lines.push(`${leadingIndent}}${condBfId})`)
59
65
  }
60
66
 
61
67
  function emitArm(
@@ -64,6 +70,7 @@ function emitArm(
64
70
  mode: 'dom' | 'raw',
65
71
  armIndent: string,
66
72
  bodyIndent: string,
73
+ profileComponentName?: string,
67
74
  ): void {
68
75
  // `__slots` accumulates live `Node` returns captured by `__bfSlot` so
69
76
  // the `insert()` runtime can splice them into the parsed fragment
@@ -72,7 +79,7 @@ function emitArm(
72
79
  // wrappers around Child-position interpolations under this var name.
73
80
  lines.push(`${armIndent}template: () => { const __slots = []; return { html: \`${arm.templateHtml}\`, slots: __slots } },`)
74
81
  lines.push(`${armIndent}bindEvents: (__branchScope, { isFirstRun: __bfFirstRun = false } = {}) => {`)
75
- emitArmBody(lines, arm.body, mode, bodyIndent)
82
+ emitArmBody(lines, arm.body, mode, bodyIndent, profileComponentName)
76
83
  lines.push(`${armIndent}}`)
77
84
  }
78
85
 
@@ -81,7 +88,12 @@ function emitArmBody(
81
88
  body: ArmBody,
82
89
  mode: 'dom' | 'raw',
83
90
  indent: string,
91
+ profileComponentName?: string,
84
92
  ): void {
93
+ // Profile mode (#1690, SR4): a branch binding effect's id, resolved from its
94
+ // text/attribute `domBinding`. Empty when off → byte-identical (SR8).
95
+ const bindingBfId = (slotId: string): string =>
96
+ profileComponentName ? `, ${JSON.stringify(`${profileComponentName}#binding:${slotId}`)}` : ''
85
97
  // 1. Combine event-bearing slots and ref slots into a single `$()` query.
86
98
  // Order: events-first, then refs (matches legacy emitter).
87
99
  const allSlotIds = new Set<string>()
@@ -105,7 +117,7 @@ function emitArmBody(
105
117
  for (const [slotId, slotEvents] of eventsBySlot) {
106
118
  const v = varSlotId(slotId)
107
119
  for (const ev of slotEvents) {
108
- emitListenerLine(lines, indent, `_${v}`, ev.eventName, ev.handler, mode)
120
+ emitListenerLine(lines, indent, `_${v}`, ev.eventName, ev.handler, mode, ev.turnId)
109
121
  }
110
122
  }
111
123
 
@@ -156,7 +168,7 @@ function emitArmBody(
156
168
  for (const stmt of emitAttrUpdate(elVar, attr.attrName, attr.expression, attr)) {
157
169
  lines.push(`${indent} ${stmt}`)
158
170
  }
159
- lines.push(`${indent} }))`)
171
+ lines.push(`${indent} }${bindingBfId(slotId)}))`)
160
172
  }
161
173
  lines.push(`${indent}} }`)
162
174
  }
@@ -172,7 +184,7 @@ function emitArmBody(
172
184
  lines.push(`${indent}__disposers.push(createDisposableEffect(() => {`)
173
185
  lines.push(`${indent} const __val = ${te.expression}`)
174
186
  lines.push(`${indent} __anchor_${v} = __bfText(__anchor_${v}, __val)`)
175
- lines.push(`${indent}}))`)
187
+ lines.push(`${indent}}${bindingBfId(te.slotId)}))`)
176
188
  }
177
189
 
178
190
  // Branch loops, now fully Plan-built. The stringifier writes its own
@@ -11,7 +11,7 @@
11
11
  * every nesting depth.
12
12
  */
13
13
 
14
- import { varSlotId, DATA_BF_PH, keyAttrName } from '../../utils.ts'
14
+ import { varSlotId, DATA_BF_PH, keyAttrName, profileBindingId } from '../../utils.ts'
15
15
  import { emitComponentAndEventSetup } from '../shared.ts'
16
16
  import { templateRootIsSvg } from './template-parse.ts'
17
17
  import { emitListenerLine } from './event-listener.ts'
@@ -44,7 +44,7 @@ export function stringifyBranchEventBindings(
44
44
  // nearest bf-s and miss descendants in that case.
45
45
  lines.push(`${indent}{ const _${v} = qsa(__branchScope, '[bf="${slot.slotId}"]')`)
46
46
  for (const ev of slot.listeners) {
47
- emitListenerLine(lines, `${indent} `, `_${v}`, ev.eventName, ev.wrappedHandler)
47
+ emitListenerLine(lines, `${indent} `, `_${v}`, ev.eventName, ev.wrappedHandler, 'dom', ev.turnId)
48
48
  }
49
49
  lines.push(`${indent}}`)
50
50
  }
@@ -80,6 +80,7 @@ export function stringifyBranchInnerLoops(
80
80
  lines: string[],
81
81
  plan: BranchInnerLoopsPlan,
82
82
  indent: string,
83
+ pc?: string,
83
84
  ): void {
84
85
  for (const inner of plan) {
85
86
  const uid = inner.uidSuffix
@@ -110,20 +111,21 @@ export function stringifyBranchInnerLoops(
110
111
  )
111
112
  }
112
113
  for (const text of inner.reactiveTexts) {
114
+ const bf = profileBindingId(pc, text.slotId)
113
115
  if (text.insideConditional) {
114
116
  // Re-query $t inside the effect: insert() may swap the text node so a
115
117
  // captured reference would silently stop updating.
116
- lines.push(`${indent} createEffect(() => { const [__rt] = $t(__bel${uid}, '${text.slotId}'); if (__rt) __rt.textContent = String(${text.wrappedExpression}) })`)
118
+ lines.push(`${indent} createEffect(() => { const [__rt] = $t(__bel${uid}, '${text.slotId}'); if (__rt) __rt.textContent = String(${text.wrappedExpression}) }${bf})`)
117
119
  } else {
118
120
  lines.push(`${indent} { const [__rt] = $t(__bel${uid}, '${text.slotId}')`)
119
- lines.push(`${indent} if (__rt) createEffect(() => { __rt.textContent = String(${text.wrappedExpression}) }) }`)
121
+ lines.push(`${indent} if (__rt) createEffect(() => { __rt.textContent = String(${text.wrappedExpression}) }${bf}) }`)
120
122
  }
121
123
  }
122
124
  if (inner.nestedConditionals.length > 0) {
123
- stringifyLoopChildConditionals(lines, inner.nestedConditionals, `${indent} `)
125
+ stringifyLoopChildConditionals(lines, inner.nestedConditionals, `${indent} `, pc)
124
126
  }
125
127
  lines.push(`${indent} return __bel${uid}`)
126
- lines.push(`${indent}}, '${inner.markerId}') }`)
128
+ lines.push(`${indent}}, '${inner.markerId}'${profileBindingId(pc, inner.slotId)}) }`)
127
129
  }
128
130
  }
129
131
 
@@ -138,9 +140,10 @@ export function stringifyLoopChildConditionals(
138
140
  lines: string[],
139
141
  conditionals: readonly LoopChildConditionalPlan[],
140
142
  indent: string,
143
+ pc?: string,
141
144
  ): void {
142
145
  for (const cond of conditionals) {
143
- stringifyLoopChildConditional(lines, cond, indent)
146
+ stringifyLoopChildConditional(lines, cond, indent, pc)
144
147
  }
145
148
  }
146
149
 
@@ -148,6 +151,7 @@ function stringifyLoopChildConditional(
148
151
  lines: string[],
149
152
  cond: LoopChildConditionalPlan,
150
153
  indent: string,
154
+ pc: string | undefined,
151
155
  ): void {
152
156
  const armIndent = `${indent} `
153
157
  // Body-form arrows wire `__bfSlot` captures into the runtime so live
@@ -157,28 +161,29 @@ function stringifyLoopChildConditional(
157
161
  lines.push(`${indent}insert(${cond.scopeVar}, '${cond.slotId}', () => ${cond.wrappedCondition}, {`)
158
162
  lines.push(`${indent} template: () => { const __slots = []; return { html: \`${cond.whenTrueTemplateHtml}\`, slots: __slots } },`)
159
163
  lines.push(`${indent} bindEvents: (__branchScope, { isFirstRun: __bfFirstRun = false } = {}) => {`)
160
- stringifyLoopChildArm(lines, cond.whenTrueArm, armIndent)
164
+ stringifyLoopChildArm(lines, cond.whenTrueArm, armIndent, pc)
161
165
  lines.push(`${indent} }`)
162
166
  lines.push(`${indent}}, {`)
163
167
  lines.push(`${indent} template: () => { const __slots = []; return { html: \`${cond.whenFalseTemplateHtml}\`, slots: __slots } },`)
164
168
  lines.push(`${indent} bindEvents: (__branchScope, { isFirstRun: __bfFirstRun = false } = {}) => {`)
165
- stringifyLoopChildArm(lines, cond.whenFalseArm, armIndent)
169
+ stringifyLoopChildArm(lines, cond.whenFalseArm, armIndent, pc)
166
170
  lines.push(`${indent} }`)
167
- lines.push(`${indent}})`)
171
+ lines.push(`${indent}}${profileBindingId(pc, cond.slotId)})`)
168
172
  }
169
173
 
170
174
  function stringifyLoopChildArm(
171
175
  lines: string[],
172
176
  arm: LoopChildArmPlan,
173
177
  armIndent: string,
178
+ pc: string | undefined,
174
179
  ): void {
175
180
  stringifyBranchEventBindings(lines, arm.events, armIndent)
176
181
  stringifyBranchChildComponentInits(lines, arm.childComponents, armIndent)
177
- stringifyBranchInnerLoops(lines, arm.innerLoops, armIndent)
178
- stringifyLoopChildConditionals(lines, arm.nestedConditionals, armIndent)
182
+ stringifyBranchInnerLoops(lines, arm.innerLoops, armIndent, pc)
183
+ stringifyLoopChildConditionals(lines, arm.nestedConditionals, armIndent, pc)
179
184
  for (const text of arm.texts) {
180
185
  const varName = `__rt_${varSlotId(text.slotId)}`
181
186
  lines.push(`${armIndent}{ const [${varName}] = $t(__branchScope, '${text.slotId}')`)
182
- lines.push(`${armIndent}if (${varName}) createEffect(() => { ${varName}.textContent = String(${text.wrappedExpression}) }) }`)
187
+ lines.push(`${armIndent}if (${varName}) createEffect(() => { ${varName}.textContent = String(${text.wrappedExpression}) }${profileBindingId(pc, text.slotId)}) }`)
183
188
  }
184
189
  }
@@ -24,7 +24,7 @@
24
24
  * passed in via `topIndent`.
25
25
  */
26
26
 
27
- import { emitRefCall, varSlotId } from '../../utils.ts'
27
+ import { emitRefCall, varSlotId, profileBindingId } from '../../utils.ts'
28
28
  import { emitAttrUpdate } from '../../emit-reactive.ts'
29
29
  import { stringifyReactiveEffects } from './reactive-effects.ts'
30
30
  import { emitTemplateCloneInline, emitLoopItemElementSetup } from './template-parse.ts'
@@ -136,13 +136,14 @@ export function stringifyPlainLoop(
136
136
  // `childRefs` need `__el` as a handle to invoke the user's callback inside
137
137
  // the factory, so non-empty refs force the multi-line layout the same way
138
138
  // reactive effects do (#1244).
139
+ const loopBfId = plan.profileLoopId ? `, ${JSON.stringify(plan.profileLoopId)}` : ''
139
140
  if (reactiveEffects === null && !bodyIsMultiRoot && childRefs.length === 0) {
140
141
  // Single-line renderItem (no reactive effects, single root, no refs).
141
142
  const unwrapInline = paramUnwrap ? `${paramUnwrap} ` : ''
142
143
  const preamble = mapPreambleWrapped ? `${mapPreambleWrapped}; ` : ''
143
144
  const cloneExpr = emitTemplateCloneInline(template)
144
145
  lines.push(
145
- `${topIndent}mapArray(() => ${arrayExpr}, ${containerVar}, ${keyFn}, (${paramHead}, ${indexParam}, __existing) => { ${unwrapInline}${preamble}if (__existing) return __existing; ${cloneExpr} }, '${markerId}')`,
146
+ `${topIndent}mapArray(() => ${arrayExpr}, ${containerVar}, ${keyFn}, (${paramHead}, ${indexParam}, __existing) => { ${unwrapInline}${preamble}if (__existing) return __existing; ${cloneExpr} }, '${markerId}'${loopBfId})`,
146
147
  )
147
148
  return
148
149
  }
@@ -163,7 +164,7 @@ export function stringifyPlainLoop(
163
164
  }
164
165
  emitLoopChildRefs(lines, childRefs, { indent: bodyIndent, elVar: '__el', bodyIsMultiRoot })
165
166
  lines.push(`${bodyIndent}return __el`)
166
- lines.push(`${topIndent}}, '${markerId}')`)
167
+ lines.push(`${topIndent}}, '${markerId}'${loopBfId})`)
167
168
  }
168
169
 
169
170
  /**
@@ -212,11 +213,12 @@ function stringifyAnchoredLoop(
212
213
  stringifyReactiveEffects(lines, reactiveEffects, { indent: bodyIndent, elVar: '__anchor', bodyIsMultiRoot: false })
213
214
  }
214
215
  lines.push(`${bodyIndent}return __frag ?? __anchor`)
215
- lines.push(`${topIndent}}, '${markerId}')`)
216
+ const loopBfId = plan.profileLoopId ? `, ${JSON.stringify(plan.profileLoopId)}` : ''
217
+ lines.push(`${topIndent}}, '${markerId}'${loopBfId})`)
216
218
  }
217
219
 
218
220
  export function stringifyStaticLoop(lines: string[], plan: StaticLoopPlan): void {
219
- const { containerVar, arrayExpr, param, indexParam, childIndexExpr, attrsBySlot, texts, childRefs, csrMaterialize } = plan
221
+ const { containerVar, arrayExpr, param, indexParam, childIndexExpr, attrsBySlot, texts, childRefs, csrMaterialize, profileComponentName: pc } = plan
220
222
  const hasAttrs = attrsBySlot.length > 0
221
223
  const hasTexts = texts.length > 0
222
224
  const hasRefs = childRefs.length > 0
@@ -298,14 +300,14 @@ export function stringifyStaticLoop(lines: string[], plan: StaticLoopPlan): void
298
300
  for (const stmt of emitAttrUpdate(varName, attr.attrName, attr.expression, attr)) {
299
301
  lines.push(` ${stmt}`)
300
302
  }
301
- lines.push(` })`)
303
+ lines.push(` }${profileBindingId(pc, slotId)})`)
302
304
  }
303
305
  lines.push(` }`)
304
306
  }
305
307
  for (const text of texts) {
306
308
  const vn = `__rt_${varSlotId(text.slotId)}`
307
309
  lines.push(` { const [${vn}] = $t(__iterEl, '${text.slotId}')`)
308
- lines.push(` if (${vn}) createEffect(() => { ${vn}.textContent = String(${text.expression}) }) }`)
310
+ lines.push(` if (${vn}) createEffect(() => { ${vn}.textContent = String(${text.expression}) }${profileBindingId(pc, text.slotId)}) }`)
309
311
  }
310
312
  // Ref callbacks fire on every forEach iteration — initial mount and any
311
313
  // future array-change-driven re-iteration (#1244). For static arrays the
@@ -8,7 +8,7 @@
8
8
  * `loop-child-arm.ts` — no legacy passthrough remains.
9
9
  */
10
10
 
11
- import { varSlotId } from '../../utils.ts'
11
+ import { varSlotId, profileBindingId } from '../../utils.ts'
12
12
  import { emitAttrUpdate } from '../../emit-reactive.ts'
13
13
  import {
14
14
  stringifyBranchChildComponentInits,
@@ -49,6 +49,8 @@ export function stringifyReactiveEffects(
49
49
  ): void {
50
50
  const { indent, elVar, bodyIsMultiRoot } = opts
51
51
  const lookup = bodyIsMultiRoot ? 'qsaItem' : 'qsa'
52
+ const pc = plan.profileComponentName
53
+ const bindingBfId = (slotId: string): string => profileBindingId(pc, slotId)
52
54
 
53
55
  // 1. Reactive attribute effects (one qsa per slot, then per-attr createEffect).
54
56
  for (const slot of plan.attrSlots) {
@@ -60,20 +62,20 @@ export function stringifyReactiveEffects(
60
62
  for (const stmt of emitAttrUpdate(varName, attr.attrName, attr.wrappedExpression, attr.meta)) {
61
63
  lines.push(`${indent} ${stmt}`)
62
64
  }
63
- lines.push(`${indent} })`)
65
+ lines.push(`${indent} }${bindingBfId(slot.slotId)})`)
64
66
  }
65
67
  lines.push(`${indent}} }`)
66
68
  }
67
69
 
68
70
  // 2. Outer text effects (slots NOT inside any conditional branch).
69
71
  for (const text of plan.outerTexts) {
70
- emitOuterText(lines, indent, elVar, text)
72
+ emitOuterText(lines, indent, elVar, text, bindingBfId(text.slotId))
71
73
  }
72
74
 
73
75
  // 3. Reactive conditionals — each emits an insert(...) over `elVar` whose
74
76
  // arm bodies dispatch through the per-arm stringifiers.
75
77
  for (const cond of plan.conditionals) {
76
- emitOuterConditional(lines, indent, elVar, cond)
78
+ emitOuterConditional(lines, indent, elVar, cond, pc)
77
79
  }
78
80
  }
79
81
 
@@ -82,10 +84,11 @@ function emitOuterText(
82
84
  indent: string,
83
85
  elVar: string,
84
86
  text: ReactiveTextEffect,
87
+ bfId: string = '',
85
88
  ): void {
86
89
  const varName = `__rt_${varSlotId(text.slotId)}`
87
90
  lines.push(`${indent}{ const [${varName}] = $t(${elVar}, '${text.slotId}')`)
88
- lines.push(`${indent}if (${varName}) createEffect(() => { ${varName}.textContent = String(${text.wrappedExpression}) }) }`)
91
+ lines.push(`${indent}if (${varName}) createEffect(() => { ${varName}.textContent = String(${text.wrappedExpression}) }${bfId}) }`)
89
92
  }
90
93
 
91
94
  function emitOuterConditional(
@@ -93,6 +96,7 @@ function emitOuterConditional(
93
96
  indent: string,
94
97
  elVar: string,
95
98
  cond: NestedConditionalPlan,
99
+ pc: string | undefined,
96
100
  ): void {
97
101
  const armIndent = `${indent} `
98
102
 
@@ -101,28 +105,28 @@ function emitOuterConditional(
101
105
  lines.push(`${indent}insert(${elVar}, '${cond.slotId}', () => ${cond.wrappedCondition}, {`)
102
106
  lines.push(`${indent} template: () => { const __slots = []; return { html: \`${cond.whenTrueTemplateHtml}\`, slots: __slots } },`)
103
107
  lines.push(`${indent} bindEvents: (__branchScope, { isFirstRun: __bfFirstRun = false } = {}) => {`)
104
- emitArmBody(lines, cond.whenTrueArm, armIndent)
108
+ emitArmBody(lines, cond.whenTrueArm, armIndent, pc)
105
109
  lines.push(`${indent} }`)
106
110
  lines.push(`${indent}}, {`)
107
111
  lines.push(`${indent} template: () => { const __slots = []; return { html: \`${cond.whenFalseTemplateHtml}\`, slots: __slots } },`)
108
112
  lines.push(`${indent} bindEvents: (__branchScope, { isFirstRun: __bfFirstRun = false } = {}) => {`)
109
- emitArmBody(lines, cond.whenFalseArm, armIndent)
113
+ emitArmBody(lines, cond.whenFalseArm, armIndent, pc)
110
114
  lines.push(`${indent} }`)
111
- lines.push(`${indent}})`)
115
+ lines.push(`${indent}}${profileBindingId(pc, cond.slotId)})`)
112
116
  }
113
117
 
114
- function emitArmBody(lines: string[], arm: LoopChildArmPlan, armIndent: string): void {
118
+ function emitArmBody(lines: string[], arm: LoopChildArmPlan, armIndent: string, pc: string | undefined): void {
115
119
  stringifyBranchEventBindings(lines, arm.events, armIndent)
116
120
  stringifyBranchChildComponentInits(lines, arm.childComponents, armIndent)
117
- stringifyBranchInnerLoops(lines, arm.innerLoops, armIndent)
118
- stringifyLoopChildConditionals(lines, arm.nestedConditionals, armIndent)
121
+ stringifyBranchInnerLoops(lines, arm.innerLoops, armIndent, pc)
122
+ stringifyLoopChildConditionals(lines, arm.nestedConditionals, armIndent, pc)
119
123
  for (const text of arm.texts) {
120
- emitArmText(lines, armIndent, text)
124
+ emitArmText(lines, armIndent, text, pc)
121
125
  }
122
126
  }
123
127
 
124
- function emitArmText(lines: string[], indent: string, text: LoopChildArmText): void {
128
+ function emitArmText(lines: string[], indent: string, text: LoopChildArmText, pc: string | undefined): void {
125
129
  const varName = `__rt_${varSlotId(text.slotId)}`
126
130
  lines.push(`${indent}{ const [${varName}] = $t(__branchScope, '${text.slotId}')`)
127
- lines.push(`${indent}if (${varName}) createEffect(() => { ${varName}.textContent = String(${text.wrappedExpression}) }) }`)
131
+ lines.push(`${indent}if (${varName}) createEffect(() => { ${varName}.textContent = String(${text.wrappedExpression}) }${profileBindingId(pc, text.slotId)}) }`)
128
132
  }
@@ -33,8 +33,9 @@ import { stringifyEventDelegation } from './control-flow/stringify/event-delegat
33
33
 
34
34
  /** Emit insert() calls for server-rendered reactive conditionals with branch configs. */
35
35
  export function emitConditionalUpdates(lines: string[], ctx: ClientJsContext): void {
36
+ const profileComponentName = ctx.profile ? ctx.componentName : undefined
36
37
  for (const elem of ctx.conditionalElements) {
37
- const plan = buildInsertPlan(elem, { scope: { kind: 'top' }, eventNameMode: 'dom' })
38
+ const plan = buildInsertPlan(elem, { scope: { kind: 'top' }, eventNameMode: 'dom', profileComponentName })
38
39
  stringifyInsert(lines, plan, { leadingIndent: ' ', bodyIndent: ' ' })
39
40
  lines.push('')
40
41
  }
@@ -42,8 +43,9 @@ export function emitConditionalUpdates(lines: string[], ctx: ClientJsContext): v
42
43
 
43
44
  /** Emit insert() calls for client-only conditionals (not server-rendered). */
44
45
  export function emitClientOnlyConditionals(lines: string[], ctx: ClientJsContext): void {
46
+ const profileComponentName = ctx.profile ? ctx.componentName : undefined
45
47
  for (const elem of ctx.clientOnlyConditionals) {
46
- const plan = buildInsertPlan(elem, { scope: { kind: 'top' }, eventNameMode: 'raw' })
48
+ const plan = buildInsertPlan(elem, { scope: { kind: 'top' }, eventNameMode: 'raw', profileComponentName })
47
49
  lines.push(` // @client conditional: ${elem.slotId}`)
48
50
  stringifyInsert(lines, plan, { leadingIndent: ' ', bodyIndent: ' ' })
49
51
  lines.push('')
@@ -64,9 +66,12 @@ export function emitClientOnlyConditionals(lines: string[], ctx: ClientJsContext
64
66
  */
65
67
  export function emitLoopUpdates(lines: string[], ctx: ClientJsContext, unsafeLocalNames: Set<string>): void {
66
68
  for (const elem of ctx.loopElements) {
67
- const plan = buildLoopPlan(elem, { unsafeLocalNames })
69
+ const plan = buildLoopPlan(elem, {
70
+ unsafeLocalNames,
71
+ profileComponentName: ctx.profile ? ctx.componentName : undefined,
72
+ })
68
73
  stringifyLoop(lines, plan)
69
- emitLoopEventDelegation(lines, elem, plan.kind)
74
+ emitLoopEventDelegation(lines, elem, plan.kind, ctx.profile ? ctx.componentName : undefined)
70
75
  }
71
76
  }
72
77
 
@@ -74,13 +79,14 @@ function emitLoopEventDelegation(
74
79
  lines: string[],
75
80
  elem: TopLevelLoop,
76
81
  kind: 'plain' | 'component' | 'composite' | 'static',
82
+ profileComponentName?: string,
77
83
  ): void {
78
84
  if (kind === 'static') {
79
85
  // Event delegation for plain elements in static arrays (#537). Static
80
86
  // arrays have no data-key/bf-i markers, so walk up from target to the
81
87
  // container's direct child and use indexOf for index lookup.
82
88
  if (!elem.childComponent && elem.bindings.events.length > 0) {
83
- stringifyEventDelegation(lines, buildStaticArrayDelegationPlan(elem))
89
+ stringifyEventDelegation(lines, buildStaticArrayDelegationPlan(elem, profileComponentName))
84
90
  }
85
91
  return
86
92
  }
@@ -99,6 +105,6 @@ function emitLoopEventDelegation(
99
105
  && !elem.useElementReconciliation
100
106
  && elem.bindings.events.length > 0
101
107
  ) {
102
- stringifyEventDelegation(lines, buildDynamicLoopDelegationPlan(elem))
108
+ stringifyEventDelegation(lines, buildDynamicLoopDelegationPlan(elem, profileComponentName))
103
109
  }
104
110
  }
@@ -10,6 +10,18 @@ import type { ClientJsContext } from './types.ts'
10
10
  import { toHtmlAttrName, varSlotId, PROPS_PARAM } from './utils.ts'
11
11
  import { createTemplateAwareStringProtector } from './html-template.ts'
12
12
 
13
+ /**
14
+ * Profile mode (#1690, SR3/SR4): the id appended to a DOM-binding effect so the
15
+ * profiler attributes its re-runs to a source location. Keyed by `slotId` (the
16
+ * `bf="sN"` marker) — `buildIdIndex` resolves `<Component>#binding:<slotId>`
17
+ * from `graph.domBindings`, which carry the same slot + loc. Empty when
18
+ * profiling is off, so the emitted effect is byte-for-byte unchanged (SR8).
19
+ */
20
+ function bindingIdArg(ctx: ClientJsContext, slotId: string | undefined): string {
21
+ if (!ctx.profile || !slotId) return ''
22
+ return `, ${JSON.stringify(`${ctx.componentName}#binding:${slotId}`)}`
23
+ }
24
+
13
25
  /**
14
26
  * Generate JS statements to update a DOM attribute reactively.
15
27
  * Centralizes the attribute-type dispatch (value, class, boolean, presence, generic)
@@ -110,6 +122,7 @@ export function emitDynamicTextUpdates(lines: string[], ctx: ClientJsContext): v
110
122
  const v = varSlotId(elem.slotId)
111
123
  lines.push(` let __anchor_${v} = _${v}`)
112
124
  }
125
+ const __textSlot = (normalElems[0] ?? conditionalElems[0])?.slotId
113
126
  lines.push(` createEffect(() => {`)
114
127
  if (normalElems.length > 0) {
115
128
  // Expression is always evaluated for non-conditional elements
@@ -139,7 +152,7 @@ export function emitDynamicTextUpdates(lines: string[], ctx: ClientJsContext): v
139
152
  lines.push(` __bfText(__el_${v}, __val)`)
140
153
  }
141
154
  }
142
- lines.push(` })`)
155
+ lines.push(` }${bindingIdArg(ctx, __textSlot)})`)
143
156
  lines.push('')
144
157
  }
145
158
  }
@@ -151,7 +164,7 @@ export function emitClientOnlyExpressions(lines: string[], ctx: ClientJsContext)
151
164
  lines.push(` // @client: ${elem.slotId}`)
152
165
  lines.push(` createEffect(() => {`)
153
166
  lines.push(` updateClientMarker(__scope, '${elem.slotId}', ${elem.expression})`)
154
- lines.push(` })`)
167
+ lines.push(` }${bindingIdArg(ctx, elem.slotId)})`)
155
168
  lines.push('')
156
169
  }
157
170
  }
@@ -178,7 +191,7 @@ export function emitReactiveAttributeUpdates(lines: string[], ctx: ClientJsConte
178
191
  }
179
192
  }
180
193
  lines.push(` }`)
181
- lines.push(` })`)
194
+ lines.push(` }${bindingIdArg(ctx, slotId)})`)
182
195
  lines.push('')
183
196
  }
184
197
  }
@@ -236,7 +249,7 @@ export function emitReactivePropBindings(lines: string[], ctx: ClientJsContext):
236
249
  lines.push(` }`)
237
250
  }
238
251
 
239
- lines.push(` })`)
252
+ lines.push(` }${bindingIdArg(ctx, ctx.reactiveProps[0]?.slotId)})`)
240
253
  }
241
254
  }
242
255
 
@@ -271,6 +284,6 @@ export function emitReactiveChildProps(lines: string[], ctx: ClientJsContext): v
271
284
  lines.push(` }`)
272
285
  }
273
286
 
274
- lines.push(` })`)
287
+ lines.push(` }${bindingIdArg(ctx, ctx.reactiveChildProps[0]?.slotId ?? undefined)})`)
275
288
  }
276
289
  }
@@ -13,6 +13,8 @@ export const RUNTIME_IMPORT_CANDIDATES = [
13
13
  'provideContext', 'createContext', 'useContext',
14
14
  'forwardProps', 'applyRestAttrs', 'splitProps', 'spreadAttrs', 'styleToCss', 'escapeAttr', 'escapeText',
15
15
  'qsa', 'qsaItem', 'qsaChildScope', 'qsaChildScopes', 'upsertChildItem', '__slot', '__bfSlot', '__bfText',
16
+ // Profile mode (#1690, SR3) — turn-boundary markers around event handlers.
17
+ 'beginTurn', 'endTurn',
16
18
  ] as const
17
19
 
18
20
  /** @deprecated Use RUNTIME_IMPORT_CANDIDATES */
@@ -58,6 +58,7 @@ export function generateClientJs(
58
58
  localImportPrefixes?: string[],
59
59
  scope?: ScopeInfo,
60
60
  adapterCapabilities?: AdapterCapabilities,
61
+ profile?: boolean,
61
62
  ): string {
62
63
  return generateClientJsWithSourceMap(
63
64
  ir,
@@ -66,6 +67,7 @@ export function generateClientJs(
66
67
  undefined,
67
68
  scope,
68
69
  adapterCapabilities,
70
+ profile,
69
71
  ).code
70
72
  }
71
73
 
@@ -80,8 +82,9 @@ export function generateClientJsWithSourceMap(
80
82
  options?: { sourceMaps?: boolean; generatedFileName?: string },
81
83
  scope?: ScopeInfo,
82
84
  adapterCapabilities?: AdapterCapabilities,
85
+ profile?: boolean,
83
86
  ): ClientJsResult {
84
- const ctx = createContext(ir, scope, adapterCapabilities)
87
+ const ctx = createContext(ir, scope, adapterCapabilities, profile)
85
88
  const siblingOffsets = computeLoopSiblingOffsets(ir.root)
86
89
  collectElements(ir.root, ctx, siblingOffsets)
87
90
 
@@ -154,11 +157,13 @@ function createContext(
154
157
  ir: ComponentIR,
155
158
  scope?: ScopeInfo,
156
159
  adapterCapabilities?: AdapterCapabilities,
160
+ profile?: boolean,
157
161
  ): ClientJsContext {
158
162
  return {
159
163
  componentName: ir.metadata.componentName,
160
164
  fileScope: scope?.fileScope ?? '',
161
165
  nonExportedSiblings: scope?.nonExportedSiblings ?? new Set(),
166
+ profile: profile ?? false,
162
167
  signals: ir.metadata.signals,
163
168
  memos: ir.metadata.memos,
164
169
  effects: ir.metadata.effects,
@@ -10,13 +10,19 @@
10
10
  import type { ClientJsContext } from '../types.ts'
11
11
 
12
12
  export function emitEffectsAndOnMounts(lines: string[], ctx: ClientJsContext): void {
13
- for (const effect of ctx.effects) {
13
+ ctx.effects.forEach((effect, i) => {
14
+ // Profile mode (#1690): IR-aligned id so runtime effect-run events join to
15
+ // this effect node. Keyed by captureName when present, else the source line
16
+ // (stable across compiles), falling back to source order.
17
+ const idArg = ctx.profile
18
+ ? `, ${JSON.stringify(`${ctx.componentName}#effect:${effect.captureName ?? effect.loc?.start.line ?? i}`)}`
19
+ : ''
14
20
  if (effect.captureName) {
15
- lines.push(` const ${effect.captureName} = createEffect(${effect.body})`)
21
+ lines.push(` const ${effect.captureName} = createEffect(${effect.body}${idArg})`)
16
22
  } else {
17
- lines.push(` createEffect(${effect.body})`)
23
+ lines.push(` createEffect(${effect.body}${idArg})`)
18
24
  }
19
- }
25
+ })
20
26
  for (const onMount of ctx.onMounts) {
21
27
  lines.push(` onMount(${onMount.body})`)
22
28
  }
@@ -10,7 +10,7 @@
10
10
  */
11
11
 
12
12
  import type { ClientJsContext } from '../types.ts'
13
- import { toDomEventName, varSlotId, wrapHandlerInBlock } from '../utils.ts'
13
+ import { toDomEventName, varSlotId, wrapHandlerInBlock, wrapHandlerForTurn } from '../utils.ts'
14
14
 
15
15
  export function emitEventHandlers(
16
16
  lines: string[],
@@ -21,7 +21,11 @@ export function emitEventHandlers(
21
21
  if (conditionalSlotIds.has(elem.slotId)) continue
22
22
  for (const event of elem.events) {
23
23
  const eventName = toDomEventName(event.name)
24
- const wrappedHandler = wrapHandlerInBlock(event.handler)
24
+ // Profile mode (#1690, SR3): bracket the handler with turn markers so a
25
+ // profiling run attributes the reactive work it triggers to this turn.
26
+ const wrappedHandler = ctx.profile
27
+ ? wrapHandlerForTurn(event.handler, `${ctx.componentName}#handler:${elem.slotId}:${event.name}`)
28
+ : wrapHandlerInBlock(event.handler)
25
29
  if (elem.slotId === '__scope') {
26
30
  lines.push(` if (__scope) __scope.addEventListener('${eventName}', ${wrappedHandler})`)
27
31
  } else {