|
1 | 1 | # MemLens |
2 | 2 |
|
3 | | -## What is this? |
| 3 | +MemLens is a lightweight, in-browser lens for spotting memory issues in React apps. It detects and visualizes: |
4 | 4 |
|
5 | | -MemLens is a debugging tool that helps identify memory leaks in React applications. It tracks: |
| 5 | +- Detached DOM elements still retained in memory |
| 6 | +- Unmounted React Fiber nodes that may indicate leaks |
| 7 | +- Event listener leaks (optional) |
| 8 | +- High-level DOM and heap usage stats (overlay header) |
6 | 9 |
|
7 | | -- Detached DOM elements that are no longer connected to the document but still held in memory |
8 | | -- Unmounted React Fiber nodes that haven't been properly cleaned up |
9 | | -- Memory usage patterns and growth over time |
| 10 | +It can run as a one-line console snippet or as a small library you embed in dev builds. |
10 | 11 |
|
11 | | -The tool provides: |
| 12 | +### Key features |
12 | 13 |
|
13 | | -1. Real-time memory monitoring through the browser console |
14 | | -2. Visual representation of problematic DOM elements and React components |
15 | | -3. Memory usage statistics and trends |
| 14 | +- **Visual overlay**: Highlights detached DOM elements; interactive panel shows counts and the React component stack for the selected element |
| 15 | +- **React Fiber analysis**: Scans the fiber tree to attribute elements to components |
| 16 | +- **Event listener leak scan (opt-in)**: Groups leaked listeners by component and type |
| 17 | +- **Non-intrusive**: Uses a transparent overlay and avoids tracking its own UI |
16 | 18 |
|
17 | | -This can help developers identify: |
18 | | -- Components that aren't properly cleaned up |
| 19 | +### Browser support |
19 | 20 |
|
20 | | -## How to Build? |
| 21 | +- Requires browsers with WeakRef/FinalizationRegistry support (e.g. modern Chrome, Edge, Safari, Firefox). In older browsers the tool may not function. |
| 22 | + |
| 23 | +--- |
| 24 | + |
| 25 | +### Quick start |
| 26 | + |
| 27 | +#### Option A: Run from the browser console (CDN) |
| 28 | + |
| 29 | +Paste this in your app page: |
| 30 | + |
| 31 | +```js |
| 32 | +(() => { |
| 33 | + const s = document.createElement('script'); |
| 34 | + s.src = 'https://unpkg.com/@memlab/lens/dist/memlens.run.bundle.min.js'; |
| 35 | + s.crossOrigin = 'anonymous'; |
| 36 | + document.head.appendChild(s); |
| 37 | +})(); |
| 38 | +``` |
| 39 | + |
| 40 | +This injects and starts MemLens with the interactive overlay. |
| 41 | + |
| 42 | +#### Option B: Run from the browser console (local build) |
| 43 | + |
| 44 | +Copy the contents of `packages/lens/dist/memlens.run.bundle.min.js` (or `packages/lens/dist/memlens.run.bundle.js`) and paste into the browser's web console. The overlay starts immediately. |
| 45 | + |
| 46 | +#### Option C: Programmatic scanner (UMD global) |
| 47 | + |
| 48 | +Load the library bundle and use the `MemLens` global: |
| 49 | + |
| 50 | +```html |
| 51 | +<script src="https://unpkg.com/@memlab/lens/dist/memlens.lib.bundle.min.js"></script> |
| 52 | +<script> |
| 53 | + // Create a scanner (no overlay by default; use the run bundle for overlay) |
| 54 | + const scan = MemLens.createReactMemoryScan({ |
| 55 | + isDevMode: true, |
| 56 | + scanIntervalMs: 1000, |
| 57 | + trackEventListenerLeaks: true, // optional |
| 58 | + }); |
| 59 | +
|
| 60 | + const unsubscribe = scan.subscribe((result) => { |
| 61 | + console.log('[MemLens]', { |
| 62 | + totalElements: result.totalElements, |
| 63 | + detached: result.totalDetachedElements, |
| 64 | + eventListenerLeaks: result.eventListenerLeaks, |
| 65 | + }); |
| 66 | + }); |
| 67 | +
|
| 68 | + scan.start(); |
| 69 | +
|
| 70 | + // Later: scan.stop(); unsubscribe(); scan.dispose(); |
| 71 | +</script> |
| 72 | +``` |
| 73 | + |
| 74 | +Note: The visualization overlay is provided by the self-starting "run" bundle (Option A/B/D). The library bundle focuses on scanning APIs. |
| 75 | + |
| 76 | +#### Option D: Node/Puppeteer injection |
| 77 | + |
| 78 | +`@memlab/lens` exposes a helper to retrieve the self-starting bundle as a string for script injection: |
| 79 | + |
| 80 | +```js |
| 81 | +// Node |
| 82 | +const {getBundleContent} = require('@memlab/lens'); |
| 83 | + |
| 84 | +// Puppeteer example |
| 85 | +await page.addScriptTag({content: getBundleContent()}); |
| 86 | +``` |
| 87 | + |
| 88 | +--- |
| 89 | + |
| 90 | +### Overlay controls and interactions |
| 91 | + |
| 92 | +- **Toggle switch**: Show/hide all overlay rectangles |
| 93 | +- **Select/pin**: Click a rectangle to pin/unpin the current selection |
| 94 | +- **Hover**: Reveal the selection chain of related detached elements |
| 95 | +- **Component stack panel**: Shows the React component stack of the selected element |
| 96 | +- **Keyboard**: Press `D` to temporarily ignore the currently selected element in the overlay |
| 97 | +- **Widget**: The control widget is draggable |
| 98 | + |
| 99 | +Notes: |
| 100 | +- The overlay ignores its own UI elements and won’t count them as part of the page. |
| 101 | +- In dev mode, MemLens logs basic timing and scan stats to the console. |
| 102 | + |
| 103 | +--- |
| 104 | + |
| 105 | +### Configuration (CreateOptions) |
| 106 | + |
| 107 | +```ts |
| 108 | +type CreateOptions = { |
| 109 | + isDevMode?: boolean; // enable console logs and dev-only behaviors |
| 110 | + subscribers?: Array<(r) => void>; // observers of each scan result |
| 111 | + extensions?: Array<BasicExtension>; // e.g., DOMVisualizationExtension |
| 112 | + scanIntervalMs?: number; // default ~1000ms |
| 113 | + trackEventListenerLeaks?: boolean; // enable listener leak scanning |
| 114 | +}; |
| 115 | +``` |
| 116 | + |
| 117 | +Core API (`ReactMemoryScan`): |
| 118 | +- `start()`, `pause()`, `stop()`, `dispose()` |
| 119 | +- `subscribe(cb) => () => void` |
| 120 | +- `registerExtension(ext) => () => void` |
| 121 | + |
| 122 | +--- |
| 123 | + |
| 124 | +### Build |
21 | 125 |
|
22 | 126 | ```bash |
| 127 | +# from packages/lens |
| 128 | +npm run build |
| 129 | +# or |
23 | 130 | webpack |
24 | 131 | ``` |
25 | 132 |
|
26 | | -## How to Test? |
| 133 | +### Test |
27 | 134 |
|
28 | | -1. Install Playwright |
| 135 | +1) Install Playwright dependencies (first time): |
29 | 136 |
|
30 | 137 | ```bash |
31 | 138 | npx playwright install |
32 | 139 | npx playwright install-deps |
33 | 140 | ``` |
34 | 141 |
|
35 | | -2. Run the test |
| 142 | +2) Run tests: |
36 | 143 |
|
37 | 144 | ```bash |
38 | 145 | npm run test:e2e |
39 | 146 | ``` |
40 | 147 |
|
41 | | -3. Test manually by copying and pasting the content of `dist/run.bundle.js` |
42 | | -into web console |
43 | | - |
44 | | - |
45 | | -## TODO List |
46 | | - * Note that document.querySelectorAll('*') only captures DOM elements on the DOM tree |
47 | | - * So the DOM tree scanning and component name analysis must be done in a frequent interval (every 10s or so) |
48 | | - * Extensible framework for tracking additional metadata |
49 | | - * Auto diffing and counting detached Element and unmounted Fiber nodes |
50 | | - * being able to summarize the leaked components that was not reported (this can reduce the report overhead) |
51 | | - * Improve the DOM visualizer - only visualize the common ancestors of the detached DOM elements and unmounted fiber nodes |
52 | | - * Improving the scanning efficiency so that the overhead is minimal in production environment |
53 | | - * Improve the fiber tree traversal efficiency (there are redundant traversals right now) |
54 | | - * Not only keep track of detached DOM elements, but also keep track of unmounted fiber nodes in WeakMap and WeakSet |
55 | | - * DOM visualizer is leaking canvas DOM elements after each scan |
56 | | - * Monitor event listener leaks? |
57 | | - * Real-time memory usage graphs |
58 | | - * Real-time component count and other react memory scan obtained stats graphs |
59 | | - * Component re-render heat maps |
60 | | - * Interactive component tree navigation |
61 | | - * Browser extension integration |
62 | | - * centralized config file |
63 | | - * centralized exception handling |
| 148 | +3) Manual test: open `src/tests/manual/todo-list/todo-with-run.bundle.html` in a browser, or copy/paste `dist/memlens.run.bundle.js` to the DevTools console on any React page. |
| 149 | + |
| 150 | +--- |
| 151 | + |
| 152 | +### Learn more |
| 153 | + |
| 154 | +Please check out this |
| 155 | +[tutorial page](https://facebook.github.io/memlab/docs/guides/visually-debug-memory-leaks-with-memlens) |
| 156 | +on how to use MemLens (a debugging utility) to visualize memory leaks in the |
| 157 | +browser for easier memory debugging. |
| 158 | + |
| 159 | +### License |
| 160 | + |
| 161 | +MIT © Meta Platforms, Inc. |
0 commit comments