@memlab/lens 1.0.2 → 1.0.3

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/README.md CHANGED
@@ -1,63 +1,161 @@
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.
@@ -21,6 +21,7 @@ export default class ReactMemoryScan {
21
21
  start(): void;
22
22
  pause(): void;
23
23
  stop(): void;
24
+ dispose(): void;
24
25
  recordBoundingRectangles(elementRefs: Array<WeakRef<Element>>): void;
25
26
  getDetachedDOMInfo(): Array<DOMElementInfo>;
26
27
  isDevMode(): boolean;
@@ -14,4 +14,5 @@ export declare class DOMVisualizationExtension extends BasicExtension {
14
14
  #private;
15
15
  constructor(scanner: ReactMemoryScan);
16
16
  afterScan(_analysisResult: AnalysisResult): void;
17
+ cleanup(): void;
17
18
  }
package/dist/index.js CHANGED
@@ -36,25 +36,15 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
36
36
  }) : function(o, v) {
37
37
  o["default"] = v;
38
38
  });
39
- var __importStar = (this && this.__importStar) || (function () {
40
- var ownKeys = function(o) {
41
- ownKeys = Object.getOwnPropertyNames || function (o) {
42
- var ar = [];
43
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
44
- return ar;
45
- };
46
- return ownKeys(o);
47
- };
48
- return function (mod) {
49
- if (mod && mod.__esModule) return mod;
50
- var result = {};
51
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
52
- __setModuleDefault(result, mod);
53
- return result;
54
- };
55
- })();
39
+ var __importStar = (this && this.__importStar) || function (mod) {
40
+ if (mod && mod.__esModule) return mod;
41
+ var result = {};
42
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
43
+ __setModuleDefault(result, mod);
44
+ return result;
45
+ };
56
46
  Object.defineProperty(exports, "__esModule", ({ value: true }));
57
- exports.getBundleContent = getBundleContent;
47
+ exports.getBundleContent = void 0;
58
48
  /**
59
49
  * Copyright (c) Meta Platforms, Inc. and affiliates.
60
50
  *
@@ -70,6 +60,7 @@ function getBundleContent() {
70
60
  const bundlePath = path.join(__dirname, 'memlens.run.bundle.min.js');
71
61
  return fs.readFileSync(bundlePath, 'utf-8');
72
62
  }
63
+ exports.getBundleContent = getBundleContent;
73
64
 
74
65
 
75
66
  /***/ })