2 |
3 | # React ChronoScope
4 |
5 | A package for traversing React Fiber Tree and extracting information.
6 |
7 | # Installing
8 |
9 | Install the package in the React application.
10 | ```
11 | npm i react-chronoscope
12 | ```
13 | Import the npm library into root container file of React Application and invoke the library with the root container.
14 | ```
15 | import chronoscope from 'react-chronoscope';
16 | const container = document.querySelector('#root');
17 | render(
18 | ,
19 | container,
20 | () => chronoscope(container)
21 | );
22 | ```
23 |
24 | # Authors
25 | [Jason Huang](https://github.com/jhmoon999)
26 | [Jimmy Mei](https://github.com/Jimmei27)
27 | [Matt Peters](https://github.com/mgpeters)
28 | [Sergiy Alariki](https://github.com/Serrzhik)
29 | [Vinh Chau](https://github.com/Vchau511)
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/src/backgroundScript/backgroundScript.ts:
--------------------------------------------------------------------------------
1 | import { IMessageData } from '../interfaces';
2 |
3 | // define a variable to store tree data structure from content script;
4 | let treeGraph;
5 | // connected port will be saved here
6 | let currentPort;
7 |
8 | // listen for connection from the chrome dev tool;
9 | chrome.runtime.onConnect.addListener((port) => {
10 | // save the port
11 | currentPort = port;
12 | // send message to Chrome Dev Tool on initial connect
13 | port.postMessage({
14 | payload: treeGraph,
15 | });
16 | });
17 |
18 | // listen for message from contentScript
19 | chrome.runtime.onMessage.addListener((msg: IMessageData) => {
20 | // reassign the treeGraph
21 | treeGraph = msg.payload;
22 | // once the message is accepted from content script, send it to dev tool
23 | if (currentPort) {
24 | currentPort.postMessage({
25 | payload: treeGraph,
26 | });
27 | }
28 | });
29 |
--------------------------------------------------------------------------------
/__tests__/TreeGraph.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow, render } from 'enzyme';
3 |
4 | // needed to mock chrome dev tools api
5 | import * as chrome from "sinon-chrome";
6 |
7 | import TreeGraph from '../src/components/TreeGraph';
8 |
9 | describe('React-ChronoScope Component Tests', () => {
10 | describe('Component: TreeGraph', () => {
11 | test.skip('skip', () => {});
12 | // let wrapper;
13 |
14 | // beforeAll(() => {
15 | // global.chrome = chrome;
16 | // wrapper = shallow( tag', () => {
24 | // expect(wrapper.type()).toEqual('div');
25 | // });
26 |
27 | // it('should have called a webextension API', () => {
28 | // expect(chrome.runtime.connect()).toHaveBeenCalled();
29 | // });
30 |
31 | // afterAll(() => {
32 | // chrome.flush()
33 | // })
34 | });
35 | });
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | browser: true,
4 | es6: true,
5 | },
6 | extends: [
7 | 'plugin:react/recommended',
8 | 'airbnb',
9 | ],
10 | globals: {
11 | Atomics: 'readonly',
12 | SharedArrayBuffer: 'readonly',
13 | },
14 | parser: '@typescript-eslint/parser',
15 | parserOptions: {
16 | ecmaFeatures: {
17 | jsx: true,
18 | },
19 | ecmaVersion: 2018,
20 | sourceType: 'module',
21 | },
22 | plugins: [
23 | 'react',
24 | '@typescript-eslint',
25 | ],
26 | rules: {
27 | "import/prefer-default-export": "off",
28 | "react/jsx-filename-extension": "off",
29 | 'import/no-unresolved': 0,
30 | "import/extensions": "off",
31 | "no-unused-vars": "off",
32 | "no-undef": "off",
33 | "class-methods-use-this": "off",
34 | "no-use-before-define": "off",
35 | "consistent-return": "off",
36 | "no-shadow": "off",
37 | "max-len": "off",
38 | "no-param-reassign": "off",
39 | "no-underscore-dangle": "off",
40 | "no-new-wrappers": "off",
41 | "react/prop-types": "off",
42 | },
43 | };
44 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 OSLabs Beta
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/__tests__/notes:
--------------------------------------------------------------------------------
1 | To debug TypeScript tests, the json specified under “VS Code debug”
2 | section below in the story need to be added under configurations in
3 | launch.json which can be created by going to Debug Menu and then Add
4 | Configuration in VS Code.
5 |
6 | <- Hit Debug Tab in VSCode
7 | < - select node.js
8 | <- edit your launch.json file with the code below
9 |
10 | {
11 | "type": "node",
12 | "request": "launch",
13 | "name": "Jest Current File",
14 | "program": "${workspaceFolder}/node_modules/.bin/jest",
15 | "args": ["${relativeFile}"],
16 | "console": "integratedTerminal",
17 | "internalConsoleOptions": "neverOpen",
18 | "windows": {
19 | "program": "${workspaceFolder}/node_modules/jest/bin/jest"
20 | }
21 | }
22 |
23 | Links:
24 | Jest - https://jestjs.io/docs/en/getting-started
25 | Jest & TS - https://medium.com/@RupaniChirag/writing-unit-tests-in-typescript-d4719b8a0a40
26 | Testing React with Jest & Enzyme - https://medium.com/codeclan/testing-react-with-jest-and-enzyme-20505fec4675
27 | (this one has the real juice -m ) Enzyme & Jest & TS - https://medium.com/@tejasupmanyu/setting-up-unit-tests-in-react-typescipt-with-jest-and-enzyme-56634e54703
28 | What and How - https://djangostars.com/blog/what-and-how-to-test-with-enzyme-and-jest-full-instruction-on-react-component-testing/
29 |
--------------------------------------------------------------------------------
/src/components/TreeGraph.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | import * as React from "react";
3 | import { useState } from 'react';
4 | import Tree from 'react-d3-tree';
5 | import { ITreeProps, IShape, IStateAndProps } from '../interfaces';
6 |
7 | const TreeGraph: React.SFC
= ({ data }) => {
8 | const [stateAndProps, setStateAndProps] = useState({});
9 | const [shape, setShape] = useState(null);
10 | const [name, setName] = useState('');
11 |
12 | const handleHover = (e) => {
13 | const { stats, nodeSvgShape, name } = e;
14 | setStateAndProps(stats);
15 | setShape(nodeSvgShape);
16 | setName(name);
17 | }
18 |
19 | const handleUnHover = () => {
20 | setStateAndProps({});
21 | setShape(null);
22 | setName('');
23 | }
24 |
25 | return (
26 |
27 |
28 | {
29 | shape &&
30 | shape.shapeProps.fill === 'red' &&
31 |
Optimize Performance: Use shouldComponentUpdate, or React.PureComponent, or React.memo
32 | }
33 | Component: {name}
34 | State: {stateAndProps.state}
35 | Props: {stateAndProps.props}
36 | Render Time: {stateAndProps.renderTotal}
37 |
38 |
39 |
48 |
49 |
50 | );
51 | }
52 |
53 | export default TreeGraph;
54 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const MiniCssExtractPlugin = require('mini-css-extract-plugin');
3 | const HtmlWebpackPlugin = require('html-webpack-plugin');
4 |
5 | module.exports = {
6 | entry: {
7 | contentScript: path.join(__dirname, './src/contentScript/contentScript.ts'), // path.resolve(__dirname, './src/index.tsx'),
8 | backgroundScript: path.join(__dirname, './src/backgroundScript/backgroundScript.ts'),
9 | devtools: path.join(__dirname, './src/devtools/devtools.ts'),
10 | bundle: path.join(__dirname, './src/index.tsx'),
11 | },
12 | output: {
13 | path: path.resolve(__dirname, 'dist'),
14 | filename: '[name].js',
15 | },
16 | devServer: {
17 | publicPath: '/build/',
18 | },
19 | mode: process.env.NODE_ENV,
20 | resolve: {
21 | extensions: ['.ts', '.tsx', '.js', '.jsx'],
22 | },
23 | module: {
24 | rules: [
25 | {
26 | test: /\.(t|j)sx?$/,
27 | exclude: /node_modules/,
28 | use: { loader: 'ts-loader' },
29 | },
30 | {
31 | enforce: 'pre',
32 | test: /\.js$/,
33 | exclude: /node_modules/,
34 | loader: 'source-map-loader',
35 | },
36 | {
37 | test: /\.html$/i,
38 | use: {
39 | loader: 'html-loader',
40 | },
41 | },
42 | {
43 | test: /\.(sa|sc|c)ss$/,
44 | use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
45 | },
46 | {
47 | test: /\.(png|jpe?g|gif)$/i,
48 | use: [
49 | {
50 | loader: 'file-loader',
51 | },
52 | ],
53 | },
54 | ],
55 | },
56 | plugins: [
57 | new MiniCssExtractPlugin({
58 | filename: '[name].css',
59 | chunkFilename: '[id].css',
60 | }),
61 | new HtmlWebpackPlugin({
62 | title: 'React-ChronoScope',
63 | filename: 'index.html',
64 | template: 'src/index.html',
65 | chunks: ['bundle'],
66 | }),
67 | new HtmlWebpackPlugin({
68 | filename: 'devtools.html',
69 | template: 'src/devtools/devtools.html',
70 | chunks: ['devtools'],
71 | }),
72 | ],
73 | devtool: 'source-map',
74 | };
75 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-chronoscope",
3 | "version": "1.0.0",
4 | "description": "React ChronoScope",
5 | "main": "webpack.config.js",
6 | "scripts": {
7 | "clean": "rm -rf dist && cpy manifest.json dist && cpy src/assets/* dist/assets",
8 | "build": "NODE_ENV=production webpack",
9 | "dev": "NODE_DEV=development webpack-dev-server",
10 | "test": "jest --silent",
11 | "test:watch": "jest --silent --watch",
12 | "coverage": "jest --silent --coverage",
13 | "lint": "eslint .",
14 | "lint:fix": "eslint . --fix"
15 | },
16 | "author": "ChronoScope",
17 | "license": "ISC",
18 | "devDependencies": {
19 | "@babel/core": "^7.8.3",
20 | "@babel/plugin-proposal-class-properties": "^7.8.3",
21 | "@babel/preset-env": "^7.8.3",
22 | "@babel/preset-react": "^7.8.3",
23 | "@babel/preset-typescript": "^7.8.3",
24 | "@types/chrome": "0.0.95",
25 | "@types/enzyme": "^3.10.5",
26 | "@types/jest": "^25.1.3",
27 | "@types/node": "^13.7.6",
28 | "@types/react": "^16.9.20",
29 | "@types/react-dom": "^16.9.5",
30 | "@typescript-eslint/eslint-plugin": "^2.21.0",
31 | "@typescript-eslint/parser": "^2.21.0",
32 | "babel-core": "^7.0.0-bridge.0",
33 | "babel-jest": "^25.1.0",
34 | "babel-loader": "^8.0.6",
35 | "cpy-cli": "^3.0.0",
36 | "css-loader": "^3.4.2",
37 | "enzyme": "^3.11.0",
38 | "enzyme-adapter-react-16": "^1.15.2",
39 | "enzyme-to-json": "^3.4.4",
40 | "eslint": "^6.8.0",
41 | "eslint-config-airbnb": "^18.0.1",
42 | "eslint-plugin-import": "^2.20.1",
43 | "eslint-plugin-jsx-a11y": "^6.2.3",
44 | "eslint-plugin-react": "^7.18.3",
45 | "eslint-plugin-react-hooks": "^1.7.0",
46 | "html-loader": "^0.5.5",
47 | "html-webpack-plugin": "^3.2.0",
48 | "jest": "^25.1.0",
49 | "mini-css-extract-plugin": "^0.9.0",
50 | "node-sass": "^4.13.1",
51 | "sass": "^1.25.0",
52 | "sass-loader": "^8.0.2",
53 | "sinon-chrome": "^3.0.1",
54 | "style-loader": "^1.1.3",
55 | "ts-jest": "^25.2.1",
56 | "ts-loader": "^6.2.1",
57 | "typescript": "^3.8.3",
58 | "webpack": "^4.41.5",
59 | "webpack-cli": "^3.3.10"
60 | },
61 | "dependencies": {
62 | "react": "^16.5.2",
63 | "react-d3-tree": "^1.16.1",
64 | "react-dom": "^16.5.2",
65 | "react-visjs-timeline": "^1.6.0",
66 | "vis-timeline": "^7.1.3"
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/components/MainContainer.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { useState, useEffect } from 'react';
3 | import TreeGraph from './TreeGraph';
4 | import LineGraph from './LineGraph';
5 | import { ITree } from '../interfaces';
6 |
7 | let treeGraphData: ITree[] = [{
8 | name: '',
9 | children: [],
10 | }];
11 |
12 | // initialize port that will be upon when component Mounts
13 | let port;
14 |
15 | const timeLineArray = [];
16 |
17 | let items = [];
18 |
19 | const options = {
20 | width: '100%',
21 | height: '500px',
22 | stack: true,
23 | showCurrentTime: false,
24 | showMajorLabels: false,
25 | zoomable: false,
26 | start: new Number(0),
27 | end: new Number(10),
28 | min: new Number(0),
29 | max: new Number(40),
30 | type: 'range',
31 | selectable: true,
32 | horizontalScroll: false,
33 | verticalScroll: true,
34 | };
35 |
36 | let event;
37 |
38 | function getData(Node, baseTime) {
39 | event = {};
40 | event.start = new Number((Number(Node.stats.renderStart) - Number(baseTime)).toFixed(2));
41 | event.end = new Number((Number(Node.stats.renderStart) + Number(Node.stats.renderTotal) - Number(baseTime)).toFixed(2));
42 | event.content = Node.name;
43 | event.title = Node.name;
44 | items.push(event);
45 | if (Node.children.length !== 0) {
46 | Node.children.forEach((child) => {
47 | getData(child, baseTime);
48 | });
49 | }
50 | }
51 |
52 | export const MainContainer: React.FC = () => {
53 | const [tree, setTree] = useState(treeGraphData);
54 |
55 | useEffect(() => {
56 | // open connection with background script
57 | // make sure to open only one port
58 | if (!port) port = chrome.runtime.connect();
59 | // listen for a message from the background script
60 | port.onMessage.addListener((message) => {
61 | if (JSON.stringify([message.payload.payload]) !== JSON.stringify(treeGraphData)) {
62 | // save new tree
63 | treeGraphData = [message.payload.payload];
64 | getData(treeGraphData[0], treeGraphData[0].stats.renderStart);
65 | setTree(treeGraphData);
66 | timeLineArray.shift();
67 | timeLineArray.push(items);
68 | items = [];
69 | }
70 | });
71 | });
72 |
73 | return (
74 | <>
75 | React ChronoScope
76 |
77 | Tree Diagram
78 |
79 |
80 |
81 |
82 |
83 |
TimeLine
84 | {
85 | timeLineArray.map((items) => )
86 | }
87 |
88 | >
89 | );
90 | };
91 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | #
8 | Developers' tool to monitor React performance and pinpoint areas that require further optimization
9 |
10 | #
11 |
12 | ### What Is React ChronoScope?
13 |
14 |
15 | React ChronoScope is a performance monitoring tool for React developers. It visualizes React application's components displaying components that require further optimization.
16 |
17 | React ChronoScope parses through the React application to construct an interactive tree diagram of the component hierarchy.
18 |
19 |
20 |
21 |
22 |
23 | ### How To Install
24 |
25 | 1. Download the [extension](https://chrome.google.com/webstore/detail/react-chronoscope/haeiefchakokoggcngggkfbgklaifbbm) from the Chrome Web Store.
26 |
27 | 2. Install the [npm package](https://www.npmjs.com/package/react-chronoscope) in the React application.
28 |
29 | ```
30 | npm i react-chronoscope
31 | ```
32 |
33 | 3. Import the npm library into root container file of React Application and invoke the library with the root container.
34 |
35 | ```
36 | import chronoscope from 'react-chronoscope';
37 | const container = document.querySelector('#root');
38 | render(
39 | ,
40 | container,
41 | () => chronoscope(container)
42 | );
43 | ```
44 |
45 | ### How To Use
46 | After installing both the Chrome Extension and the npm package, run the react application in the browser. Then open Chrome Developer Tools (Inspect) on the React Application and click on ``` React ChronoScope ``` at the top of the Developer Tools panel.
47 |
48 | ### Features
49 | - Node-collapsible tree diagram that displays all hierarchy tree components of a React application.
50 | - Each Node has information vital for debugging and development such state, props and how optimized is the rendering process.
51 | - Color legend:
52 | -  `- component was unnecessarily re-rendered.`
53 | -  `- component was re-rendered`
54 | -  `- component was not re-rendered`
55 |
56 | - Timeline that illustrates when each component renders.
57 |
58 |
59 |
60 | ## Team
61 |
62 | - **Jason Huang** - [https://github.com/jhmoon999]
63 | - **Jimmy Mei** - [https://github.com/Jimmei27]
64 | - **Matt Peters** - [https://github.com/mgpeters]
65 | - **Sergiy Alariki** - [https://github.com/Serrzhik]
66 | - **Vinh Chau** - [https://github.com/Vchau511]
67 |
68 | ## License
69 |
70 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details
--------------------------------------------------------------------------------
/package/index.js:
--------------------------------------------------------------------------------
1 | let wasMounted = false;
2 |
3 | class Node {
4 | constructor(name, parent, children, fiber) {
5 | this.name = name;
6 | this.parent = parent;
7 | this.children = children;
8 | this.stats = {
9 | state: JSON.stringify(fiber.memoizedState),
10 | props: JSONStringify(fiber.memoizedProps),
11 | effectTag: fiber.effectTag,
12 | type: typeof fiber.type,
13 | renderStart: fiber.actualStartTime.toFixed(2),
14 | renderTotal: fiber.actualDuration.toFixed(2),
15 | };
16 | this.nodeSvgShape = {
17 | shape: 'ellipse',
18 | shapeProps: {
19 | rx: 10,
20 | ry: 10,
21 | fill: 'lightgreen',
22 | },
23 | };
24 | }
25 |
26 | initializeProps(fiber) {
27 | let props = '';
28 | if (fiber.memoizedProps.children) {
29 | if (typeof fiber.memoizedProps.children[0] === 'object') {
30 | fiber.memoizedProps.children.forEach((object) => {
31 | props += JSON.stringify(object.props);
32 | });
33 | } else props = JSON.stringify(fiber.memoizedProps.children);
34 | } else {
35 | props = JSON.stringify(fiber.memoizedProps);
36 | }
37 |
38 | return props;
39 | }
40 | }
41 |
42 | function JSONStringify(object) {
43 | let cache = [];
44 | const string = JSON.stringify(object,
45 | // custom replacer - gets around "TypeError: Converting circular structure to JSON"
46 | (key, value) => {
47 | if (typeof value === 'object' && value !== null) {
48 | if (cache.indexOf(value) !== -1) {
49 | // Circular reference found, discard key
50 | return;
51 | }
52 | // Store value in collection
53 | cache.push(value);
54 | }
55 | return value;
56 | }, 4);
57 | cache = null; // garbage collection
58 | return string;
59 | }
60 |
61 | let prevTreeGraph = null;
62 |
63 | function treeCreator(hostRoot) {
64 | // helper function - that accepts the node - Host Root
65 | function treeGraphFromHostRootCreator(fiber) {
66 | // create a treeGraph
67 | const treeGraph = new Node(fiber.type.name, null, [], fiber); // Represent the top most Element (like App);
68 | const helper = (fiber, treeGraph) => {
69 | // check if fiber.child !== null - traverse
70 | if (fiber.child) {
71 | // push the new Node to the treeGraph.children array
72 | // the parent will the tree graph we are currently working with (do the type check for elements that are functions or html elements)
73 | let newGraphNode = treeGraph;
74 | if (typeof fiber.child.type !== 'object' && (fiber.child.child ? typeof fiber.child.child.type !== 'object' : true)) {
75 | newGraphNode = new Node(fiber.child.key || (fiber.child.type ? fiber.child.type.name : fiber.child.type) || fiber.child.type, treeGraph, [], fiber.child);
76 | treeGraph.children.push(newGraphNode);
77 | }
78 | // recursively invoke the helper on child
79 | helper(fiber.child, newGraphNode);
80 | }
81 | // check if fiber.sibling !== null - traverse
82 | if (fiber.sibling) {
83 | let newGraphNode = treeGraph;
84 | if (typeof fiber.sibling.type !== 'object' && (fiber.sibling.child ? typeof fiber.sibling.child.type !== 'object' : true)) {
85 | // create new GraphNode based on it with parent being a treeGraph.parent
86 | newGraphNode = new Node(fiber.sibling.key || (fiber.sibling.type ? fiber.sibling.type.name : fiber.sibling.type) || fiber.sibling.type, treeGraph.parent, [], fiber.sibling);
87 | // push the node on to the treeGraph.parent.children array
88 | treeGraph.parent.children.push(newGraphNode);
89 | }
90 | helper(fiber.sibling, newGraphNode);
91 | }
92 | // name of the element can be found in child.type.name
93 | };
94 | // invoke the helper function
95 | helper(fiber, treeGraph); // fiber is an App Fiber
96 | return treeGraph;
97 | }
98 | let treeGraph;
99 | // check if the hostRoot has a child
100 | if (hostRoot.child) {
101 | // yes? invoke the search function on the child - App Fiber
102 | // assign the returned result to tree
103 | treeGraph = treeGraphFromHostRootCreator(hostRoot.child);
104 | }
105 |
106 | function recursivelyDeleteParent(root) {
107 | if (root.parent) {
108 | delete root.parent;
109 | }
110 | if (root.children) {
111 | root.children.forEach((child) => recursivelyDeleteParent(child));
112 | }
113 | }
114 | recursivelyDeleteParent(treeGraph);
115 | delete treeGraph.parent;
116 |
117 | const tempTreeGraph = JSON.parse(JSON.stringify(treeGraph));
118 | // recursively compare state and props in prevTreeGraph and treeGraph
119 | const compareStateAndProps = (node, prevNode, parentShapeProps) => {
120 | // compare state and props properties on stats properties for both nodes
121 | // if same - treeGraph.stats.stateOrPropsChanged - false
122 | if (node && prevNode) {
123 | // check if the node's type is a string
124 | // yes? give it a color of the parent - because Composite Component renders(or not) Host Component
125 | if (node.stats.type === 'string') {
126 | node.nodeSvgShape.shapeProps = parentShapeProps;
127 | delete node.stats.state;
128 | delete node.stats.props;
129 | } else if (prevNode.stats.state === node.stats.state && prevNode.stats.props === node.stats.props) {
130 | if ((node.stats.effectTag === 0 || node.stats.effectTag === 4) && wasMounted) {
131 | node.nodeSvgShape.shapeProps.fill = 'gray';
132 | } else {
133 | node.nodeSvgShape.shapeProps.fill = 'red';
134 | node.nodeSvgShape.shapeProps.rx = 12;
135 | node.nodeSvgShape.shapeProps.ry = 12;
136 | }
137 | }
138 |
139 | // delete node.stats;
140 |
141 | // recursively invoke the function for each children
142 | if (node.children.length) {
143 | for (let i = 0; i < node.children.length; i += 1) {
144 | compareStateAndProps(node.children[i], prevNode.children[i], node.nodeSvgShape.shapeProps);
145 | }
146 | }
147 | } else if (node) {
148 | // delete node.stats;
149 | if (node.stats.type === 'string') {
150 | delete node.stats.state;
151 | delete node.stats.props;
152 | }
153 |
154 | // recursively invoke the function for each children
155 | if (node.children.length) {
156 | for (let i = 0; i < node.children.length; i += 1) {
157 | compareStateAndProps(node.children[i], null, node.nodeSvgShape.shapeProps);
158 | }
159 | }
160 | }
161 |
162 | if (!wasMounted) {
163 | // delete node.stats;
164 | if (node.stats.type === 'string') {
165 | delete node.stats.state;
166 | delete node.stats.props;
167 | }
168 | if (node.children.length) {
169 | for (let i = 0; i < node.children.length; i += 1) {
170 | compareStateAndProps(node.children[i]);
171 | }
172 | }
173 | }
174 | };
175 |
176 | compareStateAndProps(treeGraph, prevTreeGraph, null);
177 | prevTreeGraph = tempTreeGraph;
178 | wasMounted = true;
179 | window.postMessage({ action: 'npmToContent', payload: treeGraph });
180 | }
181 |
182 | module.exports = function (container) {
183 | const fiberRoot = container._reactRootContainer._internalRoot;
184 | const hostRoot = fiberRoot.current;
185 |
186 | setTimeout(() => treeCreator(hostRoot), 500); // needs to wait for the page load
187 | window.addEventListener('click', () => {
188 | // check if the hostRoot is new - only then invoke
189 | setTimeout(() => {
190 | if (hostRoot !== fiberRoot.current) {
191 | treeCreator(fiberRoot.current);
192 | }
193 | }, 200);
194 | });
195 | window.addEventListener('keyup', () => {
196 | setTimeout(() => {
197 | // check if the hostRoot is new - only then invoke
198 | if (hostRoot !== fiberRoot.current) {
199 | treeCreator(fiberRoot.current);
200 | }
201 | }, 200);
202 | });
203 | };
204 |
--------------------------------------------------------------------------------