react-render-detective 0.4.0 → 0.5.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.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var chunkJD4IJ4MN_cjs = require('./chunk-JD4IJ4MN.cjs');
4
- var chunkDZ3BZ654_cjs = require('./chunk-DZ3BZ654.cjs');
4
+ var chunkOSPTERGK_cjs = require('./chunk-OSPTERGK.cjs');
5
5
  var chunkH5RG2EPP_cjs = require('./chunk-H5RG2EPP.cjs');
6
6
  var React = require('react');
7
7
  var jsxRuntime = require('react/jsx-runtime');
@@ -26,6 +26,40 @@ function _interopNamespace(e) {
26
26
 
27
27
  var React__namespace = /*#__PURE__*/_interopNamespace(React);
28
28
 
29
+ // src/console/style.ts
30
+ var PALETTE = {
31
+ plain: "",
32
+ dim: "color:#8a94a6",
33
+ strong: "font-weight:600",
34
+ good: "color:#1f9d5b",
35
+ warn: "color:#c2820a",
36
+ bad: "color:#d1493f;font-weight:600"
37
+ };
38
+ var supportsStyling = () => {
39
+ try {
40
+ return typeof window !== "undefined" && typeof document !== "undefined";
41
+ } catch {
42
+ return false;
43
+ }
44
+ };
45
+ function styled(segments) {
46
+ if (!supportsStyling()) {
47
+ return [segments.map((s) => typeof s === "string" ? s : s[0]).join("")];
48
+ }
49
+ let format = "";
50
+ const styles = [];
51
+ for (const segment of segments) {
52
+ if (typeof segment === "string") {
53
+ format += segment.replace(/%/g, "%%");
54
+ continue;
55
+ }
56
+ const [text, tone] = segment;
57
+ format += `%c${text.replace(/%/g, "%%")}%c`;
58
+ styles.push(PALETTE[tone], "");
59
+ }
60
+ return [format, ...styles];
61
+ }
62
+
29
63
  // src/console/reporter.ts
30
64
  var ICON = {
31
65
  normal: "\xB7",
@@ -34,30 +68,102 @@ var ICON = {
34
68
  "very-slow": "\u25B2\u25B2",
35
69
  critical: "\u25A0"
36
70
  };
71
+ var BATCH_WINDOW_MS = 400;
37
72
  function attachConsoleReporter(detective) {
38
- return detective.subscribe((event) => {
39
- const mode = detective.config.mode;
40
- if (mode === "silent") return;
73
+ let pending = [];
74
+ let timer;
75
+ const flush = () => {
76
+ timer = void 0;
77
+ const batch = pending;
78
+ pending = [];
79
+ if (batch.length === 0) return;
41
80
  try {
42
- if (mode === "verbose") printVerbose(event);
43
- else printConcise(event, detective.config.slowRenderThreshold);
81
+ printBatch(batch, detective.config.slowRenderThreshold);
44
82
  } catch {
45
83
  }
84
+ };
85
+ const unsubscribe = detective.subscribe((event) => {
86
+ const mode = detective.config.mode;
87
+ if (mode === "silent") return;
88
+ if (mode === "verbose") {
89
+ try {
90
+ printVerbose(event);
91
+ } catch {
92
+ }
93
+ return;
94
+ }
95
+ pending.push(event);
96
+ if (timer === void 0) {
97
+ timer = setTimeout(flush, BATCH_WINDOW_MS);
98
+ timer.unref?.();
99
+ }
46
100
  });
101
+ return () => {
102
+ unsubscribe();
103
+ if (timer !== void 0) clearTimeout(timer);
104
+ };
47
105
  }
48
- function printConcise(event, slowThreshold) {
49
- const { diagnosis, timings, component } = event;
50
- const changed = event.changedProps.map((c) => c.key).join(", ");
51
- const parts = [
52
- `[RRD] ${component.name} #${event.renderNumber}`,
53
- `Reason: ${label(event)}`,
54
- changed ? `Changed: ${changed}` : void 0,
55
- timings.subtreeDuration > 0 ? `Duration: ${timings.selfDuration.toFixed(1)}ms` : void 0
56
- ].filter(Boolean);
57
- const line = `${ICON[diagnosis.severity] ?? "\xB7"} ${parts.join(" ")}`;
58
- const slow = timings.selfDuration >= slowThreshold;
59
- if (slow) console.warn(line);
60
- else console.log(line);
106
+ function printBatch(batch, slowThreshold) {
107
+ const byComponent = /* @__PURE__ */ new Map();
108
+ let totalMs = 0;
109
+ let avoidable = 0;
110
+ for (const event of batch) {
111
+ const key = event.component.name;
112
+ const entry = byComponent.get(key) ?? {
113
+ name: key,
114
+ source: event.component.source,
115
+ count: 0,
116
+ totalMs: 0,
117
+ reasons: /* @__PURE__ */ new Map(),
118
+ notableReasons: /* @__PURE__ */ new Map(),
119
+ avoidable: 0,
120
+ remount: false,
121
+ slow: false
122
+ };
123
+ entry.count++;
124
+ entry.totalMs += event.timings.selfDuration;
125
+ entry.reasons.set(event.diagnosis.reason, (entry.reasons.get(event.diagnosis.reason) ?? 0) + 1);
126
+ if (event.diagnosis.potentiallyAvoidable || event.timings.selfDuration >= slowThreshold) {
127
+ entry.notableReasons.set(event.diagnosis.reason, (entry.notableReasons.get(event.diagnosis.reason) ?? 0) + 1);
128
+ }
129
+ if (event.diagnosis.potentiallyAvoidable) entry.avoidable++;
130
+ if (event.diagnosis.summary.includes("rebuilt")) entry.remount = true;
131
+ if (event.timings.selfDuration >= slowThreshold) entry.slow = true;
132
+ if (!entry.worst || event.timings.selfDuration > entry.worst.timings.selfDuration) entry.worst = event;
133
+ byComponent.set(key, entry);
134
+ totalMs += event.timings.selfDuration;
135
+ if (event.diagnosis.potentiallyAvoidable) avoidable++;
136
+ }
137
+ const notable = [...byComponent.values()].filter((a) => a.avoidable > 0 || a.slow || a.remount);
138
+ if (notable.length === 0) return;
139
+ const ranked = [...notable].sort((a, b) => b.totalMs - a.totalMs);
140
+ const anySlow = ranked.some((a) => a.slow);
141
+ const segments = [
142
+ ["[RRD] ", "dim"],
143
+ [`${batch.length} render${batch.length === 1 ? "" : "s"}`, "strong"],
144
+ [` \xB7 ${totalMs.toFixed(1)}ms`, "dim"]
145
+ ];
146
+ if (avoidable > 0) segments.push([` \xB7 ${avoidable} potentially avoidable`, "warn"]);
147
+ for (const a of ranked.slice(0, 8)) {
148
+ const reasonSource = a.notableReasons.size > 0 ? a.notableReasons : a.reasons;
149
+ const reason = [...reasonSource.entries()].sort((x, y) => y[1] - x[1])[0]?.[0] ?? "unknown";
150
+ const tone = a.remount ? "bad" : a.slow ? "warn" : a.avoidable > 0 ? "warn" : "good";
151
+ const label2 = ` ${ICON[a.slow ? "slow" : "normal"]} ${a.name}${a.count > 1 ? ` \xD7${a.count}` : ""}`;
152
+ segments.push("\n", [label2.padEnd(30), tone], [`${a.totalMs.toFixed(1)}ms`, "plain"], [` ${reason}`, "dim"]);
153
+ if (a.remount) segments.push([" rebuilt", "bad"]);
154
+ if (a.avoidable > 0) segments.push([` ${a.avoidable} avoidable`, "warn"]);
155
+ if (a.source) segments.push(["\n " + a.source, "dim"]);
156
+ }
157
+ const worst = ranked[0]?.worst;
158
+ segments.push(
159
+ "\n",
160
+ [
161
+ worst?.diagnosis.suggestion ? ` \u2192 ${worst.diagnosis.suggestion}` : " \u2192 rrd.printOpportunities() to rank everything by recoverable time",
162
+ "dim"
163
+ ]
164
+ );
165
+ const log = anySlow ? console.warn : console.log;
166
+ log(...styled(segments));
61
167
  }
62
168
  function printVerbose(event) {
63
169
  const { diagnosis, timings } = event;
@@ -80,12 +186,10 @@ function printVerbose(event) {
80
186
  for (const c of event.changedProps) console.log(propLine(c));
81
187
  console.groupEnd();
82
188
  }
83
- if (event.unchangedProps.length > 0) {
84
- console.log(`Props same: ${event.unchangedProps.join(", ")}`);
85
- }
189
+ if (event.unchangedProps.length > 0) console.log(`Props same: ${event.unchangedProps.join(", ")}`);
86
190
  if (event.trackedState.length > 0) {
87
191
  console.log(
88
- `State: ${event.trackedState.map((s) => `${s.name}: ${chunkDZ3BZ654_cjs.formatInspected(s.previous)} \u2192 ${chunkDZ3BZ654_cjs.formatInspected(s.current)}`).join(", ")}`
192
+ `State: ${event.trackedState.map((s) => `${s.name}: ${chunkOSPTERGK_cjs.formatInspected(s.previous)} \u2192 ${chunkOSPTERGK_cjs.formatInspected(s.current)}`).join(", ")}`
89
193
  );
90
194
  }
91
195
  if (event.contextChanges.length > 0) {
@@ -102,11 +206,11 @@ function printVerbose(event) {
102
206
  }
103
207
  function propLine(c) {
104
208
  const head = `${c.key} (${c.valueType}) \u2014 ${describeKind(c)}`;
105
- if (c.kind === "added") return `${head}: ${chunkDZ3BZ654_cjs.formatInspected(c.current)}`;
106
- if (c.kind === "removed") return `${head}: was ${chunkDZ3BZ654_cjs.formatInspected(c.previous)}`;
209
+ if (c.kind === "added") return `${head}: ${chunkOSPTERGK_cjs.formatInspected(c.current)}`;
210
+ if (c.kind === "removed") return `${head}: was ${chunkOSPTERGK_cjs.formatInspected(c.previous)}`;
107
211
  return `${head}
108
- previous: ${chunkDZ3BZ654_cjs.formatInspected(c.previous)}
109
- current: ${chunkDZ3BZ654_cjs.formatInspected(c.current)}`;
212
+ previous: ${chunkOSPTERGK_cjs.formatInspected(c.previous)}
213
+ current: ${chunkOSPTERGK_cjs.formatInspected(c.current)}`;
110
214
  }
111
215
  function describeKind(c) {
112
216
  switch (c.kind) {
@@ -381,7 +485,7 @@ function formatOpportunities(opportunities) {
381
485
  var AncestryContext = React.createContext(void 0);
382
486
  AncestryContext.displayName = "RenderDetectiveAncestry";
383
487
  function useInstrumentedNode(name, props, source) {
384
- const detective = chunkDZ3BZ654_cjs.getDetective();
488
+ const detective = chunkOSPTERGK_cjs.getDetective();
385
489
  const parent = React.useContext(AncestryContext);
386
490
  const [node] = React.useState(
387
491
  () => detective.enabled ? detective.createNode(name, parent, source) : void 0
@@ -431,7 +535,7 @@ var IS_WRAPPER = /* @__PURE__ */ Symbol.for("react-render-detective.wrapper");
431
535
  function withRenderDetective(Component, options = {}) {
432
536
  if (Component[IS_WRAPPER]) return Component;
433
537
  const name = options.name ?? componentName(Component);
434
- chunkDZ3BZ654_cjs.getDetective().noteDefinition(name, options.source, options.declaredInRender === true);
538
+ chunkOSPTERGK_cjs.getDetective().noteDefinition(name, options.source, options.declaredInRender === true);
435
539
  function RenderDetected(props) {
436
540
  const { node, onRender } = useInstrumentedNode(name, props, options.source);
437
541
  return renderInstrumented(node, onRender, /* @__PURE__ */ jsxRuntime.jsx(Component, { ...props }));
@@ -447,7 +551,7 @@ function RenderDetective({ name, children }) {
447
551
  return renderInstrumented(node, onRender, children);
448
552
  }
449
553
  function useRenderDiagnostics(name, props) {
450
- const detective = chunkDZ3BZ654_cjs.getDetective();
554
+ const detective = chunkOSPTERGK_cjs.getDetective();
451
555
  const parent = React.useContext(AncestryContext);
452
556
  const [node] = React.useState(() => {
453
557
  if (!detective.enabled) return void 0;
@@ -482,7 +586,7 @@ function useRenderDiagnostics(name, props) {
482
586
  }
483
587
  var useOwnerId = typeof React__namespace.useId === "function" ? React__namespace.useId : () => "";
484
588
  function useTrackedState(name, initial) {
485
- const detective = chunkDZ3BZ654_cjs.getDetective();
589
+ const detective = chunkOSPTERGK_cjs.getDetective();
486
590
  const node = React.useContext(AncestryContext);
487
591
  const ownerId = useOwnerId();
488
592
  const [state, setState] = React.useState(initial);
@@ -492,8 +596,8 @@ function useTrackedState(name, initial) {
492
596
  if (node && owns && !Object.is(previous.current, state)) {
493
597
  detective.recordStateChange(node, {
494
598
  name,
495
- previous: chunkDZ3BZ654_cjs.inspect(previous.current, detective.config.inspection),
496
- current: chunkDZ3BZ654_cjs.inspect(state, detective.config.inspection)
599
+ previous: chunkOSPTERGK_cjs.inspect(previous.current, detective.config.inspection),
600
+ current: chunkOSPTERGK_cjs.inspect(state, detective.config.inspection)
497
601
  });
498
602
  previous.current = state;
499
603
  } else if (!owns) {
@@ -502,7 +606,7 @@ function useTrackedState(name, initial) {
502
606
  return [state, setState];
503
607
  }
504
608
  function useTrackedEffect(name, effect, deps) {
505
- const detective = chunkDZ3BZ654_cjs.getDetective();
609
+ const detective = chunkOSPTERGK_cjs.getDetective();
506
610
  const previous = React.useRef(void 0);
507
611
  const changed = React.useRef([]);
508
612
  if (detective.enabled) {
@@ -518,7 +622,7 @@ function useTrackedEffect(name, effect, deps) {
518
622
  }, deps);
519
623
  }
520
624
  function useTrackedContextValue(contextName, value) {
521
- const detective = chunkDZ3BZ654_cjs.getDetective();
625
+ const detective = chunkOSPTERGK_cjs.getDetective();
522
626
  const previous = React.useRef(void 0);
523
627
  const first = React.useRef(true);
524
628
  if (detective.enabled) {
@@ -526,8 +630,8 @@ function useTrackedContextValue(contextName, value) {
526
630
  first.current = false;
527
631
  } else if (!Object.is(previous.current, value)) {
528
632
  const prev = previous.current;
529
- const equal = chunkDZ3BZ654_cjs.shallowEqual(prev, value, detective.config);
530
- const changedKeys = isRecord(prev) && isRecord(value) ? chunkDZ3BZ654_cjs.diffProps(prev, value, detective.config).changed.map((c) => c.key) : [];
633
+ const equal = chunkOSPTERGK_cjs.shallowEqual(prev, value, detective.config);
634
+ const changedKeys = isRecord(prev) && isRecord(value) ? chunkOSPTERGK_cjs.diffProps(prev, value, detective.config).changed.map((c) => c.key) : [];
531
635
  detective.recordContextChange({
532
636
  contextName,
533
637
  changedKeys,
@@ -552,7 +656,7 @@ function tracker() {
552
656
  return g[INTERACTIONS];
553
657
  }
554
658
  function init(options = {}) {
555
- const detective = chunkDZ3BZ654_cjs.getDetective();
659
+ const detective = chunkOSPTERGK_cjs.getDetective();
556
660
  detective.init(options);
557
661
  const g = globalThis;
558
662
  g[REPORTER]?.();
@@ -569,28 +673,28 @@ function init(options = {}) {
569
673
  }
570
674
  }
571
675
  function configure(options) {
572
- chunkDZ3BZ654_cjs.getDetective().configure(options);
676
+ chunkOSPTERGK_cjs.getDetective().configure(options);
573
677
  }
574
678
  function getConfig() {
575
- return chunkDZ3BZ654_cjs.getDetective().config;
679
+ return chunkOSPTERGK_cjs.getDetective().config;
576
680
  }
577
681
  function isEnabled() {
578
- return chunkDZ3BZ654_cjs.getDetective().enabled;
682
+ return chunkOSPTERGK_cjs.getDetective().enabled;
579
683
  }
580
684
  function getEvents() {
581
- return chunkDZ3BZ654_cjs.getDetective().getEvents();
685
+ return chunkOSPTERGK_cjs.getDetective().getEvents();
582
686
  }
583
687
  function getStats() {
584
- return chunkDZ3BZ654_cjs.getDetective().getStats();
688
+ return chunkOSPTERGK_cjs.getDetective().getStats();
585
689
  }
586
690
  function getComponentStats(name) {
587
- return chunkDZ3BZ654_cjs.getDetective().getComponentStats(name);
691
+ return chunkOSPTERGK_cjs.getDetective().getComponentStats(name);
588
692
  }
589
693
  function subscribe(listener) {
590
- return chunkDZ3BZ654_cjs.getDetective().subscribe(listener);
694
+ return chunkOSPTERGK_cjs.getDetective().subscribe(listener);
591
695
  }
592
696
  function clear() {
593
- chunkDZ3BZ654_cjs.getDetective().clear();
697
+ chunkOSPTERGK_cjs.getDetective().clear();
594
698
  tracker().clear();
595
699
  }
596
700
  function reset() {
@@ -599,18 +703,18 @@ function reset() {
599
703
  g[REPORTER] = void 0;
600
704
  g[INTERACTIONS]?.stop();
601
705
  g[INTERACTIONS] = void 0;
602
- chunkDZ3BZ654_cjs.getDetective().reset();
706
+ chunkOSPTERGK_cjs.getDetective().reset();
603
707
  }
604
708
  function explain(componentName2) {
605
709
  const explanation = explainStructured(componentName2);
606
710
  return explanation ? chunkJD4IJ4MN_cjs.formatExplanation(explanation) : void 0;
607
711
  }
608
712
  function explainStructured(componentName2) {
609
- return chunkJD4IJ4MN_cjs.explainEvents(componentName2, getEvents(), chunkDZ3BZ654_cjs.getDetective().lifecycleOf(componentName2));
713
+ return chunkJD4IJ4MN_cjs.explainEvents(componentName2, getEvents(), chunkOSPTERGK_cjs.getDetective().lifecycleOf(componentName2));
610
714
  }
611
715
  function getRenderProfile(scenario) {
612
716
  const remounts = {};
613
- for (const stats of chunkDZ3BZ654_cjs.getDetective().getComponentStats()) remounts[stats.name] = stats.remountCount;
717
+ for (const stats of chunkOSPTERGK_cjs.getDetective().getComponentStats()) remounts[stats.name] = stats.remountCount;
614
718
  return chunkH5RG2EPP_cjs.profileFromEvents(scenario, getEvents(), remounts);
615
719
  }
616
720
  function getInteractions() {
@@ -639,7 +743,7 @@ function measureInteraction(label2, action) {
639
743
  return tracker().measure(label2, action);
640
744
  }
641
745
  function getOpportunities(limit = 10) {
642
- const detective = chunkDZ3BZ654_cjs.getDetective();
746
+ const detective = chunkOSPTERGK_cjs.getDetective();
643
747
  const lifecycles = /* @__PURE__ */ new Map();
644
748
  for (const stats of detective.getComponentStats()) {
645
749
  lifecycles.set(stats.name, { remounts: stats.remountCount });