@barefootjs/jsx 0.11.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +22 -13
- package/dist/profiler.d.ts +12 -2
- package/dist/profiler.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/profiler.test.ts +58 -0
- package/src/profiler.ts +51 -19
package/dist/index.js
CHANGED
|
@@ -19844,8 +19844,8 @@ function buildStaticBudget(source, filePath, componentName, options = {}) {
|
|
|
19844
19844
|
const summary = buildComponentSummary(source, filePath, componentName, program);
|
|
19845
19845
|
const subscriptions = graph.signals.reduce((n, s) => n + s.consumers.filter((c) => !isEventHandlerConsumer(c)).length, 0);
|
|
19846
19846
|
const fanOut = graph.signals.map((s) => {
|
|
19847
|
-
const
|
|
19848
|
-
return { signal: s.name, subscribers, hot:
|
|
19847
|
+
const { direct, total } = subscriberCounts(graph, s.name);
|
|
19848
|
+
return { signal: s.name, subscribers: total, direct, hot: direct >= threshold, loc: s.loc };
|
|
19849
19849
|
}).sort((a, b) => b.subscribers - a.subscribers);
|
|
19850
19850
|
const { depth, chain } = longestMemoChain(graph);
|
|
19851
19851
|
const hasReactiveState = graph.signals.length > 0 || graph.memos.length > 0;
|
|
@@ -19873,20 +19873,27 @@ function isEventHandlerConsumer(consumer) {
|
|
|
19873
19873
|
const i = consumer.indexOf(":");
|
|
19874
19874
|
return i > 0 && isEventHandlerEntry(consumer.slice(0, i), consumer.slice(i + 1));
|
|
19875
19875
|
}
|
|
19876
|
-
function
|
|
19876
|
+
function subscriberCounts(graph, name) {
|
|
19877
19877
|
const path = traceUpdatePath(graph, name);
|
|
19878
19878
|
if (!path)
|
|
19879
|
-
return 0;
|
|
19879
|
+
return { direct: 0, total: 0 };
|
|
19880
19880
|
const seen = new Set;
|
|
19881
|
-
const
|
|
19881
|
+
const directSeen = new Set;
|
|
19882
|
+
const walk = (entries, depth) => {
|
|
19882
19883
|
for (const e of entries) {
|
|
19883
|
-
if (!isEventHandlerEntry(e.kind, e.name))
|
|
19884
|
+
if (!isEventHandlerEntry(e.kind, e.name)) {
|
|
19884
19885
|
seen.add(`${e.kind}:${e.name}`);
|
|
19885
|
-
|
|
19886
|
+
if (depth === 0)
|
|
19887
|
+
directSeen.add(`${e.kind}:${e.name}`);
|
|
19888
|
+
}
|
|
19889
|
+
walk(e.children, depth + 1);
|
|
19886
19890
|
}
|
|
19887
19891
|
};
|
|
19888
|
-
walk(path.dependents);
|
|
19889
|
-
return seen.size;
|
|
19892
|
+
walk(path.dependents, 0);
|
|
19893
|
+
return { direct: directSeen.size, total: seen.size };
|
|
19894
|
+
}
|
|
19895
|
+
function transitiveSubscriberCount(graph, name) {
|
|
19896
|
+
return subscriberCounts(graph, name).total;
|
|
19890
19897
|
}
|
|
19891
19898
|
function longestMemoChain(graph) {
|
|
19892
19899
|
const memoChainFrom = (entry) => {
|
|
@@ -19927,7 +19934,9 @@ function formatStaticBudget(b) {
|
|
|
19927
19934
|
if (shown.length > 0) {
|
|
19928
19935
|
lines.push(" fan-out (top):");
|
|
19929
19936
|
for (const f of shown) {
|
|
19930
|
-
|
|
19937
|
+
const indirect = f.subscribers - f.direct;
|
|
19938
|
+
const detail = indirect > 0 ? ` (${f.direct} direct · ${indirect} via memo)` : "";
|
|
19939
|
+
lines.push(` ${f.signal.padEnd(12)} → ${f.subscribers} subscribers${detail}${f.hot ? " ⚠ high" : ""}`);
|
|
19931
19940
|
}
|
|
19932
19941
|
}
|
|
19933
19942
|
if (b.crossComponentOnly) {
|
|
@@ -19940,8 +19949,8 @@ function formatStaticBudget(b) {
|
|
|
19940
19949
|
`);
|
|
19941
19950
|
}
|
|
19942
19951
|
function diffStaticBudget(base, head) {
|
|
19943
|
-
const baseFan = new Map(base.fanOut.map((f) => [f.signal, f.
|
|
19944
|
-
const headFan = new Map(head.fanOut.map((f) => [f.signal, f.
|
|
19952
|
+
const baseFan = new Map(base.fanOut.map((f) => [f.signal, f.direct]));
|
|
19953
|
+
const headFan = new Map(head.fanOut.map((f) => [f.signal, f.direct]));
|
|
19945
19954
|
const signals = new Set([...baseFan.keys(), ...headFan.keys()]);
|
|
19946
19955
|
const fanOut = [];
|
|
19947
19956
|
for (const sig of signals) {
|
|
@@ -19981,7 +19990,7 @@ function formatBudgetDiff(d) {
|
|
|
19981
19990
|
lines.push(` memo chain ${d.memoChainDepth > 0 ? "deepened" : "shortened"} by ${Math.abs(d.memoChainDepth)}`);
|
|
19982
19991
|
}
|
|
19983
19992
|
for (const f of d.fanOut) {
|
|
19984
|
-
lines.push(` signal \`${f.signal}\` fan-out ${f.before}→${f.after}`);
|
|
19993
|
+
lines.push(` signal \`${f.signal}\` direct fan-out ${f.before}→${f.after}`);
|
|
19985
19994
|
}
|
|
19986
19995
|
if (lines.length === 1)
|
|
19987
19996
|
lines.push(" no structural reactivity change");
|
package/dist/profiler.d.ts
CHANGED
|
@@ -25,7 +25,17 @@ export interface FanOutEntry {
|
|
|
25
25
|
signal: string;
|
|
26
26
|
/** Distinct transitive subscribers (memos + effects + DOM bindings). */
|
|
27
27
|
subscribers: number;
|
|
28
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* Distinct subscribers that read the signal *directly* (not through a memo).
|
|
30
|
+
* This is the real per-write re-run pressure: a direct write re-runs exactly
|
|
31
|
+
* these. The remaining `subscribers − direct` sit behind a memo barrier and
|
|
32
|
+
* only re-run when that memo's value actually changes — so adding a memo
|
|
33
|
+
* barrier *lowers* `direct` while it can *raise* `subscribers` (more nodes
|
|
34
|
+
* become statically attributable). Read `direct`, not `subscribers`, to judge
|
|
35
|
+
* cost.
|
|
36
|
+
*/
|
|
37
|
+
direct: number;
|
|
38
|
+
/** True when *direct* fan-out (not the transitive total) meets the threshold. */
|
|
29
39
|
hot: boolean;
|
|
30
40
|
loc: {
|
|
31
41
|
file: string;
|
|
@@ -90,7 +100,7 @@ export interface BudgetDiff {
|
|
|
90
100
|
loops: number;
|
|
91
101
|
subscriptions: number;
|
|
92
102
|
memoChainDepth: number;
|
|
93
|
-
/** Signals whose fan-out changed (added/removed signals
|
|
103
|
+
/** Signals whose *direct* fan-out changed (added/removed signals as 0↔n). */
|
|
94
104
|
fanOut: FanOutChange[];
|
|
95
105
|
/** True when any tracked metric regressed (grew) past zero. */
|
|
96
106
|
regressed: boolean;
|
package/dist/profiler.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"profiler.d.ts","sourceRoot":"","sources":["../src/profiler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,EAKL,KAAK,cAAc,EAGpB,MAAM,YAAY,CAAA;AAEnB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAIvD,MAAM,WAAW,WAAW;IAC1B,+DAA+D;IAC/D,MAAM,EAAE,MAAM,CAAA;IACd,wEAAwE;IACxE,WAAW,EAAE,MAAM,CAAA;IACnB,
|
|
1
|
+
{"version":3,"file":"profiler.d.ts","sourceRoot":"","sources":["../src/profiler.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@barefootjs/jsx",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "JSX compiler for BarefootJS - transforms JSX to server HTML + client JS",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"directory": "packages/jsx"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@barefootjs/shared": "0.
|
|
56
|
+
"@barefootjs/shared": "0.12.0"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
59
|
"@barefootjs/client": ">=0.2.0",
|
|
@@ -103,6 +103,30 @@ describe('buildStaticBudget (SR5)', () => {
|
|
|
103
103
|
expect(cold.fanOut.every(f => f.hot === false)).toBe(true)
|
|
104
104
|
})
|
|
105
105
|
|
|
106
|
+
test('splits fan-out into direct vs via-memo and keys `hot` off direct', () => {
|
|
107
|
+
// count → a → b → c → {effect, text}: exactly ONE direct subscriber (memo
|
|
108
|
+
// a); the rest are reached only through memo barriers.
|
|
109
|
+
const b = buildStaticBudget(memoChainSource, 'Calc.tsx', 'Calc')
|
|
110
|
+
const count = b.fanOut.find(f => f.signal === 'count')!
|
|
111
|
+
expect(count.direct).toBe(1)
|
|
112
|
+
expect(count.subscribers).toBeGreaterThanOrEqual(4)
|
|
113
|
+
// The transitive total clears a threshold of 3, but direct (1) does not — so
|
|
114
|
+
// the signal is NOT hot. `hot` tracks real per-write pressure, not the total.
|
|
115
|
+
const mid = buildStaticBudget(memoChainSource, 'Calc.tsx', 'Calc', { fanOutThreshold: 3 })
|
|
116
|
+
const c = mid.fanOut.find(f => f.signal === 'count')!
|
|
117
|
+
expect(c.subscribers).toBeGreaterThanOrEqual(3)
|
|
118
|
+
expect(c.hot).toBe(false)
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
test('formats the direct/via-memo split only when a memo barrier routes', () => {
|
|
122
|
+
// Memo chain → the split is shown.
|
|
123
|
+
const withMemo = formatStaticBudget(buildStaticBudget(memoChainSource, 'Calc.tsx', 'Calc'))
|
|
124
|
+
expect(withMemo).toMatch(/count\s+→ \d+ subscribers \(1 direct · \d+ via memo\)/)
|
|
125
|
+
// Counter reads its signal directly (no memo) → no parenthetical.
|
|
126
|
+
const direct = formatStaticBudget(buildStaticBudget(counterSource, 'Counter.tsx', 'Counter'))
|
|
127
|
+
expect(direct).not.toContain('via memo')
|
|
128
|
+
})
|
|
129
|
+
|
|
106
130
|
test('formats a human-readable budget', () => {
|
|
107
131
|
const out = formatStaticBudget(buildStaticBudget(memoChainSource, 'Calc.tsx', 'Calc'))
|
|
108
132
|
expect(out).toContain('static reactivity budget')
|
|
@@ -175,6 +199,40 @@ describe('diffStaticBudget (SR6)', () => {
|
|
|
175
199
|
expect(formatBudgetDiff(diff)).toContain('no structural reactivity change')
|
|
176
200
|
})
|
|
177
201
|
|
|
202
|
+
test('a memo barrier that lowers direct fan-out is not a fan-out regression', () => {
|
|
203
|
+
// Before: `count` is read directly by an effect AND a text binding (direct 2).
|
|
204
|
+
const before = `
|
|
205
|
+
'use client'
|
|
206
|
+
import { createSignal, createEffect } from '@barefootjs/client'
|
|
207
|
+
export function R() {
|
|
208
|
+
const [count, setCount] = createSignal(0)
|
|
209
|
+
createEffect(() => console.log(count()))
|
|
210
|
+
return <button>{count()}</button>
|
|
211
|
+
}
|
|
212
|
+
`
|
|
213
|
+
// After: both reads go through a memo, so `count`'s direct fan-out drops to 1
|
|
214
|
+
// (the memo) even though a memo was added and the transitive total rose.
|
|
215
|
+
const after = `
|
|
216
|
+
'use client'
|
|
217
|
+
import { createSignal, createMemo, createEffect } from '@barefootjs/client'
|
|
218
|
+
export function R() {
|
|
219
|
+
const [count, setCount] = createSignal(0)
|
|
220
|
+
const m = createMemo(() => count())
|
|
221
|
+
createEffect(() => console.log(m()))
|
|
222
|
+
return <button>{m()}</button>
|
|
223
|
+
}
|
|
224
|
+
`
|
|
225
|
+
const base = buildStaticBudget(before, 'R.tsx', 'R')
|
|
226
|
+
const head = buildStaticBudget(after, 'R.tsx', 'R')
|
|
227
|
+
expect(base.fanOut.find(f => f.signal === 'count')!.direct).toBe(2)
|
|
228
|
+
expect(head.fanOut.find(f => f.signal === 'count')!.direct).toBe(1)
|
|
229
|
+
// The fan-out delta reads the refactor as the improvement it is: direct
|
|
230
|
+
// fan-out shrank, so the recorded change is a decrease, not growth.
|
|
231
|
+
const diff = diffStaticBudget(base, head)
|
|
232
|
+
const ch = diff.fanOut.find(f => f.signal === 'count')!
|
|
233
|
+
expect(ch.after).toBeLessThan(ch.before)
|
|
234
|
+
})
|
|
235
|
+
|
|
178
236
|
test('carries a "diff" kind discriminator (#1849 B2)', () => {
|
|
179
237
|
// The three JSON modes must be distinguishable: a zero-delta diff
|
|
180
238
|
// (signals: 0 = "no change") must not look like a static budget
|
package/src/profiler.ts
CHANGED
|
@@ -39,7 +39,17 @@ export interface FanOutEntry {
|
|
|
39
39
|
signal: string
|
|
40
40
|
/** Distinct transitive subscribers (memos + effects + DOM bindings). */
|
|
41
41
|
subscribers: number
|
|
42
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* Distinct subscribers that read the signal *directly* (not through a memo).
|
|
44
|
+
* This is the real per-write re-run pressure: a direct write re-runs exactly
|
|
45
|
+
* these. The remaining `subscribers − direct` sit behind a memo barrier and
|
|
46
|
+
* only re-run when that memo's value actually changes — so adding a memo
|
|
47
|
+
* barrier *lowers* `direct` while it can *raise* `subscribers` (more nodes
|
|
48
|
+
* become statically attributable). Read `direct`, not `subscribers`, to judge
|
|
49
|
+
* cost.
|
|
50
|
+
*/
|
|
51
|
+
direct: number
|
|
52
|
+
/** True when *direct* fan-out (not the transitive total) meets the threshold. */
|
|
43
53
|
hot: boolean
|
|
44
54
|
loc: { file: string; line: number }
|
|
45
55
|
}
|
|
@@ -107,8 +117,10 @@ export function buildStaticBudget(
|
|
|
107
117
|
|
|
108
118
|
const fanOut: FanOutEntry[] = graph.signals
|
|
109
119
|
.map(s => {
|
|
110
|
-
const
|
|
111
|
-
|
|
120
|
+
const { direct, total } = subscriberCounts(graph, s.name)
|
|
121
|
+
// `hot` keys off *direct* fan-out — a signal driving 12 nodes through one
|
|
122
|
+
// memo (direct 1) isn't hot; one driving 12 nodes directly is.
|
|
123
|
+
return { signal: s.name, subscribers: total, direct, hot: direct >= threshold, loc: s.loc }
|
|
112
124
|
})
|
|
113
125
|
.sort((a, b) => b.subscribers - a.subscribers)
|
|
114
126
|
|
|
@@ -161,23 +173,35 @@ function isEventHandlerConsumer(consumer: string): boolean {
|
|
|
161
173
|
}
|
|
162
174
|
|
|
163
175
|
/**
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
176
|
+
* Subscriber counts for a signal/memo: `direct` (read it without a memo in
|
|
177
|
+
* between — the top level of the dependent tree) and `total` (every distinct
|
|
178
|
+
* transitive subscriber). Walks the same tagged `consumers` tree
|
|
179
|
+
* `traceUpdatePath` builds (`debug.ts`), deduplicating across branches so a
|
|
180
|
+
* diamond dependency counts each subscriber once. Event handlers are excluded —
|
|
181
|
+
* they read but don't react. `total − direct` are the nodes reached only
|
|
182
|
+
* through a memo barrier.
|
|
168
183
|
*/
|
|
169
|
-
function
|
|
184
|
+
function subscriberCounts(graph: ComponentGraph, name: string): { direct: number; total: number } {
|
|
170
185
|
const path = traceUpdatePath(graph, name)
|
|
171
|
-
if (!path) return 0
|
|
186
|
+
if (!path) return { direct: 0, total: 0 }
|
|
172
187
|
const seen = new Set<string>()
|
|
173
|
-
const
|
|
188
|
+
const directSeen = new Set<string>()
|
|
189
|
+
const walk = (entries: UpdatePathEntry[], depth: number): void => {
|
|
174
190
|
for (const e of entries) {
|
|
175
|
-
if (!isEventHandlerEntry(e.kind, e.name))
|
|
176
|
-
|
|
191
|
+
if (!isEventHandlerEntry(e.kind, e.name)) {
|
|
192
|
+
seen.add(`${e.kind}:${e.name}`)
|
|
193
|
+
if (depth === 0) directSeen.add(`${e.kind}:${e.name}`)
|
|
194
|
+
}
|
|
195
|
+
walk(e.children, depth + 1)
|
|
177
196
|
}
|
|
178
197
|
}
|
|
179
|
-
walk(path.dependents)
|
|
180
|
-
return seen.size
|
|
198
|
+
walk(path.dependents, 0)
|
|
199
|
+
return { direct: directSeen.size, total: seen.size }
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** Distinct transitive subscribers — the `total` of {@link subscriberCounts}. */
|
|
203
|
+
function transitiveSubscriberCount(graph: ComponentGraph, name: string): number {
|
|
204
|
+
return subscriberCounts(graph, name).total
|
|
181
205
|
}
|
|
182
206
|
|
|
183
207
|
/**
|
|
@@ -223,7 +247,11 @@ export function formatStaticBudget(b: StaticBudget): string {
|
|
|
223
247
|
if (shown.length > 0) {
|
|
224
248
|
lines.push(' fan-out (top):')
|
|
225
249
|
for (const f of shown) {
|
|
226
|
-
|
|
250
|
+
// Show the direct/indirect split only when a memo barrier routes some of
|
|
251
|
+
// the subscribers — otherwise the bare count is already the direct count.
|
|
252
|
+
const indirect = f.subscribers - f.direct
|
|
253
|
+
const detail = indirect > 0 ? ` (${f.direct} direct · ${indirect} via memo)` : ''
|
|
254
|
+
lines.push(` ${f.signal.padEnd(12)} → ${f.subscribers} subscribers${detail}${f.hot ? ' ⚠ high' : ''}`)
|
|
227
255
|
}
|
|
228
256
|
}
|
|
229
257
|
if (b.crossComponentOnly) {
|
|
@@ -260,7 +288,7 @@ export interface BudgetDiff {
|
|
|
260
288
|
loops: number
|
|
261
289
|
subscriptions: number
|
|
262
290
|
memoChainDepth: number
|
|
263
|
-
/** Signals whose fan-out changed (added/removed signals
|
|
291
|
+
/** Signals whose *direct* fan-out changed (added/removed signals as 0↔n). */
|
|
264
292
|
fanOut: FanOutChange[]
|
|
265
293
|
/** True when any tracked metric regressed (grew) past zero. */
|
|
266
294
|
regressed: boolean
|
|
@@ -272,8 +300,12 @@ export interface BudgetDiff {
|
|
|
272
300
|
* `regressed` is true (or gate on a specific metric threshold).
|
|
273
301
|
*/
|
|
274
302
|
export function diffStaticBudget(base: StaticBudget, head: StaticBudget): BudgetDiff {
|
|
275
|
-
|
|
276
|
-
|
|
303
|
+
// Track *direct* fan-out: a refactor that routes subscribers through a new
|
|
304
|
+
// memo lowers direct fan-out (less re-run pressure) even as it can raise the
|
|
305
|
+
// transitive total, so a direct-fan-out delta reads such a change as the
|
|
306
|
+
// improvement it is instead of a false regression.
|
|
307
|
+
const baseFan = new Map(base.fanOut.map(f => [f.signal, f.direct]))
|
|
308
|
+
const headFan = new Map(head.fanOut.map(f => [f.signal, f.direct]))
|
|
277
309
|
const signals = new Set([...baseFan.keys(), ...headFan.keys()])
|
|
278
310
|
|
|
279
311
|
const fanOut: FanOutChange[] = []
|
|
@@ -318,7 +350,7 @@ export function formatBudgetDiff(d: BudgetDiff): string {
|
|
|
318
350
|
lines.push(` memo chain ${d.memoChainDepth > 0 ? 'deepened' : 'shortened'} by ${Math.abs(d.memoChainDepth)}`)
|
|
319
351
|
}
|
|
320
352
|
for (const f of d.fanOut) {
|
|
321
|
-
lines.push(` signal \`${f.signal}\` fan-out ${f.before}→${f.after}`)
|
|
353
|
+
lines.push(` signal \`${f.signal}\` direct fan-out ${f.before}→${f.after}`)
|
|
322
354
|
}
|
|
323
355
|
if (lines.length === 1) lines.push(' no structural reactivity change')
|
|
324
356
|
else lines.push(d.regressed ? ' ⚠ reactivity regressed' : ' ✓ no regression')
|