bippy 0.6.0 → 0.6.1-dev.03b829a
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/LICENSE +1 -1
- package/README.md +126 -136
- package/dist/core.cjs +1 -1
- package/dist/core.d.cts +45 -60
- package/dist/core.d.ts +45 -60
- package/dist/core.js +1 -1
- package/dist/core2.cjs +9 -0
- package/dist/core2.d.cts +11 -3
- package/dist/core2.d.ts +11 -3
- package/dist/core2.js +9 -0
- package/dist/errors.d.cts +447 -0
- package/dist/errors.d.ts +447 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +11 -3
- package/dist/index.d.ts +11 -3
- package/dist/index.iife.js +1 -1
- package/dist/index.js +1 -1
- package/dist/install-hook-only.cjs +1 -1
- package/dist/install-hook-only.d.cts +9 -1
- package/dist/install-hook-only.d.ts +9 -1
- package/dist/install-hook-only.iife.js +1 -1
- package/dist/install-hook-only.js +1 -1
- package/dist/rdt-hook.cjs +1 -1
- package/dist/rdt-hook.js +1 -1
- package/dist/source.cjs +14 -4
- package/dist/source.d.cts +27 -9
- package/dist/source.d.ts +27 -9
- package/dist/source.js +14 -4
- package/package.json +32 -22
- package/src/core.ts +368 -314
- package/src/errors.ts +34 -0
- package/src/install-hook-only.ts +2 -2
- package/src/rdt-hook.ts +150 -153
- package/src/react-internals/generated/react-work-tags.ts +318 -0
- package/src/react-internals/index.ts +67 -0
- package/src/react-internals/semver.ts +78 -0
- package/src/react-internals/types.ts +269 -0
- package/src/source/constants.ts +1 -1
- package/src/source/error-stack.ts +11 -0
- package/src/source/get-display-name-from-source.ts +1 -1
- package/src/source/get-source.ts +4 -3
- package/src/source/index.ts +9 -1
- package/src/source/inspect-hooks.ts +221 -169
- package/src/source/owner-stack.ts +88 -114
- package/src/source/parse-debug-stack.ts +4 -4
- package/src/source/parse-hook-names.ts +1 -1
- package/src/source/parse-stack.ts +1 -1
- package/src/source/symbolication.ts +488 -88
- package/dist/get-source.cjs +0 -19
- package/dist/get-source.js +0 -19
- package/dist/react-refresh.cjs +0 -9
- package/dist/react-refresh.d.cts +0 -66
- package/dist/react-refresh.d.ts +0 -66
- package/dist/react-refresh.js +0 -9
- package/dist/unsubscribe.d.cts +0 -298
- package/dist/unsubscribe.d.ts +0 -298
- package/src/react-refresh/constants.ts +0 -9
- package/src/react-refresh/detect-hmr-transport.ts +0 -33
- package/src/react-refresh/index.ts +0 -173
- package/src/react-refresh/metro-hmr-transport.ts +0 -188
- package/src/react-refresh/next-webpack-hmr-transport.ts +0 -72
- package/src/react-refresh/normalize-hmr-file-path.ts +0 -24
- package/src/react-refresh/types.ts +0 -7
- package/src/react-refresh/vite-hmr-transport.ts +0 -116
- package/src/types.ts +0 -438
- package/src/unsubscribe.ts +0 -17
package/LICENSE
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Copyright 2024 Aiden Bai
|
|
1
|
+
Copyright 2024-present Aiden Bai
|
|
2
2
|
|
|
3
3
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
4
|
|
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
> [!WARNING]
|
|
2
2
|
> ⚠️⚠️⚠️ **this project may break production apps and cause unexpected behavior** ⚠️⚠️⚠️
|
|
3
3
|
>
|
|
4
|
-
> this project uses react internals, which can change at any time.
|
|
4
|
+
> this project uses react internals, which can change at any time. we don't recommend depending on internals unless you really, _really_ have to. by proceeding, you acknowledge the risk of breaking your own code or apps that use your code.
|
|
5
5
|
|
|
6
6
|
# <img src="https://github.com/aidenybai/bippy/blob/main/.github/public/bippy.png?raw=true" width="60" align="center" /> bippy
|
|
7
7
|
|
|
@@ -10,17 +10,17 @@
|
|
|
10
10
|
|
|
11
11
|
bippy is a toolkit to **hack into react internals**
|
|
12
12
|
|
|
13
|
-
by default, you cannot access react internals. bippy bypasses this by
|
|
13
|
+
by default, you cannot access react internals. bippy bypasses this by “pretending” to be react devtools, giving you access to the fiber tree and other internals.
|
|
14
14
|
|
|
15
|
-
- works outside of react
|
|
15
|
+
- works outside of react: no react code modification needed
|
|
16
16
|
- utility functions that work across modern react (v17-19)
|
|
17
17
|
- no prior react source code knowledge required
|
|
18
18
|
|
|
19
19
|
```jsx
|
|
20
|
-
import {
|
|
20
|
+
import { instrument, traverseFiber } from "bippy"; // must be imported BEFORE react
|
|
21
21
|
|
|
22
22
|
instrument({
|
|
23
|
-
onCommitFiberRoot
|
|
23
|
+
onCommitFiberRoot(rendererID, root) {
|
|
24
24
|
traverseFiber(root.current, (fiber) => {
|
|
25
25
|
// prints every fiber in the current React tree
|
|
26
26
|
console.log("fiber:", fiber);
|
|
@@ -33,7 +33,7 @@ instrument({
|
|
|
33
33
|
|
|
34
34
|
bippy allows you to **access** and **use** react fibers **outside** of react components.
|
|
35
35
|
|
|
36
|
-
a react fiber is a
|
|
36
|
+
a react fiber is a “unit of execution.” this means react will do something based on the data in a fiber. each fiber either represents a composite (function/class component) or a host (dom element).
|
|
37
37
|
|
|
38
38
|
> here is a [live visualization](https://jser.pro/ddir/rie?reactVersion=18.3.1&snippetKey=hq8jm2ylzb9u8eh468) of what the fiber tree looks like, and here is a [deep dive article](https://jser.dev/2023-07-18-how-react-rerenders/).
|
|
39
39
|
|
|
@@ -74,14 +74,14 @@ here, the `child`, `sibling`, and `return` properties are pointers to other fibe
|
|
|
74
74
|
|
|
75
75
|
additionally, `memoizedProps`, `memoizedState`, and `dependencies` are the fiber's props, state, and contexts.
|
|
76
76
|
|
|
77
|
-
while all of the information is there, it's
|
|
77
|
+
while all of the information is there, it's awkward to work with, and changes frequently across different versions of react. bippy simplifies this by providing utility functions like:
|
|
78
78
|
|
|
79
79
|
- `traverseRenderedFibers` to detect renders and `traverseFiber` to traverse the overall fiber tree
|
|
80
80
|
- _(instead of `child`, `sibling`, and `return` pointers)_
|
|
81
81
|
- `traverseProps`, `traverseState`, and `traverseContexts` to traverse the fiber's props, state, and contexts
|
|
82
82
|
- _(instead of `memoizedProps`, `memoizedState`, and `dependencies`)_
|
|
83
83
|
|
|
84
|
-
however,
|
|
84
|
+
however, react doesn't expose fibers to you directly. so, we have to hack our way around to access them.
|
|
85
85
|
|
|
86
86
|
luckily, react [reads from a property](https://github.com/facebook/react/blob/6a4b46cd70d2672bc4be59dcb5b8dede22ed0cef/packages/react-reconciler/src/ReactFiberDevToolsHook.js#L48) in the window object: `window.__REACT_DEVTOOLS_GLOBAL_HOOK__` and runs handlers on it when certain events happen. this property must exist before react's bundle is executed. this is intended for react devtools, but we can use it to our advantage.
|
|
87
87
|
|
|
@@ -107,9 +107,7 @@ interface __REACT_DEVTOOLS_GLOBAL_HOOK__ {
|
|
|
107
107
|
bippy works by monkey-patching `window.__REACT_DEVTOOLS_GLOBAL_HOOK__` with our own custom handlers. bippy simplifies this by providing utility functions like:
|
|
108
108
|
|
|
109
109
|
- `instrument` to safely patch `window.__REACT_DEVTOOLS_GLOBAL_HOOK__`
|
|
110
|
-
- _(instead of directly mutating `onCommitFiberRoot`,
|
|
111
|
-
- `secure` to wrap your handlers in a try/catch and determine if handlers are safe to run
|
|
112
|
-
- _(instead of rawdogging `window.__REACT_DEVTOOLS_GLOBAL_HOOK__` handlers, which may crash your app)_
|
|
110
|
+
- _(instead of directly mutating `onCommitFiberRoot`, …)_
|
|
113
111
|
- `traverseRenderedFibers` to traverse the fiber tree and determine which fibers have actually rendered
|
|
114
112
|
- _(instead of `child`, `sibling`, and `return` pointers)_
|
|
115
113
|
- `traverseFiber` to traverse the fiber tree, regardless of whether it has rendered
|
|
@@ -121,13 +119,13 @@ bippy works by monkey-patching `window.__REACT_DEVTOOLS_GLOBAL_HOOK__` with our
|
|
|
121
119
|
|
|
122
120
|
we recommend installing via npm.
|
|
123
121
|
|
|
124
|
-
this package
|
|
122
|
+
import this package before your react app runs. it adds a special object to the global scope that react reports its internals to (react devtools uses the same mechanism). as soon as react loads and attaches, bippy starts collecting data about what is going on in react's internals.
|
|
125
123
|
|
|
126
124
|
```shell
|
|
127
125
|
npm install bippy
|
|
128
126
|
```
|
|
129
127
|
|
|
130
|
-
since bippy
|
|
128
|
+
since bippy must load before react, some bundlers need specific configuration to get the import order right.
|
|
131
129
|
|
|
132
130
|
### next.js
|
|
133
131
|
|
|
@@ -153,9 +151,9 @@ import { createRoot } from "react-dom/client";
|
|
|
153
151
|
// ... rest of your code
|
|
154
152
|
```
|
|
155
153
|
|
|
156
|
-
the import order is critical: bippy
|
|
154
|
+
the import order is critical: import bippy before any react packages.
|
|
157
155
|
|
|
158
|
-
> **note for library maintainers**: if you're building a library and want to define your own utility functions while minimizing bundle size, you can use `bippy/install-hook-only` (~90 bytes) instead of the main `bippy` export. this only installs the react devtools hook without importing any utility functions, allowing you to import only what you need from `bippy/core` or define your own fiber utilities. that said, the full `bippy` package is only ~
|
|
156
|
+
> **note for library maintainers**: if you're building a library and want to define your own utility functions while minimizing bundle size, you can use `bippy/install-hook-only` (~90 bytes) instead of the main `bippy` export. this only installs the react devtools hook without importing any utility functions, allowing you to import only what you need from `bippy/core` or define your own fiber utilities. that said, the full `bippy` package is only ~4 KB gzipped, so bundle size is rarely a concern.
|
|
159
157
|
|
|
160
158
|
> ```typescript
|
|
161
159
|
> import "bippy/install-hook-only"; // only installs the hook
|
|
@@ -170,29 +168,32 @@ the import order is critical: bippy must be imported before any react packages.
|
|
|
170
168
|
|
|
171
169
|
### instrument
|
|
172
170
|
|
|
173
|
-
patches `window.__REACT_DEVTOOLS_GLOBAL_HOOK__` with your handlers.
|
|
171
|
+
patches `window.__REACT_DEVTOOLS_GLOBAL_HOOK__` with your handlers. import bippy before react, and call `instrument` before any other methods.
|
|
174
172
|
|
|
175
|
-
|
|
173
|
+
bippy patches each hook event once and dispatches it to a set of listeners, so multiple `instrument` calls compose instead of replacing each other. `instrument` returns an unsubscribe function that removes exactly the handlers you registered (also a `Disposable`, so it works with `using`).
|
|
176
174
|
|
|
177
175
|
```typescript
|
|
178
|
-
import { instrument
|
|
176
|
+
import { instrument } from "bippy"; // must be imported BEFORE react
|
|
179
177
|
import * as React from "react";
|
|
180
178
|
|
|
181
|
-
instrument(
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
)
|
|
179
|
+
const unsubscribe = instrument({
|
|
180
|
+
onCommitFiberRoot(rendererID, root) {
|
|
181
|
+
console.log("root ready to commit", root);
|
|
182
|
+
},
|
|
183
|
+
onPostCommitFiberRoot(rendererID, root) {
|
|
184
|
+
console.log("root with effects committed", root);
|
|
185
|
+
},
|
|
186
|
+
onCommitFiberUnmount(rendererID, fiber) {
|
|
187
|
+
console.log("fiber unmounted", fiber);
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// later, stop listening (other instrument() subscribers keep working)
|
|
192
|
+
unsubscribe();
|
|
194
193
|
```
|
|
195
194
|
|
|
195
|
+
instrumentation, React DevTools, and hook-listener failures propagate unchanged. callback dispatch stops at the failure so the caller controls error handling. failures created by bippy, such as unsupported hooks and source-map timeouts, use the exported `BippyError` subclasses.
|
|
196
|
+
|
|
196
197
|
### getRDTHook
|
|
197
198
|
|
|
198
199
|
returns the `window.__REACT_DEVTOOLS_GLOBAL_HOOK__` object. great for advanced use cases, such as accessing or modifying the `renderers` property.
|
|
@@ -209,18 +210,16 @@ console.log(hook);
|
|
|
209
210
|
not every fiber in the fiber tree renders. `traverseRenderedFibers` allows you to traverse the fiber tree and determine which fibers have actually rendered.
|
|
210
211
|
|
|
211
212
|
```typescript
|
|
212
|
-
import { instrument,
|
|
213
|
+
import { instrument, traverseRenderedFibers } from "bippy"; // must be imported BEFORE react
|
|
213
214
|
import * as React from "react";
|
|
214
215
|
|
|
215
|
-
instrument(
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
}),
|
|
223
|
-
);
|
|
216
|
+
instrument({
|
|
217
|
+
onCommitFiberRoot(rendererID, root) {
|
|
218
|
+
traverseRenderedFibers(root, (fiber) => {
|
|
219
|
+
console.log("fiber rendered", fiber);
|
|
220
|
+
});
|
|
221
|
+
},
|
|
222
|
+
});
|
|
224
223
|
```
|
|
225
224
|
|
|
226
225
|
### traverseFiber
|
|
@@ -228,18 +227,16 @@ instrument(
|
|
|
228
227
|
calls a callback on every fiber in the fiber tree.
|
|
229
228
|
|
|
230
229
|
```typescript
|
|
231
|
-
import { instrument,
|
|
230
|
+
import { instrument, traverseFiber } from "bippy"; // must be imported BEFORE react
|
|
232
231
|
import * as React from "react";
|
|
233
232
|
|
|
234
|
-
instrument(
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
}),
|
|
242
|
-
);
|
|
233
|
+
instrument({
|
|
234
|
+
onCommitFiberRoot(rendererID, root) {
|
|
235
|
+
traverseFiber(root.current, (fiber) => {
|
|
236
|
+
console.log(fiber);
|
|
237
|
+
});
|
|
238
|
+
},
|
|
239
|
+
});
|
|
243
240
|
```
|
|
244
241
|
|
|
245
242
|
### traverseProps
|
|
@@ -342,16 +339,14 @@ import { memo } from "react";
|
|
|
342
339
|
const RealComponent = () => {
|
|
343
340
|
return <div>hello</div>;
|
|
344
341
|
};
|
|
345
|
-
const MemoizedComponent = memo(
|
|
346
|
-
return <div>hello</div>;
|
|
347
|
-
});
|
|
342
|
+
const MemoizedComponent = memo(RealComponent);
|
|
348
343
|
|
|
349
344
|
console.log(getType(fiberForMemoizedComponent) === RealComponent);
|
|
350
345
|
```
|
|
351
346
|
|
|
352
347
|
### getNearestHostFiber / getNearestHostFibers
|
|
353
348
|
|
|
354
|
-
getNearestHostFiber returns the closest host fiber above or below a given fiber. getNearestHostFibers
|
|
349
|
+
`getNearestHostFiber` returns the closest host fiber above or below a given fiber. `getNearestHostFibers` returns all host fibers associated with the provided fiber and its subtree.
|
|
355
350
|
|
|
356
351
|
```jsx
|
|
357
352
|
import { getNearestHostFiber, getNearestHostFibers } from "bippy";
|
|
@@ -433,28 +428,6 @@ const latestFiber = getLatestFiber(getFiberFromHostInstance(document.querySelect
|
|
|
433
428
|
console.log(latestFiber);
|
|
434
429
|
```
|
|
435
430
|
|
|
436
|
-
### getFiberSource
|
|
437
|
-
|
|
438
|
-
returns the source code location of a fiber.
|
|
439
|
-
|
|
440
|
-
```typescript
|
|
441
|
-
import { getFiberSource } from "bippy/source";
|
|
442
|
-
|
|
443
|
-
const fiber = getFiberFromHostInstance(document.querySelector("div"));
|
|
444
|
-
|
|
445
|
-
console.log(await getFiberSource(fiber));
|
|
446
|
-
```
|
|
447
|
-
|
|
448
|
-
> note: in order to get accurate source locations in react >= 19, you need to add this in your `tsconfig.json`:
|
|
449
|
-
>
|
|
450
|
-
> ```json
|
|
451
|
-
> {
|
|
452
|
-
> "compilerOptions": {
|
|
453
|
-
> "jsxImportSource": "bippy/dist"
|
|
454
|
-
> }
|
|
455
|
-
> }
|
|
456
|
-
> ```
|
|
457
|
-
|
|
458
431
|
### overrideProps
|
|
459
432
|
|
|
460
433
|
overrides component props at runtime by modifying the fiber's props.
|
|
@@ -472,7 +445,7 @@ overrideProps(fiber, {
|
|
|
472
445
|
});
|
|
473
446
|
```
|
|
474
447
|
|
|
475
|
-
the function accepts a fiber and a partial object containing the props to override.
|
|
448
|
+
the function accepts a fiber and a partial object containing the props to override. bippy automatically flattens nested objects into property paths.
|
|
476
449
|
|
|
477
450
|
### overrideHookState
|
|
478
451
|
|
|
@@ -493,7 +466,7 @@ overrideHookState(fiber, 1, {
|
|
|
493
466
|
});
|
|
494
467
|
```
|
|
495
468
|
|
|
496
|
-
the hook id parameter corresponds to the order of hooks in the component (0-indexed).
|
|
469
|
+
the hook id parameter corresponds to the order of hooks in the component (0-indexed). pass either a primitive value or an object for nested state updates.
|
|
497
470
|
|
|
498
471
|
### overrideContext
|
|
499
472
|
|
|
@@ -519,26 +492,13 @@ the function traverses up the fiber tree to find the context provider matching t
|
|
|
519
492
|
|
|
520
493
|
### getSource
|
|
521
494
|
|
|
522
|
-
gets the source code location of
|
|
495
|
+
gets the source code location of any fiber with development source metadata. resolution is based on fiber debug data and source maps, so it is independent of the renderer's host instances and works with DOM, native, terminal, canvas, PDF, and custom reconcilers.
|
|
523
496
|
|
|
524
497
|
```typescript
|
|
525
498
|
import { getSource } from "bippy/source";
|
|
526
499
|
|
|
527
|
-
|
|
528
|
-
const
|
|
529
|
-
|
|
530
|
-
// get nearest composite fiber up the tree
|
|
531
|
-
const compositeFiber = traverseFiber(
|
|
532
|
-
hostFiber,
|
|
533
|
-
(fiber) => {
|
|
534
|
-
if (isCompositeFiber(fiber)) {
|
|
535
|
-
return fiber;
|
|
536
|
-
}
|
|
537
|
-
},
|
|
538
|
-
true,
|
|
539
|
-
);
|
|
540
|
-
|
|
541
|
-
const source = await getSource(compositeFiber);
|
|
500
|
+
const fiber = getFiberFromHostInstance(hostInstance);
|
|
501
|
+
const source = await getSource(fiber);
|
|
542
502
|
// {
|
|
543
503
|
// columnNumber: 12,
|
|
544
504
|
// fileName: 'path/to/file.tsx',
|
|
@@ -549,17 +509,38 @@ const source = await getSource(compositeFiber);
|
|
|
549
509
|
> **caveats:**
|
|
550
510
|
>
|
|
551
511
|
> - only available in dev mode
|
|
552
|
-
> -
|
|
553
|
-
> - captures the location where the
|
|
554
|
-
> -
|
|
555
|
-
> -
|
|
512
|
+
> - source availability is controlled by react and the renderer; production builds normally remove debug metadata
|
|
513
|
+
> - captures the location where the element is _used_; definition locations are recovered when react exposes an owned child debug stack
|
|
514
|
+
> - react 18 requires `_debugSource` from the JSX source transform (see [react#31981](https://github.com/facebook/react/issues/31981))
|
|
515
|
+
> - react 19 uses `_debugStack` and works for both composite and host fibers
|
|
516
|
+
> - source-map fetching is optional; runtimes without `fetch` still receive the unsymbolicated source location
|
|
517
|
+
|
|
518
|
+
`getSourceMap` accepts an optional fetch implementation plus request limits, an abort signal, and a timeout. its cache is scoped to the fetch implementation so credentials or virtual file systems cannot leak results into each other.
|
|
519
|
+
|
|
520
|
+
### getOwnerStack / getParentStack
|
|
521
|
+
|
|
522
|
+
returns a symbolicated stack of components above a fiber.
|
|
523
|
+
|
|
524
|
+
`getOwnerStack` walks the chain of components that _created_ this fiber's JSX (react's `_debugOwner` chain), with exact creation-site locations on react 19, including server component owners. wrappers that merely render `{children}` don't appear. it automatically falls back to `getParentStack` when no usable owner frames exist (e.g. react <19).
|
|
525
|
+
|
|
526
|
+
`getParentStack` walks _all_ ancestors in the render tree (the fiber's `return` chain), including `{children}` wrappers. works on every react version.
|
|
527
|
+
|
|
528
|
+
```typescript
|
|
529
|
+
import { getOwnerStack, getParentStack } from "bippy/source";
|
|
530
|
+
|
|
531
|
+
const ownerFrames = await getOwnerStack(fiber);
|
|
532
|
+
// [{ functionName: "Button", fileName: "src/button.tsx", lineNumber: 12, ... }, ...]
|
|
533
|
+
|
|
534
|
+
const parentFrames = await getParentStack(fiber);
|
|
535
|
+
// includes every wrapper between the fiber and the root
|
|
536
|
+
```
|
|
556
537
|
|
|
557
538
|
## example
|
|
558
539
|
|
|
559
540
|
here's a mini toy version of [`react-scan`](https://github.com/aidenybai/react-scan) that highlights renders in your app.
|
|
560
541
|
|
|
561
542
|
```javascript
|
|
562
|
-
import { instrument,
|
|
543
|
+
import { instrument, getNearestHostFiber, traverseRenderedFibers } from "bippy"; // must be imported BEFORE react
|
|
563
544
|
|
|
564
545
|
const highlightFiber = (fiber) => {
|
|
565
546
|
if (!(fiber.stateNode instanceof HTMLElement)) return;
|
|
@@ -583,55 +564,64 @@ const highlightFiber = (fiber) => {
|
|
|
583
564
|
* `instrument` is a function that installs the react DevTools global
|
|
584
565
|
* hook and allows you to set up custom handlers for react fiber events.
|
|
585
566
|
*/
|
|
586
|
-
instrument(
|
|
567
|
+
instrument({
|
|
587
568
|
/**
|
|
588
|
-
* `
|
|
589
|
-
*
|
|
590
|
-
*
|
|
591
|
-
*
|
|
592
|
-
* this is not required but highly recommended to provide "safeguards"
|
|
593
|
-
* in case something breaks.
|
|
569
|
+
* `onCommitFiberRoot` is a handler that is called when react is
|
|
570
|
+
* ready to commit a fiber root. this means that react is has
|
|
571
|
+
* rendered your entire app and is ready to apply changes to
|
|
572
|
+
* the host tree (e.g. via DOM mutations).
|
|
594
573
|
*/
|
|
595
|
-
|
|
574
|
+
onCommitFiberRoot(rendererID, root) {
|
|
596
575
|
/**
|
|
597
|
-
* `
|
|
598
|
-
*
|
|
599
|
-
*
|
|
600
|
-
*
|
|
576
|
+
* `traverseRenderedFibers` traverses the fiber tree and determines which
|
|
577
|
+
* fibers have actually rendered.
|
|
578
|
+
*
|
|
579
|
+
* A fiber tree contains many fibers that may have not rendered. this
|
|
580
|
+
* can be because it bailed out (e.g. `useMemo`) or because it wasn't
|
|
581
|
+
* actually rendered (if <Child> re-rendered, then <Parent> didn't
|
|
582
|
+
* actually render, but exists in the fiber tree).
|
|
601
583
|
*/
|
|
602
|
-
|
|
584
|
+
traverseRenderedFibers(root, (fiber) => {
|
|
603
585
|
/**
|
|
604
|
-
* `
|
|
605
|
-
*
|
|
586
|
+
* `getNearestHostFiber` is a utility function that finds the
|
|
587
|
+
* nearest host fiber to a given fiber.
|
|
606
588
|
*
|
|
607
|
-
*
|
|
608
|
-
*
|
|
609
|
-
* actually rendered (if <Child> re-rendered, then <Parent> didn't
|
|
610
|
-
* actually render, but exists in the fiber tree).
|
|
589
|
+
* a host fiber for `react-dom` is a fiber that has a DOM element
|
|
590
|
+
* as its `stateNode`.
|
|
611
591
|
*/
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
* a host fiber for `react-dom` is a fiber that has a DOM element
|
|
618
|
-
* as its `stateNode`.
|
|
619
|
-
*/
|
|
620
|
-
const hostFiber = getNearestHostFiber(fiber);
|
|
621
|
-
highlightFiber(hostFiber);
|
|
622
|
-
});
|
|
623
|
-
},
|
|
624
|
-
}),
|
|
625
|
-
);
|
|
592
|
+
const hostFiber = getNearestHostFiber(fiber);
|
|
593
|
+
highlightFiber(hostFiber);
|
|
594
|
+
});
|
|
595
|
+
},
|
|
596
|
+
});
|
|
626
597
|
```
|
|
627
598
|
|
|
599
|
+
## renderer support
|
|
600
|
+
|
|
601
|
+
bippy observes renderers through the React DevTools global hook. a renderer is automatically supported when it injects its reconciler and forwards commits to that hook.
|
|
602
|
+
|
|
603
|
+
| level | renderers | coverage |
|
|
604
|
+
| ------------- | ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- |
|
|
605
|
+
| automatic | React DOM, Remotion | unit matrix and browser e2e |
|
|
606
|
+
| automatic | React Native Fabric, React Native Skia | iOS and Android Detox e2e |
|
|
607
|
+
| automatic | React Three Fiber, Ink, react-nil | unit matrix |
|
|
608
|
+
| automatic | `@opentui/react`, `@pixi/react`, React BabylonJS | unit matrix with non-DOM host instances |
|
|
609
|
+
| compatibility | `@react-pdf/renderer` | its real reconciler root is forwarded through a synthetic DevTools hook bridge because react-pdf does not inject itself |
|
|
610
|
+
| compatibility | React Konva | its exported reconciler is injected by a test bridge because upstream automatic injection is disabled |
|
|
611
|
+
|
|
612
|
+
compatibility entries are not zero-config support claims. the bridge implementations in [`packages/bippy/tests/renderer-adapters.tsx`](packages/bippy/tests/renderer-adapters.tsx) verify bippy's fiber APIs against the real host trees while keeping the missing upstream DevTools integration explicit. React Native Windows, macOS, and NativeScript are not currently asserted.
|
|
613
|
+
|
|
614
|
+
terminal renderers must load bippy before their reconciler initializes. importing `bippy` first works in Node and Bun; `bippy/install-hook-only` is also available as a minimal prelude when application import order is controlled elsewhere. the test suite verifies OpenTUI in clean Node and Bun processes and verifies that Ink can replace the hook with full React DevTools without losing either renderer.
|
|
615
|
+
|
|
616
|
+
`@testing-library/react` is a React DOM testing utility, not a renderer. bippy uses it throughout the test suite, including hydration, event-driven updates, portals, unmounts, and error-boundary recovery.
|
|
617
|
+
|
|
628
618
|
## glossary
|
|
629
619
|
|
|
630
|
-
- fiber: a
|
|
620
|
+
- fiber: a “unit of execution” in react, representing a component or dom element
|
|
631
621
|
- commit: the process of applying changes to the host tree (e.g. DOM mutations)
|
|
632
622
|
- render: the process of building the fiber tree by executing component function/classes
|
|
633
623
|
- host tree: the tree of UI elements that react mutates (e.g. DOM elements)
|
|
634
|
-
- reconciler (or
|
|
624
|
+
- reconciler (or “renderer”): custom bindings for react, e.g. react-dom, react-native, react-three-fiber, etc to mutate the host tree
|
|
635
625
|
- `rendererID`: the id of the reconciler, starting at 1 (can be from multiple reconciler instances)
|
|
636
626
|
- `root`: a special `FiberRoot` type that contains the container fiber (the one you pass to `ReactDOM.createRoot`) in the `current` property
|
|
637
627
|
- `onCommitFiberRoot`: called when react is ready to commit a fiber root
|
|
@@ -640,7 +630,7 @@ instrument(
|
|
|
640
630
|
|
|
641
631
|
## misc
|
|
642
632
|
|
|
643
|
-
|
|
633
|
+
we initially created bippy for [react-scan](https://github.com/aidenybai/react-scan), which ships with safeguards so it only runs in development or error-guarded in production.
|
|
644
634
|
|
|
645
635
|
if you're seeking more robust solutions, you might consider [its-fine](https://github.com/pmndrs/its-fine) for accessing fibers within react using hooks, or [react-devtools-inline](https://www.npmjs.com/package/react-devtools-inline) for a headful interface.
|
|
646
636
|
|
package/dist/core.cjs
CHANGED
|
@@ -6,4 +6,4 @@
|
|
|
6
6
|
* This source code is licensed under the MIT license found in the
|
|
7
7
|
* LICENSE file in the root directory of this source tree.
|
|
8
8
|
*/
|
|
9
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./rdt-hook.cjs`),t=60111,n=`Symbol(react.concurrent_mode)`,r=`Symbol(react.async_mode)`,i=13366,a=e=>typeof e==`object`&&!!e&&`$$typeof`in e&&[`Symbol(react.element)`,`Symbol(react.transitional.element)`].includes(String(e.$$typeof)),o=e=>typeof e==`object`&&!!e&&`tag`in e&&`stateNode`in e&&`return`in e&&`child`in e&&`sibling`in e&&`flags`in e,s=e=>{switch(e.tag){case 5:case 26:case 27:return!0;default:return typeof e.type==`string`}},c=e=>{switch(e.tag){case 1:case 11:case 0:case 14:case 15:return!0;default:return!1}},l=e=>!e||typeof e!=`object`?!1:`pendingProps`in e&&!(`containerInfo`in e),ee=(e,t)=>{try{let n=e.dependencies,r=e.alternate?.dependencies;if(!n||!r||typeof n!=`object`||!(`firstContext`in n)||typeof r!=`object`||!(`firstContext`in r))return!1;let i=n.firstContext,a=r.firstContext;for(;i&&typeof i==`object`&&`memoizedValue`in i||a&&typeof a==`object`&&`memoizedValue`in a;){if(t(i,a)===!0)return!0;i=i?.next,a=a?.next}}catch{}return!1},u=(e,t)=>{try{let n=e.memoizedState,r=e.alternate?.memoizedState;for(;n||r;){if(t(n,r)===!0)return!0;n=n?.next,r=r?.next}}catch{}return!1},d=(e,t)=>{try{let n=e.memoizedProps,r=e.alternate?.memoizedProps||{};for(let e of Object.keys(n))if(t(e,n[e],r[e])===!0)return!0;for(let e of Object.keys(r))if(!(e in n)&&t(e,n[e],r[e])===!0)return!0}catch{}return!1},f=e=>{let t=e.memoizedProps,n=e.alternate?.memoizedProps||{},r=e.flags??e.effectTag??0;switch(e.tag){case 1:case 9:case 11:case 0:case 14:case 15:return(r&1)==1;default:return e.alternate?n!==t||e.alternate.memoizedState!==e.memoizedState||e.alternate.ref!==e.ref:!0}},p=e=>!!(e.flags&(i|8)||e.subtreeFlags&(i|8)),te=e=>{let t=[],n=[e];for(;n.length;){let e=n.pop();e&&(s(e)&&p(e)&&f(e)&&t.push(e),e.child&&n.push(e.child),e.sibling&&n.push(e.sibling))}return t},ne=e=>{let t=[],n=e;for(;n.return;)t.push(n),n=n.return;return t},m=e=>{switch(e.tag){case 18:return!0;case 7:case 6:case 23:case 22:return!0;case 3:return!1;default:{let i=typeof e.type==`object`&&e.type!==null?e.type.$$typeof:e.type;if(typeof i==`symbol`)return i.description===`react.concurrent_mode`||i.description===`react.async_mode`;switch(i){case t:case n:case r:return!0;default:return!1}}}},h=(e,t=!1)=>{let n=_(e,s,t);return n||=_(e,s,!t),n},g=e=>{let t=[],n=[];for(s(e)?t.push(e):e.child&&n.push(e.child);n.length;){let e=n.pop();if(!e)break;s(e)?t.push(e):e.child&&n.push(e.child),e.sibling&&n.push(e.sibling)}return t};function _(e,t,n=!1){if(!e)return null;let r=t(e);if(r instanceof Promise)return(async()=>{if(await r===!0)return e;let i=n?e.return:e.child;for(;i;){let e=await y(i,t,n);if(e)return e;i=n?null:i.sibling}return null})();if(r===!0)return e;let i=n?e.return:e.child;for(;i;){let e=v(i,t,n);if(e)return e;i=n?null:i.sibling}return null}const v=(e,t,n=!1)=>{if(!e)return null;if(t(e)===!0)return e;let r=n?e.return:e.child;for(;r;){let e=v(r,t,n);if(e)return e;r=n?null:r.sibling}return null},y=async(e,t,n=!1)=>{if(!e)return null;if(await t(e)===!0)return e;let r=n?e.return:e.child;for(;r;){let e=await y(r,t,n);if(e)return e;r=n?null:r.sibling}return null},b=e=>{let t=e?.actualDuration??0,n=t,r=e?.child??null;for(;t>0&&r!=null;)n-=r.actualDuration??0,r=r.sibling;return{selfTime:n,totalTime:t}},x=e=>!!e.updateQueue?.memoCache,S=e=>{let t=e;return typeof t==`function`?t:typeof t==`object`&&t?S(t.type||t.render):null},re=e=>{let t=e;if(typeof t==`string`)return t;if(typeof t!=`function`&&!(typeof t==`object`&&t))return null;let n=t.displayName||t.name||null;if(n)return n;let r=S(t);return r&&(r.displayName||r.name)||null},ie=e=>{try{if(typeof e.version==`string`&&e.bundleType>0)return`development`}catch{}return`production`},ae=()=>{let t=globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__;return!!t?._instrumentationIsActive||e.l(t)||e.c(t)},C=new Set,w=e=>{let t=e.alternate;if(!t)return e;if(t.actualStartTime&&e.actualStartTime)return t.actualStartTime>e.actualStartTime?t:e;for(let t of C){let n=_(t.current,t=>{if(t===e)return!0});if(n)return n}return e};let T=0;const E=new WeakMap,D=(e,t=T++)=>{E.set(e,t)},O=e=>{let t=E.get(e);return!t&&e.alternate&&(t=E.get(e.alternate)),t||(t=T++,D(e,t)),t},k=(e,t,n)=>{let r=t;for(;r!=null;){if(E.has(r)||O(r),!m(r)&&f(r)&&e(r,`mount`),r.tag===13)if(r.memoizedState!==null){let t=r.child,n=t?t.sibling:null;if(n){let t=n.child;t!==null&&k(e,t,!1)}}else{let t=null;r.child!==null&&(t=r.child.child),t!==null&&k(e,t,!1)}else r.child!=null&&k(e,r.child,!0);r=n?r.sibling:null}},A=(e,t,n,r)=>{if(E.has(t)||O(t),!n)return;E.has(n)||O(n);let i=t.tag===13,a=!m(t);a&&f(t)&&e(t,`update`);let o=i&&n.memoizedState!==null,s=i&&t.memoizedState!==null;if(o&&s){let r=t.child?.sibling??null,i=n.child?.sibling??null;r!==null&&i!==null&&A(e,r,i,t)}else if(o&&!s){let n=t.child;n!==null&&k(e,n,!0)}else if(!o&&s){M(e,n);let r=t.child?.sibling??null;r!==null&&k(e,r,!0)}else if(t.child!==n.child){let n=t.child;for(;n;){if(n.alternate){let i=n.alternate;A(e,n,i,a?t:r)}else k(e,n,!1);n=n.sibling}}},j=(e,t)=>{(t.tag===3||!m(t))&&e(t,`unmount`)},M=(e,t)=>{let n=t.tag===13&&t.memoizedState!==null,r=t.child;for(n&&(r=(t.child?.sibling??null)?.child??null);r!==null;)r.return!==null&&(j(e,r),M(e,r)),r=r.sibling};let oe=0;const N=new WeakMap,se=(e,t)=>{let n=`current`in e?e.current:e,r=N.get(e);r||(r={id:oe++,prevFiber:null},N.set(e,r));let{prevFiber:i}=r;if(!n)j(t,n);else if(i!==null){let e=i&&i.memoizedState!=null&&i.memoizedState.element!=null&&i.memoizedState.isDehydrated!==!0,r=n.memoizedState!=null&&n.memoizedState.element!=null&&n.memoizedState.isDehydrated!==!0;!e&&r?k(t,n,!1):e&&r?A(t,n,n.alternate,null):e&&!r&&j(t,n)}else k(t,n,!0);r.prevFiber=n},P=new Set;let F=!1;const I=()=>{if(F||!e.a())return;let t=e.i();if(t?.renderers){F=!0,Q(t);for(let e of t.renderers.values())P.add(e);e.u(e=>{P.add(e)})}},L=t=>{if(!e.a())return null;let n=t;for(;n.return;)n=n.return;let r=Z.get(n.stateNode);return r===void 0?null:e.i().renderers?.get(r)??null},R=e=>{I();let t=L(e);return t?[t]:Array.from(P)},z=(e,t,n,r)=>{for(let i of e)try{i.overrideProps?.(t,n,r)}catch{}},B=(e,t)=>{let n=e.memoizedState;for(let e=0;e<t;e++){if(!n?.next)return null;n=n.next}let r=n?.queue;return H(r)&&typeof r.dispatch==`function`?r.dispatch:null},V=(e,t)=>{let n=e;for(;n;){let e=n.type;if(e===t||e?.Provider===t)return n;n=n.return}return null},H=e=>Object.prototype.toString.call(e)===`[object Object]`&&(Object.getPrototypeOf(e)===Object.prototype||Object.getPrototypeOf(e)===null),U=(e,t=[])=>{if(!H(e))return[{path:t,value:e}];let n=[];for(let r in e){let i=e[r],a=t.concat(r);H(i)?n.push(...U(i,a)):n.push({path:a,value:i})}return n},W=e=>H(e)?U(e):[{path:[],value:e}],G=(e,t)=>{let n=R(e);for(let{path:r,value:i}of W(t))z(n,e,r,i)},ce=(e,t,n)=>{let r=R(e).filter(e=>!!e.overrideHookState),i=W(n);if(r.length>0){let n=String(t);for(let t of r)for(let{path:r,value:a}of i)try{t.overrideHookState?.(e,n,r,a)}catch{}return}if(H(n))return;let a=B(e,t);if(a)try{a(n)}catch{}},le=(e,t,n)=>{let r=V(e,t);if(!r)return;let i=R(r);for(let{path:e,value:t}of W(n))z(i,r,[`value`,...e],t),r.alternate&&z(i,r.alternate,[`value`,...e],t)},K=new Set,q=new Set,J=new Set,Y=new Set,X=new WeakMap,Z=new WeakMap,Q=e=>{let t=X.get(e)??{};if(X.set(e,t),!t.onCommitFiberRoot||e.onCommitFiberRoot!==t.onCommitFiberRoot){let n=e.onCommitFiberRoot,r=(t,i,a)=>{if(n?.(t,i,a),X.get(e)?.onCommitFiberRoot===r){C.add(i),Z.set(i,t);for(let e of K)e(t,i,a)}};t.onCommitFiberRoot=r,e.onCommitFiberRoot=r}if(!t.onCommitFiberUnmount||e.onCommitFiberUnmount!==t.onCommitFiberUnmount){let n=e.onCommitFiberUnmount,r=(t,i)=>{if(n?.(t,i),X.get(e)?.onCommitFiberUnmount===r)for(let e of q)e(t,i)};t.onCommitFiberUnmount=r,e.onCommitFiberUnmount=r}if(!t.onPostCommitFiberRoot||e.onPostCommitFiberRoot!==t.onPostCommitFiberRoot){let n=e.onPostCommitFiberRoot,r=(t,i)=>{if(n?.(t,i),X.get(e)?.onPostCommitFiberRoot===r)for(let e of J)e(t,i)};t.onPostCommitFiberRoot=r,e.onPostCommitFiberRoot=r}if(!t.onScheduleFiberRoot||e.onScheduleFiberRoot!==t.onScheduleFiberRoot){let n=e.onScheduleFiberRoot,r=(t,i,a)=>{if(n?.(t,i,a),X.get(e)?.onScheduleFiberRoot===r)for(let e of Y)e(t,i,a)};t.onScheduleFiberRoot=r,e.onScheduleFiberRoot=r}},ue=t=>{let n=e.i(t.onActive);n._instrumentationSource=t.name??e.t,Q(n);let{onActive:r,onCommitFiberRoot:i,onCommitFiberUnmount:a,onPostCommitFiberRoot:o,onScheduleFiberRoot:s}=t;return i&&K.add(i),a&&q.add(a),o&&J.add(o),s&&Y.add(s),e.m(()=>{r&&e.n.delete(r),i&&K.delete(i),a&&q.delete(a),o&&J.delete(o),s&&Y.delete(s)})},$=new Set,de=e=>e.startsWith(`__reactContainer$`)||e.startsWith(`__reactInternalInstance$`)||e.startsWith(`__reactFiber`),fe=e=>{let t=globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__;if(t?.renderers)for(let n of t.renderers.values())try{let t=n.findFiberByHostInstance?.(e);if(t)return t}catch{}if(typeof e==`object`&&e){if(`_reactRootContainer`in e)return e._reactRootContainer?._internalRoot?.current?.child;let t=e.__internalInstanceHandle??e._internalInstanceHandle;if(l(t))return t;let n=e;for(let e of $){let t=n[e];if(t)return t}for(let e of Object.keys(n))if(de(e))return $.add(e),n[e]||null}return null};exports.ActivityComponentTag=28,exports.BIPPY_INSTRUMENTATION_STRING=e.t,exports.CONCURRENT_MODE_NUMBER=t,exports.CONCURRENT_MODE_SYMBOL_DESCRIPTION=`react.concurrent_mode`,exports.CONCURRENT_MODE_SYMBOL_STRING=n,exports.ClassComponentTag=1,exports.ContextConsumerTag=9,exports.DEPRECATED_ASYNC_MODE_SYMBOL_DESCRIPTION=`react.async_mode`,exports.DEPRECATED_ASYNC_MODE_SYMBOL_STRING=r,exports.DehydratedSuspenseComponentTag=18,exports.ELEMENT_TYPE_SYMBOL_STRING=`Symbol(react.element)`,exports.ForwardRefTag=11,exports.FragmentTag=7,exports.FunctionComponentTag=0,exports.HostComponentTag=5,exports.HostHoistableTag=26,exports.HostPortalTag=4,exports.HostRootTag=3,exports.HostSingletonTag=27,exports.HostTextTag=6,exports.LazyComponentTag=16,exports.LegacyHiddenComponentTag=23,exports.MemoComponentTag=14,exports.OffscreenComponentTag=22,exports.SimpleMemoComponentTag=15,exports.SuspenseComponentTag=13,exports.SuspenseListComponentTag=19,exports.TRANSITIONAL_ELEMENT_TYPE_SYMBOL_STRING=`Symbol(react.transitional.element)`,exports.ViewTransitionComponentTag=30,exports._fiberRoots=C,exports._onActiveListeners=e.n,exports._renderers=e.r,exports.detectReactBuildType=ie,exports.didFiberCommit=p,exports.didFiberRender=f,exports.getDisplayName=re,exports.getFiberFromHostInstance=fe,exports.getFiberId=O,exports.getFiberStack=ne,exports.getLatestFiber=w,exports.getMutatedHostFibers=te,exports.getNearestHostFiber=h,exports.getNearestHostFibers=g,exports.getRDTHook=e.i,exports.getTimings=b,exports.getType=S,exports.hasMemoCache=x,exports.hasRDTHook=e.a,exports.installRDTHook=e.o,exports.instrument=ue,exports.isClientEnvironment=e.s,exports.isCompositeFiber=c,exports.isFiber=l,exports.isHostFiber=s,exports.isInstrumentationActive=ae,exports.isReactRefresh=e.c,exports.isRealReactDevtools=e.l,exports.isValidElement=a,exports.isValidFiber=o,exports.onRendererInject=e.u,exports.overrideContext=le,exports.overrideHookState=ce,exports.overrideProps=G,exports.patchRDTHook=e.d,exports.safelyInstallRDTHook=e.f,exports.setFiberId=D,exports.toUnsubscribe=e.m,exports.traverseContexts=ee,exports.traverseFiber=_,exports.traverseProps=d,exports.traverseRenderedFibers=se,exports.traverseState=u,exports.version=e.p;
|
|
9
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./rdt-hook.cjs"),t=require("./core2.cjs");exports.BIPPY_INSTRUMENTATION_STRING=e.t,exports.BippyError=t.M,exports.BippyHookInspectionError=t.N,exports.BippyHookRenderError=t.P,exports.BippySourceMapError=t.F,exports.BippyUnsupportedHookError=t.I,exports.ReactSymbols=t.R,exports._fiberRoots=t.t,exports._onActiveListeners=e.n,exports._renderers=e.r,exports.detectReactBuildType=t.n,exports.didFiberCommit=t.r,exports.didFiberRender=t.i,exports.getDisplayName=t.a,exports.getFiberFromHostInstance=t.o,exports.getFiberId=t.s,exports.getFiberStack=t.c,exports.getLatestFiber=t.l,exports.getMutatedHostFibers=t.u,exports.getNearestHostFiber=t.d,exports.getNearestHostFibers=t.f,exports.getRDTHook=e.a,exports.getReactWorkTags=t.z,exports.getTimings=t.p,exports.getType=t.m,exports.hasMemoCache=t.h,exports.hasRDTHook=e.o,exports.installRDTHook=e.s,exports.instrument=t.g,exports.isCompositeFiber=t._,exports.isFiber=t.v,exports.isHostFiber=t.y,exports.isInstrumentationActive=t.b,exports.isRealReactDevtools=e.c,exports.isValidElement=t.x,exports.isValidFiber=t.S,exports.onRendererInject=e.u,exports.overrideContext=t.C,exports.overrideHookState=t.w,exports.overrideProps=t.T,exports.patchRDTHook=e.d,exports.setFiberId=t.E,exports.traverseContexts=t.D,exports.traverseFiber=t.O,exports.traverseProps=t.k,exports.traverseRenderedFibers=t.A,exports.traverseState=t.j,exports.version=e.f;
|