rerender-lens 0.2.0 → 0.3.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/CHANGELOG.md +69 -0
- package/README.md +146 -5
- package/dist/budget-COBu7jBU.d.cts +64 -0
- package/dist/budget-LkNjGtRc.d.ts +64 -0
- package/dist/cli.cjs +458 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +4 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.js +452 -0
- package/dist/cli.js.map +1 -0
- package/dist/devtools-BHGUUo-p.d.ts +244 -0
- package/dist/devtools-DmhWiWcN.d.cts +244 -0
- package/dist/index.cjs +652 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +40 -336
- package/dist/index.d.ts +40 -336
- package/dist/index.js +636 -24
- package/dist/index.js.map +1 -1
- package/dist/notifiers-BGQtWKfX.d.cts +90 -0
- package/dist/notifiers-BjjGSHpp.d.ts +90 -0
- package/dist/playwright.cjs +196 -0
- package/dist/playwright.cjs.map +1 -0
- package/dist/playwright.d.cts +35 -0
- package/dist/playwright.d.ts +35 -0
- package/dist/playwright.js +184 -0
- package/dist/playwright.js.map +1 -0
- package/dist/relay.cjs +166 -0
- package/dist/relay.cjs.map +1 -0
- package/dist/relay.d.cts +41 -0
- package/dist/relay.d.ts +41 -0
- package/dist/relay.js +161 -0
- package/dist/relay.js.map +1 -0
- package/dist/rerender-lens.iife.js +1717 -0
- package/dist/setup.cjs +1510 -0
- package/dist/setup.cjs.map +1 -0
- package/dist/setup.d.cts +2 -0
- package/dist/setup.d.ts +2 -0
- package/dist/setup.js +1508 -0
- package/dist/setup.js.map +1 -0
- package/dist/types-BzUEVkxJ.d.cts +177 -0
- package/dist/types-BzUEVkxJ.d.ts +177 -0
- package/dist/vite.cjs +113 -0
- package/dist/vite.cjs.map +1 -0
- package/dist/vite.d.cts +84 -0
- package/dist/vite.d.ts +84 -0
- package/dist/vite.js +103 -0
- package/dist/vite.js.map +1 -0
- package/dist/vitest-setup.cjs +1299 -0
- package/dist/vitest-setup.cjs.map +1 -0
- package/dist/vitest-setup.d.cts +8 -0
- package/dist/vitest-setup.d.ts +8 -0
- package/dist/vitest-setup.js +1297 -0
- package/dist/vitest-setup.js.map +1 -0
- package/dist/vitest.cjs +1360 -0
- package/dist/vitest.cjs.map +1 -0
- package/dist/vitest.d.cts +52 -0
- package/dist/vitest.d.ts +52 -0
- package/dist/vitest.js +1351 -0
- package/dist/vitest.js.map +1 -0
- package/package.json +84 -4
- package/panel/panel.css +418 -0
- package/panel/panel.html +12 -0
- package/panel/panel.js +3325 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,75 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
- **`npx rerender-lens panel`.** A dependency-free relay (`rerender-lens/relay`) that serves the
|
|
6
|
+
panel and forwards messages between apps and panels over server-sent events and POST, so the
|
|
7
|
+
panel works for any app on any origin without the extension or Vite: Next.js, Webpack, a remote
|
|
8
|
+
dev box, a phone. `createDevtoolsNotifier({ relay })`, `window.__RERENDER_LENS_RELAY__`, or
|
|
9
|
+
`RERENDER_LENS_RELAY` / `NEXT_PUBLIC_RERENDER_LENS_RELAY` with `rerender-lens/setup`. Commands
|
|
10
|
+
(Settings, highlight, replay, clear) go back to the app; the panel re-attaches when an app
|
|
11
|
+
reloads.
|
|
12
|
+
- **`rerender-lens/vitest`.** `setupFiles: ['rerender-lens/vitest/setup']` collects every test
|
|
13
|
+
file's reports; the reporter prints the run's ranked fixes, enforces a budget file, and can
|
|
14
|
+
write a panel-compatible export. `setupRerenderLens(options, { afterAll })` for custom options
|
|
15
|
+
and `failFast`.
|
|
16
|
+
- **`rerender-lens/playwright`.** `installRerenderLens(page, options)` injects the library at
|
|
17
|
+
document start (the bundle now ships as `dist/rerender-lens.iife.js`); `pullReports`,
|
|
18
|
+
`clearReports`, `expectWithinBudget`.
|
|
19
|
+
- A second copy of the library (another bundle on the same page) no longer wraps the hook
|
|
20
|
+
again, which doubled every report.
|
|
21
|
+
|
|
22
|
+
## 0.3.0
|
|
23
|
+
|
|
24
|
+
Highlights: the panel without the extension (Vite plugin `panel: true`), the `rerender-lens/vite`
|
|
25
|
+
plugin and `rerender-lens/setup` entry, sessions with before/after comparison, custom hook names,
|
|
26
|
+
updaters and effect-loop detection, full hook/context/state snapshots, CI budgets and a CLI, the
|
|
27
|
+
side panel and window modes, and a UI refresh. Details, newest first:
|
|
28
|
+
|
|
29
|
+
- **Panel without the extension.** `rerenderLens({ panel: true })` serves the panel from the Vite
|
|
30
|
+
dev server at `/__rerender-lens/`; the app publishes on a same-origin `BroadcastChannel`
|
|
31
|
+
(`createDevtoolsNotifier({ channel })`) and the panel sends commands back over it. The `panel/`
|
|
32
|
+
directory ships in the package. `pages` limits which HTML pages get the setup script.
|
|
33
|
+
- **Overhead readout.** `info().overhead` reports the time the library spent inspecting commits
|
|
34
|
+
(total and worst commit); the panel shows it in the status tooltip.
|
|
35
|
+
- **Panel polish.** Offenders and Fixes are windowed above 200 rows (sticky header, sortable);
|
|
36
|
+
an Offenders column chooser adds *Places* and *Last seen*; the search box takes `~text` to
|
|
37
|
+
search prop, hook, context and state values; per-component notes and a *Mute* toggle (muted
|
|
38
|
+
components leave the Fixes ranking and the summary strip, persisted per origin); roles and
|
|
39
|
+
ARIA states on the tree, lists, toast and settings dialog, Escape closes Settings.
|
|
40
|
+
- **CI tooling.** `rankFixes` / `formatFixes`, `summarizeReports` / `compareSummaries`, and
|
|
41
|
+
`checkBudget` / `toBudget` / `assertWithinBudget` in the package; the collector gains
|
|
42
|
+
`fixes()`, `summary()` and `assertWithinBudget()`, and `assertNoAvoidable()` now ends with the
|
|
43
|
+
ranked fixes. A `rerender-lens` CLI (`fixes`, `summary --out`, `compare`, `budget --init`)
|
|
44
|
+
works on panel exports and session files and exits non-zero on regressions or violations.
|
|
45
|
+
- **Source context.** The report shows the lines around where the element was created (DevTools
|
|
46
|
+
resources or a fetch of the module in side-panel mode).
|
|
47
|
+
- **Shareable links.** *Copy link* puts the report into a `panel.html?report=…` URL (deflated when
|
|
48
|
+
the browser can); anyone with the extension opens it without the page.
|
|
49
|
+
- **Custom hook names, opt-in** (`resolveHookNames`, also in Settings): hook changes and
|
|
50
|
+
snapshots read `useCounter › useCart › useState#0`. Like React DevTools, the library re-runs a
|
|
51
|
+
component type once with a stand-in dispatcher and reads the custom hooks off the call stack;
|
|
52
|
+
results are cached per type and a failing replay yields no names.
|
|
53
|
+
- **Updaters.** Every report names the components that scheduled the commit (`updaters`, from
|
|
54
|
+
React's updater tracking), so the Commits view shows "set by <X>" even when X is untracked.
|
|
55
|
+
- **Effect loops.** A commit scheduled right after the previous one by a component that rendered
|
|
56
|
+
in it is flagged `effect-after-commit` with the offending commit id; the report explains the
|
|
57
|
+
effect → setState pattern. Suspense boundaries resolving are labelled `suspense-resolved`.
|
|
58
|
+
- **Store advice.** A `useSyncExternalStore` snapshot with equal contents now gets Redux
|
|
59
|
+
(`shallowEqual` / `createSelector`) or Zustand (`useShallow`) advice when the hook chain
|
|
60
|
+
identifies the store hook.
|
|
61
|
+
- **Instances.** Reports carry the element `key`; the tree can group by instance
|
|
62
|
+
(`<Row key="a">`, `<Row #12>`) via the Instances toggle.
|
|
63
|
+
- **`rerender-lens/vite`**: a Vite plugin that starts the library before React in dev, with the
|
|
64
|
+
DevTools notifier, from one line in `vite.config.ts`. **`rerender-lens/setup`**: a side-effect
|
|
65
|
+
entry for Next.js `instrumentation-client.ts`, Webpack entry arrays and the like.
|
|
66
|
+
- CI lints the Firefox package with `web-ext lint`; `extension/store/QA.md` is the manual
|
|
67
|
+
checklist for what no test reaches. A docs site builds from the READMEs (`npm run build:docs`)
|
|
68
|
+
and deploys to GitHub Pages; `npm run docs:media` captures its screenshots and GIF.
|
|
69
|
+
- **Full state on every report.** `hookState` (every `useState` / `useReducer` /
|
|
70
|
+
`useSyncExternalStore` value), `contexts` (every context the component reads) and, for class
|
|
71
|
+
components, `state`. The panel's report shows Hooks, State and Contexts in full, with the
|
|
72
|
+
changed entries as prev → next; the console prints them too. `includeState: false` turns it off
|
|
73
|
+
(also in the panel's Settings).
|
|
5
74
|
- **Sessions.** Record, use the app, Stop; apply a fix; record again. The Sessions view compares
|
|
6
75
|
two sessions: avoidable re-renders per component with deltas, totals, wasted time, and which
|
|
7
76
|
suggested fixes went away. Summaries persist per origin; exports include them.
|
package/README.md
CHANGED
|
@@ -77,6 +77,67 @@ npm i -D rerender-lens
|
|
|
77
77
|
|
|
78
78
|
Peer dependency: `react >= 16.8`. Tested with React 18 and 19.
|
|
79
79
|
|
|
80
|
+
## One-line setup per bundler
|
|
81
|
+
|
|
82
|
+
**Vite** (recommended for Vite users: no extension injection, no permissions, one line):
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
// vite.config.ts
|
|
86
|
+
import { rerenderLens } from 'rerender-lens/vite';
|
|
87
|
+
|
|
88
|
+
export default defineConfig({
|
|
89
|
+
plugins: [react(), rerenderLens({ trackAllMemoized: true })],
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The plugin injects a module before your entry in dev (`vite build` is untouched) that calls
|
|
94
|
+
`init` with your options plus the DevTools notifier. Matchers can be strings or RegExps;
|
|
95
|
+
`devtools: false` skips the bridge, `applyInBuild: true` keeps it in builds, `pages: ['/']`
|
|
96
|
+
limits which HTML pages get it.
|
|
97
|
+
|
|
98
|
+
**No extension at all**: add `panel: true` and open `http://localhost:5173/__rerender-lens/` in a
|
|
99
|
+
second tab. The dev server serves the same panel the extension uses; the app publishes reports on
|
|
100
|
+
a same-origin `BroadcastChannel` and the panel sends settings and highlight commands back over it.
|
|
101
|
+
Panel state lives in `localStorage`. (`channel` renames the channel, `panel: '/some/path/'` moves
|
|
102
|
+
the mount.)
|
|
103
|
+
|
|
104
|
+
**Next.js** (App or Pages router): run it before React from the client instrumentation file:
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
// instrumentation-client.ts
|
|
108
|
+
import 'rerender-lens/setup';
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**Webpack / Rspack / others**: put the setup entry first:
|
|
112
|
+
|
|
113
|
+
```js
|
|
114
|
+
entry: ['rerender-lens/setup', './src/index.tsx'],
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`rerender-lens/setup` is a side-effect module that calls
|
|
118
|
+
`init({ trackAllMemoized: true, notifier: createDevtoolsNotifier() })` unless
|
|
119
|
+
`process.env.NODE_ENV === 'production'` or the page already runs the library. Use `configure()`
|
|
120
|
+
afterwards to change options, or the extension's Settings.
|
|
121
|
+
|
|
122
|
+
**The panel for any app, no extension, no Vite** (Next.js, Webpack, a remote dev box, a phone):
|
|
123
|
+
|
|
124
|
+
```sh
|
|
125
|
+
npx rerender-lens panel # http://127.0.0.1:4141/ serves the panel and relays messages
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Point the app at it and open the printed URL in any browser:
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
init({ trackAllMemoized: true, notifier: createDevtoolsNotifier({ relay: 'http://127.0.0.1:4141' }) });
|
|
132
|
+
// or, with rerender-lens/setup: RERENDER_LENS_RELAY=http://127.0.0.1:4141 (NEXT_PUBLIC_RERENDER_LENS_RELAY for Next.js)
|
|
133
|
+
// or, before the app loads: window.__RERENDER_LENS_RELAY__ = 'http://127.0.0.1:4141'
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
The app streams commands in over server-sent events and posts hello, reports and replies back; the
|
|
137
|
+
panel connects the same way (`panel.html?relay=…`), so Settings, highlight and replay all work.
|
|
138
|
+
Several apps or panels can share one relay; the panel re-attaches when an app reloads.
|
|
139
|
+
`--port` and `--host` change where it listens (`--host 0.0.0.0` for another machine).
|
|
140
|
+
|
|
80
141
|
## Setup
|
|
81
142
|
|
|
82
143
|
```ts
|
|
@@ -136,6 +197,10 @@ Every update of a tracked component produces a `RenderReport`:
|
|
|
136
197
|
| `propChanges` | one entry per changed prop with `path`, `kind`, `prev`, `next` |
|
|
137
198
|
| `stateChanges` | class components: `this.state` diff |
|
|
138
199
|
| `hookChanges` | `useState`, `useReducer`, `useSyncExternalStore` and `useContext` values that changed; context entries carry `provider` (who renders it) and `changedKeys` / `totalKeys` for object values |
|
|
200
|
+
| `hookState`, `contexts`, `state` | current value of every state hook, every context read, and class `this.state` (off with `includeState: false`) |
|
|
201
|
+
| `updaters` | components that scheduled the commit (`setState`, dispatch), from React's updater tracking |
|
|
202
|
+
| `commitCause`, `afterCommit` | `effect-after-commit` (an effect of the previous commit set state) or `suspense-resolved` |
|
|
203
|
+
| `key` | the element's `key`, when it has one |
|
|
139
204
|
| `parent` | nearest ancestor that rendered in the same commit, and why |
|
|
140
205
|
| `owner` | component that created the element (dev builds) |
|
|
141
206
|
| `path` | component ancestry from the root |
|
|
@@ -184,6 +249,71 @@ test('typing in the search box does not re-render the grid rows', async () => {
|
|
|
184
249
|
In Vitest or Jest, call `ensureDevtoolsHook()` (or `init`) from a `setupFiles` entry so the hook
|
|
185
250
|
exists before `react-dom` is imported by your tests.
|
|
186
251
|
|
|
252
|
+
### Vitest, whole suite
|
|
253
|
+
|
|
254
|
+
Two config lines collect every avoidable re-render across the run and print the ranked fixes at
|
|
255
|
+
the end; a budget file turns it into a gate:
|
|
256
|
+
|
|
257
|
+
```ts
|
|
258
|
+
// vitest.config.ts
|
|
259
|
+
test: {
|
|
260
|
+
setupFiles: ['rerender-lens/vitest/setup'],
|
|
261
|
+
reporters: ['default', ['rerender-lens/vitest', { budget: 'rerender-budget.json', exportTo: 'rerender-lens.json' }]],
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
The setup entry starts the library (all memoized components, silent) in every test file and hands
|
|
266
|
+
the file's reports to the reporter; the reporter prints `rerender-lens: N reports, M avoidable`
|
|
267
|
+
with the fixes, fails the run on budget violations, and can write a panel-compatible export
|
|
268
|
+
(`Sessions > Import` in the extension). For other options, or per-test assertions, write your own
|
|
269
|
+
setup file:
|
|
270
|
+
|
|
271
|
+
```ts
|
|
272
|
+
import { afterAll } from 'vitest';
|
|
273
|
+
import { setupRerenderLens } from 'rerender-lens/vitest';
|
|
274
|
+
export const lens = setupRerenderLens({ include: [/^Grid/], failFast: true }, { afterAll });
|
|
275
|
+
// lens.collector.assertNoAvoidable() inside a test
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### Playwright, real browser
|
|
279
|
+
|
|
280
|
+
```ts
|
|
281
|
+
import { installRerenderLens, pullReports, expectWithinBudget } from 'rerender-lens/playwright';
|
|
282
|
+
|
|
283
|
+
test('search does not re-render the grid', async ({ page }) => {
|
|
284
|
+
await installRerenderLens(page, { include: ['ProductRow'] }); // before goto: runs before React
|
|
285
|
+
await page.goto('/products');
|
|
286
|
+
await page.getByRole('searchbox').fill('abc');
|
|
287
|
+
expectWithinBudget(await pullReports(page), { '*': 0 }); // throws with the ranked fixes
|
|
288
|
+
});
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
`installRerenderLens` injects the same bundle the extension uses at document start, so it works on
|
|
292
|
+
any build the browser can load (production builds are flagged; names may be minified). Pass
|
|
293
|
+
`relay: 'http://127.0.0.1:4141'` to watch the run in the panel, `clearReports(page)` between
|
|
294
|
+
scenarios.
|
|
295
|
+
|
|
296
|
+
### Budgets and comparisons in CI
|
|
297
|
+
|
|
298
|
+
```ts
|
|
299
|
+
// allow known offenders, fail on anything new or worse
|
|
300
|
+
collector.assertWithinBudget(JSON.parse(readFileSync('rerender-budget.json', 'utf8')));
|
|
301
|
+
// produce the baseline once: writeFileSync('rerender-budget.json', JSON.stringify(toBudget(collector.reports)))
|
|
302
|
+
collector.fixes(); // ranked fixes, most re-renders removed first
|
|
303
|
+
collector.summary('after'); // a session summary, comparable with compareSummaries()
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
The `rerender-lens` CLI does the same with files from the extension (Export) or from `summary()`:
|
|
307
|
+
|
|
308
|
+
```sh
|
|
309
|
+
npx rerender-lens fixes export.json # ranked fixes
|
|
310
|
+
npx rerender-lens summary export.json --out before.json
|
|
311
|
+
npx rerender-lens compare before.json after.json # per-component deltas; exit 1 on regressions
|
|
312
|
+
npx rerender-lens budget export.json --init > rerender-budget.json
|
|
313
|
+
npx rerender-lens budget export.json rerender-budget.json # exit 1 when a component exceeds its budget
|
|
314
|
+
npx rerender-lens panel [--port 4141] [--host 127.0.0.1] # the panel + relay for any app (see above)
|
|
315
|
+
```
|
|
316
|
+
|
|
187
317
|
## Track a single component from the inside
|
|
188
318
|
|
|
189
319
|
Works anywhere without `init`, for example inside library packages or Storybook:
|
|
@@ -236,10 +366,18 @@ disable(): void
|
|
|
236
366
|
isEnabled(): boolean
|
|
237
367
|
track(component, name?): component
|
|
238
368
|
ensureDevtoolsHook(): hook // create the global hook early (test setup files)
|
|
369
|
+
// 'rerender-lens/vite': rerenderLens(options), renderSetupModule(options)
|
|
370
|
+
// 'rerender-lens/setup': side-effect entry (init with defaults outside production)
|
|
371
|
+
// 'rerender-lens/relay': createRelayServer({ port?, host? }) — what `rerender-lens panel` runs
|
|
372
|
+
// 'rerender-lens/playwright': installRerenderLens(page, options?), pullReports(page), clearReports(page), expectWithinBudget(reports, budget)
|
|
373
|
+
// 'rerender-lens/vitest': setupRerenderLens(options?, { afterAll }), default reporter ({ budget?, exportTo?, limit? }); 'rerender-lens/vitest/setup'
|
|
239
374
|
useWhyRerender(name, values, options?)
|
|
240
|
-
createCollector(): { reports, avoidable, notifier, clear, assertNoAvoidable }
|
|
375
|
+
createCollector(): { reports, avoidable, notifier, clear, assertNoAvoidable, fixes, summary, assertWithinBudget }
|
|
376
|
+
rankFixes(reports), formatFixes(reports) // ranked fixes
|
|
377
|
+
summarizeReports(reports), compareSummaries(a, b), formatComparison(c), parseExport(json)
|
|
378
|
+
checkBudget(reports, budget), toBudget(reports), assertWithinBudget(reports, budget)
|
|
241
379
|
combineNotifiers(...notifiers): Notifier
|
|
242
|
-
createDevtoolsNotifier({ bufferSize?, target?, maxDepth?, flashAvoidable? }): Notifier
|
|
380
|
+
createDevtoolsNotifier({ bufferSize?, target?, maxDepth?, flashAvoidable?, channel?, relay? }): Notifier
|
|
243
381
|
getRenderers(), isProductionReact() // what react-dom registered on the DevTools hook
|
|
244
382
|
serializeOptions(o), deserializeOptions(o) // Options <-> JSON-safe form used by the bridge
|
|
245
383
|
VERSION
|
|
@@ -254,6 +392,8 @@ deepEqual(a, b), diffRecords(prev, next), classify(prev, next)
|
|
|
254
392
|
| `trackAllComponents` | `false` | track everything (noisy) |
|
|
255
393
|
| `include` / `exclude` | | display-name matchers |
|
|
256
394
|
| `trackHooks` | `true` | diff hook state and contexts |
|
|
395
|
+
| `includeState` | `true` | put every hook, context and class state value on each report |
|
|
396
|
+
| `resolveHookNames` | `false` | label hooks with the custom hooks that own them (`useCart › useState#0`); re-runs each component type once |
|
|
257
397
|
| `logAll` | `false` | print non-avoidable reports too |
|
|
258
398
|
| `silent` | `false` | never print; notifier still runs |
|
|
259
399
|
| `notifier` | | receives every `RenderReport` |
|
|
@@ -291,9 +431,10 @@ npm --prefix examples/vite-react install
|
|
|
291
431
|
npm run dev:example
|
|
292
432
|
```
|
|
293
433
|
|
|
294
|
-
`http://localhost:5199/` runs the library
|
|
295
|
-
`http://localhost:5199/plain.html`
|
|
296
|
-
extension's *Inject the library* mode.
|
|
434
|
+
`http://localhost:5199/` runs the library through the Vite plugin (console, extension, and the
|
|
435
|
+
built-in panel at `http://localhost:5199/__rerender-lens/`). `http://localhost:5199/plain.html`
|
|
436
|
+
is the same app without the library, for trying the extension's *Inject the library* mode.
|
|
437
|
+
`src/rerender-lens.ts` shows the manual setup for apps without the plugin.
|
|
297
438
|
|
|
298
439
|
## Migrating from why-did-you-render
|
|
299
440
|
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { R as RenderReport } from './types-BzUEVkxJ.cjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Concrete fixes derived from reports, ranked by how many avoidable re-renders each one removes.
|
|
5
|
+
* The same logic drives the extension's Fixes view; here it is typed on `RenderReport` for tests,
|
|
6
|
+
* CI budgets and the CLI. Pure.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
type FixKind = 'memo' | 'useCallback' | 'useMemo' | 'useMemoElement' | 'children' | 'contextValue' | 'splitContext' | 'storeSnapshot' | 'bailout';
|
|
10
|
+
interface Fix {
|
|
11
|
+
kind: FixKind;
|
|
12
|
+
/** Where the change goes (the component that must be edited, or `<Ctx>.Provider`). */
|
|
13
|
+
owner: string;
|
|
14
|
+
/** The component whose re-render this removes. */
|
|
15
|
+
target: string;
|
|
16
|
+
prop: string | null;
|
|
17
|
+
label: string;
|
|
18
|
+
detail: string;
|
|
19
|
+
}
|
|
20
|
+
interface RankedFix extends Fix {
|
|
21
|
+
key: string;
|
|
22
|
+
/** Avoidable re-renders this fix removes. */
|
|
23
|
+
count: number;
|
|
24
|
+
components: Record<string, number>;
|
|
25
|
+
}
|
|
26
|
+
declare function fixesFor(r: RenderReport): Fix[];
|
|
27
|
+
declare const fixKey: (f: Fix) => string;
|
|
28
|
+
/** Every fix across `reports`, most re-renders removed first. */
|
|
29
|
+
declare function rankFixes(reports: RenderReport[]): RankedFix[];
|
|
30
|
+
/** Multi-line text for a test failure or a CI log. */
|
|
31
|
+
declare function formatFixes(reports: RenderReport[], limit?: number): string;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Re-render budgets for CI: a JSON baseline of allowed avoidable re-renders per component, checked
|
|
35
|
+
* against a collector's reports (like a bundle-size budget). Pure; reading and writing the file is
|
|
36
|
+
* up to the test runner (`fs`), so this works in Node and in the browser.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/** `{ "*": 0, "ProductRow": 3 }`: max avoidable re-renders per component; `*` is the default. */
|
|
40
|
+
type Budget = Record<string, number>;
|
|
41
|
+
interface BudgetViolation {
|
|
42
|
+
component: string;
|
|
43
|
+
avoidable: number;
|
|
44
|
+
allowed: number;
|
|
45
|
+
}
|
|
46
|
+
interface BudgetResult {
|
|
47
|
+
ok: boolean;
|
|
48
|
+
violations: BudgetViolation[];
|
|
49
|
+
/** Components with headroom left, so budgets can be tightened. */
|
|
50
|
+
slack: {
|
|
51
|
+
component: string;
|
|
52
|
+
avoidable: number;
|
|
53
|
+
allowed: number;
|
|
54
|
+
}[];
|
|
55
|
+
counts: Record<string, number>;
|
|
56
|
+
}
|
|
57
|
+
declare function avoidableCounts(reports: RenderReport[]): Record<string, number>;
|
|
58
|
+
declare function checkBudget(reports: RenderReport[], budget: Budget | number): BudgetResult;
|
|
59
|
+
/** A budget that exactly matches the current counts (the baseline to commit). */
|
|
60
|
+
declare function toBudget(reports: RenderReport[]): Budget;
|
|
61
|
+
/** Throws with the violations and the ranked fixes; use it from a test. */
|
|
62
|
+
declare function assertWithinBudget(reports: RenderReport[], budget: Budget | number): BudgetResult;
|
|
63
|
+
|
|
64
|
+
export { type Budget as B, type Fix as F, type RankedFix as R, type BudgetResult as a, type BudgetViolation as b, checkBudget as c, type FixKind as d, assertWithinBudget as e, formatFixes as f, avoidableCounts as g, fixKey as h, fixesFor as i, rankFixes as r, toBudget as t };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { R as RenderReport } from './types-BzUEVkxJ.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Concrete fixes derived from reports, ranked by how many avoidable re-renders each one removes.
|
|
5
|
+
* The same logic drives the extension's Fixes view; here it is typed on `RenderReport` for tests,
|
|
6
|
+
* CI budgets and the CLI. Pure.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
type FixKind = 'memo' | 'useCallback' | 'useMemo' | 'useMemoElement' | 'children' | 'contextValue' | 'splitContext' | 'storeSnapshot' | 'bailout';
|
|
10
|
+
interface Fix {
|
|
11
|
+
kind: FixKind;
|
|
12
|
+
/** Where the change goes (the component that must be edited, or `<Ctx>.Provider`). */
|
|
13
|
+
owner: string;
|
|
14
|
+
/** The component whose re-render this removes. */
|
|
15
|
+
target: string;
|
|
16
|
+
prop: string | null;
|
|
17
|
+
label: string;
|
|
18
|
+
detail: string;
|
|
19
|
+
}
|
|
20
|
+
interface RankedFix extends Fix {
|
|
21
|
+
key: string;
|
|
22
|
+
/** Avoidable re-renders this fix removes. */
|
|
23
|
+
count: number;
|
|
24
|
+
components: Record<string, number>;
|
|
25
|
+
}
|
|
26
|
+
declare function fixesFor(r: RenderReport): Fix[];
|
|
27
|
+
declare const fixKey: (f: Fix) => string;
|
|
28
|
+
/** Every fix across `reports`, most re-renders removed first. */
|
|
29
|
+
declare function rankFixes(reports: RenderReport[]): RankedFix[];
|
|
30
|
+
/** Multi-line text for a test failure or a CI log. */
|
|
31
|
+
declare function formatFixes(reports: RenderReport[], limit?: number): string;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Re-render budgets for CI: a JSON baseline of allowed avoidable re-renders per component, checked
|
|
35
|
+
* against a collector's reports (like a bundle-size budget). Pure; reading and writing the file is
|
|
36
|
+
* up to the test runner (`fs`), so this works in Node and in the browser.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/** `{ "*": 0, "ProductRow": 3 }`: max avoidable re-renders per component; `*` is the default. */
|
|
40
|
+
type Budget = Record<string, number>;
|
|
41
|
+
interface BudgetViolation {
|
|
42
|
+
component: string;
|
|
43
|
+
avoidable: number;
|
|
44
|
+
allowed: number;
|
|
45
|
+
}
|
|
46
|
+
interface BudgetResult {
|
|
47
|
+
ok: boolean;
|
|
48
|
+
violations: BudgetViolation[];
|
|
49
|
+
/** Components with headroom left, so budgets can be tightened. */
|
|
50
|
+
slack: {
|
|
51
|
+
component: string;
|
|
52
|
+
avoidable: number;
|
|
53
|
+
allowed: number;
|
|
54
|
+
}[];
|
|
55
|
+
counts: Record<string, number>;
|
|
56
|
+
}
|
|
57
|
+
declare function avoidableCounts(reports: RenderReport[]): Record<string, number>;
|
|
58
|
+
declare function checkBudget(reports: RenderReport[], budget: Budget | number): BudgetResult;
|
|
59
|
+
/** A budget that exactly matches the current counts (the baseline to commit). */
|
|
60
|
+
declare function toBudget(reports: RenderReport[]): Budget;
|
|
61
|
+
/** Throws with the violations and the ranked fixes; use it from a test. */
|
|
62
|
+
declare function assertWithinBudget(reports: RenderReport[], budget: Budget | number): BudgetResult;
|
|
63
|
+
|
|
64
|
+
export { type Budget as B, type Fix as F, type RankedFix as R, type BudgetResult as a, type BudgetViolation as b, checkBudget as c, type FixKind as d, assertWithinBudget as e, formatFixes as f, avoidableCounts as g, fixKey as h, fixesFor as i, rankFixes as r, toBudget as t };
|