├── .DS_Store
├── .env
├── .gitignore
├── LICENSE
├── README.md
├── assets
└── images
│ ├── demoLoop.gif
│ ├── hifiber-128-new.png
│ ├── hifiber-16-new.png
│ ├── hifiber-32-new.png
│ ├── hifiber-48-new.png
│ ├── hifiber-logo-mini.png
│ ├── hifiber-logo-resized-new.png
│ └── hifiber-logo.png
├── build
├── bundle.js
└── index.html
├── d3-integration
├── Chart1.jsx
├── D3.css
├── D3.js
├── TreeChart.js
└── useResizeObserver.js
├── devtools
├── background.js
├── contentscript.js
├── devtools.html
├── devtools.js
└── panel.html
├── manifest.json
├── package-lock.json
├── package.json
├── public
├── favicon.ico
└── index.html
├── src
├── App.jsx
├── index.js
├── script.js
└── styles.css
└── webpack.config.js
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/HiFiber/2b32cd02fc1f44290538d62d35ef164051765dbb/.DS_Store
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | DISABLE_ESLINT_PLUGIN = true
2 | ESLINT_NO_DEV_ERRORS = true
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .vscode
3 | node_modules
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | React Fiber Tree Visualizer & Performance Metric Tool
6 |
7 | HiFiber is a React Fiber tree visualization tool that provides a dynamic representation of the Fiber tree as it loads and updates on your browser’s page. Working with pages built in React 16+, it maps out the application structure and will also measure performance metrics – including Fiber node start time, duration, rerender time/count, and various other useful Fiber properties.
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | 
16 |
17 |
18 | Installation
19 | •
20 | How to Use
21 | •
22 | Stretch Features
23 | •
24 | Meet the Team
25 | •
26 | License
27 | •
28 | How to Contribute
29 |
30 |
31 |
32 |
33 | ## About
34 | HiFiber is a versatile Chrome devtool that allows for easy frontend debugging and visualization. In our team's work, our aim is twofold; HiFiber is designed not only for the onboarding frontend engineer surveying the lay of the land of their company’s codebase – but is also developed with the more experienced frontend engineer in mind. HiFiber will offer robust frontend Fiber tree debugging, including allowing developers to quickly identify Fiber nodes that unnecessarily rerender, or hog valuable render time. It will also offer various visualization options, by default providing a clean and simple visual representation of the DOM after React has worked its magic through its reconciliation algorithm.
35 |
36 | ## Installation
37 | Our project will soon be published and hosted on the Chrome Web Store! For now, you can install HiFiber by downloading our codebase and manually storing its root directory where your other Chrome extensions are stored. Depending on the Operating System you use, this path will vary:
38 |
39 | Mac Users
40 | Chrome extensions are stored in: /Users/username/Library/Application Support/Google/Chrome/Default/Extensions
41 |
42 | You can navigate through Finder to access this path directly.
43 |
44 | Windows Users
45 | Chrome extensions are stored in: C:\Users\User_Name\AppData\Local\Google\Chrome\User Data\Default\Extensions
46 |
47 | You can manually type this path into Explorer’s navbar, or you can access it through Explorer physically by enabling the view of hidden files, folder and drives. To do this, press Windows key + E on your keyboard to open Explorer. Go to View > Options and check the box next to Show hidden files, folders or drives , and uncheck the box next to Hide protected operating system files . Click OK . You can now manually navigate to the above path.
48 |
49 | Linux Users
50 | Chrome extensions are stored in the filesystem , in the extensions folder , which is found in Chrome’s user data directory .
51 |
52 | After you’ve placed the extension in the appropriate directory, now it’s time to install it to your browser. Open Chrome and click the puzzle piece icon on the upper right corner of your browser. This is the tab for your Extensions. At the bottom of the tab, click Manage Extensions then click Load Unpacked . Navigate to the directory that you’ve installed HiFiber in and click Select Folder . You’ve now installed HiFiber in your browser! The Extension will read the `manifest.json` to interface the extension with your browser.
53 |
54 | ## How to Use
55 | Once you’ve installed and enabled HiFiber, the front end world is your oyster! Simply navigate to a page built in React 16+ and click the Manage Extensions button again. Click the icon for HiFiber and voila – a devtool panel will automatically appear with the frontend visualization of the app’s Fiber tree.
56 |
57 | Once you are in the app, an animation will show your tree being created. You will have the option to view a Simple or Full tree .
58 |
59 | Simple : Shows the nodes that represent HTML, Class, or Functional components. High level nodes most likely what a debugger is working with. Also simpler for basic onboarding.
60 |
61 | Full : Shows the entirety of every node on the fiber tree. This is better for educational purposes, and occasional deep dive debugging. Many of the nodes are excessive and not useful to the average coder.
62 |
63 | Next you will be able to interact with your tree. You can click on any node to reveal additional information for it.
64 |
65 | - Type of node
66 | - Render time
67 | - State and previous state values
68 | - Active or Idle
69 | - React Tag
70 | - Line number
71 | - Key
72 |
73 | Error Handling : When something breaks in your code, The tree will highlight and blink on the breaking component. This will allow the user to locate the line that the breaking logic is happening on.
74 |
75 | ## Stretch Features
76 | Containerization
77 | We get it: Everyone’s dev environment looks different. Whether you’re a Linux user on an older version of Chrome, a Mac OS user with Chrome’s latest release, or anything in between, we feel it’s important our users have easy access to HiFiber in their Chrome dev environment. That’s why we’re currently working to switch from our current monolithic application architecture to one that’s containerized – so all of its dependencies are packaged up in a standalone environment. Now, all of HiFiber’s tools will be readily available for you in a lightweight and standardized deployment.
78 |
79 |
80 | Data Visualization with D3.js
81 | While we’re proud of the elegant styling our DOM traversal algorithm yields to the frontend visualization, we also want our performance metrics to be represented equally as beautifully. That’s why we plan on utilizing D3.js, a popular JavaScript library suitable for manipulating/visualizing documents based on data. Using HTML, SVG and CSS, D3.js will allow our team to deliver clean and elegant visual representations of performance data each Fiber node yields upon render or rerender. Moreover, we’ll be able to visualize other Fiber node properties, making debugging your frontend fast, simple and fun!
82 |
83 | NoSQL Database Utilization
84 | Want to save performance metrics and compare them after subsequent builds of your app? We sure do! That’s why we’re planning on adding NoSQL database functionality, allowing users to effortlessly store performance metrics over time. Even further, we want our users to be able to compare performance reports across builds.
85 |
86 | ## Meet the Team
87 | - Lauren Acrich: [LinkedIn](https://www.linkedin.com/in/laurenacrich/) | [GitHub](https://github.com/lauren-acrich)
88 | - Matthew Birnholtz: [LinkedIn](https://www.linkedin.com/in/matthew-birnholtz-1b607a85/) | [GitHub](https://github.com/mattbirn93)
89 | - Michael Filoramo: [LinkedIn](https://www.linkedin.com/in/michael-filoramo/) | [GitHub](https://github.com/mfiloramo)
90 | - Mikel Giffin: [LinkedIn](https://www.linkedin.com/in/mikel-giffin-69480678/) | [GitHub](https://github.com/giffinmike)
91 | - Adrian Karnani: [LinkedIn](https://www.linkedin.com/in/adriankarnani/) | [GitHub](https://github.com/adriangk228)
92 |
93 | ## License
94 | ```
95 | MIT License
96 |
97 | Copyright © 2022 OSLabs Beta
98 |
99 | 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
100 | 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:
101 |
102 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
103 |
104 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
105 | ```
106 |
107 | ## How to Contribute
108 | Feel free to fork our repo, add or fix features, and send pull requests! Our application is growing each day, and we always have an open mind to others’ input on bug fixes and additions. If you have ideas for stretch features, or wish to contribute to the already planned ones, we’d love your input. After all, open source is love!
109 |
110 |
--------------------------------------------------------------------------------
/assets/images/demoLoop.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/HiFiber/2b32cd02fc1f44290538d62d35ef164051765dbb/assets/images/demoLoop.gif
--------------------------------------------------------------------------------
/assets/images/hifiber-128-new.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/HiFiber/2b32cd02fc1f44290538d62d35ef164051765dbb/assets/images/hifiber-128-new.png
--------------------------------------------------------------------------------
/assets/images/hifiber-16-new.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/HiFiber/2b32cd02fc1f44290538d62d35ef164051765dbb/assets/images/hifiber-16-new.png
--------------------------------------------------------------------------------
/assets/images/hifiber-32-new.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/HiFiber/2b32cd02fc1f44290538d62d35ef164051765dbb/assets/images/hifiber-32-new.png
--------------------------------------------------------------------------------
/assets/images/hifiber-48-new.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/HiFiber/2b32cd02fc1f44290538d62d35ef164051765dbb/assets/images/hifiber-48-new.png
--------------------------------------------------------------------------------
/assets/images/hifiber-logo-mini.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/HiFiber/2b32cd02fc1f44290538d62d35ef164051765dbb/assets/images/hifiber-logo-mini.png
--------------------------------------------------------------------------------
/assets/images/hifiber-logo-resized-new.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/HiFiber/2b32cd02fc1f44290538d62d35ef164051765dbb/assets/images/hifiber-logo-resized-new.png
--------------------------------------------------------------------------------
/assets/images/hifiber-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/HiFiber/2b32cd02fc1f44290538d62d35ef164051765dbb/assets/images/hifiber-logo.png
--------------------------------------------------------------------------------
/build/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | HiFiber
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/d3-integration/Chart1.jsx:
--------------------------------------------------------------------------------
1 | import * as d3 from 'd3-integration/D3';
2 |
3 |
4 | function Tree(
5 | data,
6 | {
7 | path,
8 | id = Array.isArray(data) ? (d) => d.id : null,
9 | parentId = Array.isArray(data) ? (d) => d.parentId : null,
10 | children,
11 | tree = d3.tree,
12 | sort,
13 | label,
14 | title,
15 | link,
16 | linkTarget = '_blank', /
17 | width = 640,
18 | height,
19 | r = 3,
20 | padding = 1,
21 | fill = '#999',
22 | fillOpacity,
23 | stroke = '#555',
24 | strokeWidth = 1.5,
25 | strokeOpacity = 0.4,
26 | strokeLinejoin,
27 | strokeLinecap,
28 | halo = '#fff',
29 | haloWidth = 3,
30 | } = {}
31 | ) {
32 |
33 | const root =
34 | path != null
35 | ? d3.stratify().path(path)(data)
36 | : id != null || parentId != null
37 | ? d3.stratify().id(id).parentId(parentId)(data)
38 | : d3.hierarchy(data, children);
39 |
40 | if (sort != null) root.sort(sort);
41 |
42 | const descendants = root.descendants();
43 | const L = label == null ? null : descendants.map((d) => label(d.data, d));
44 |
45 | const dx = 10;
46 | const dy = width / (root.height + padding);
47 | tree().nodeSize([dx, dy])(root);
48 |
49 | let x0 = Infinity;
50 | let x1 = -x0;
51 | root.each((d) => {
52 | if (d.x > x1) x1 = d.x;
53 | if (d.x < x0) x0 = d.x;
54 | });
55 |
56 | if (height === undefined) height = x1 - x0 + dx * 2;
57 |
58 | const svg = d3
59 | .create('svg')
60 | .attr('viewBox', [(-dy * padding) / 2, x0 - dx, width, height])
61 | .attr('width', width)
62 | .attr('height', height)
63 | .attr('style', 'max-width: 100%; height: auto; height: intrinsic;')
64 | .attr('font-family', 'sans-serif')
65 | .attr('font-size', 10);
66 |
67 | svg
68 | .append('g')
69 | .attr('fill', 'none')
70 | .attr('stroke', stroke)
71 | .attr('stroke-opacity', strokeOpacity)
72 | .attr('stroke-linecap', strokeLinecap)
73 | .attr('stroke-linejoin', strokeLinejoin)
74 | .attr('stroke-width', strokeWidth)
75 | .selectAll('path')
76 | .data(root.links())
77 | .join('path')
78 | .attr(
79 | 'd',
80 | d3
81 | .linkHorizontal()
82 | .x((d) => d.y)
83 | .y((d) => d.x)
84 | );
85 |
86 | const node = svg
87 | .append('g')
88 | .selectAll('a')
89 | .data(root.descendants())
90 | .join('a')
91 | .attr('xlink:href', link == null ? null : (d) => link(d.data, d))
92 | .attr('target', link == null ? null : linkTarget)
93 | .attr('transform', (d) => `translate(${d.y},${d.x})`);
94 |
95 | node
96 | .append('circle')
97 | .attr('fill', (d) => (d.children ? stroke : fill))
98 | .attr('r', r);
99 |
100 | if (title != null) node.append('title').text((d) => title(d.data, d));
101 |
102 | if (L)
103 | node
104 | .append('text')
105 | .attr('dy', '0.32em')
106 | .attr('x', (d) => (d.children ? -6 : 6))
107 | .attr('text-anchor', (d) => (d.children ? 'end' : 'start'))
108 | .attr('paint-order', 'stroke')
109 | .attr('stroke', halo)
110 | .attr('stroke-width', haloWidth)
111 | .text((d, i) => L[i]);
112 |
113 | return svg.node();
114 | }
115 |
116 | export default Tree;
117 |
--------------------------------------------------------------------------------
/d3-integration/D3.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: rgb(34, 34, 34);
3 | }
4 | svg {
5 | display: block;
6 | width: 800px;
7 | height: 700px;
8 | overflow: visible;
9 | }
10 |
11 | svg rect.selection {
12 | stroke: none;
13 | }
14 |
15 | h1 {
16 | color: white;
17 | font-family: SF Pro Display, SF Pro Icons, Helvetica Neue, Helvetica, Arial, sans-serif;
18 | font-size: 40px;
19 | font-weight: 900;
20 | display: flex;
21 | align-items: center;
22 | margin-left: 105px;
23 | padding-top: 30px;
24 | position: relative
25 | }
26 |
27 | h2 {
28 | color: white;
29 | font-family: SF Pro Display, SF Pro Icons, Helvetica Neue, Helvetica, Arial, sans-serif;
30 | font-size: 15px;
31 | font-weight: 900;
32 | display: flex;
33 | align-items: center;
34 | margin-left: 45px;
35 | position: relative
36 | }
37 |
38 | #root {
39 | background-color: 'black';
40 | max-width: 600px;
41 | margin: 0 auto;
42 | display: flex;
43 | flex-direction: column;
44 | align-items: stretch;
45 | justify-content: center;
46 | height: 100vh;
47 | padding: 0 50px;
48 | text-align: center;
49 | box-sizing: border-box;
50 | }
51 |
52 | button {
53 | width: 100px;
54 | border: 1px solid black;
55 | margin: 0 auto 5px;
56 | }
57 |
58 | video {
59 | position: fixed;
60 | bottom: 0;
61 | left: 0;
62 | right: 0;
63 | top: auto;
64 | width: 100%;
65 | }
66 |
67 | .fields {
68 | margin-bottom: 1rem;
69 | }
70 |
71 | .field {
72 | display: inline-block;
73 | margin: 0 0.5em;
74 | }
75 |
76 | .field input {
77 | margin-right: 0.5em;
78 | }
79 |
80 | .buttonComponent1 {
81 | color: white;
82 | font-family: SF Pro Display, SF Pro Icons, Helvetica Neue, Helvetica, Arial, sans-serif;
83 | font-size: 15px;
84 | font-weight: 900;
85 | display: flex;
86 | align-items: center;
87 | margin-left: 45px;
88 | position: relative
89 | }
--------------------------------------------------------------------------------
/d3-integration/D3.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef } from 'react';
2 | import TreeChart from './TreeChart';
3 | import './D3.css';
4 |
5 | const initialData = {
6 | name: 'App',
7 | children: [
8 | {
9 | name: 'Child1',
10 | children: [
11 | {
12 | name: 'Sibling3',
13 | },
14 | {
15 | name: 'Child2',
16 | },
17 | {
18 | name: 'Sibling4',
19 | },
20 | ],
21 | },
22 | ],
23 | };
24 |
25 | function App() {
26 | const [data, setData] = useState(initialData);
27 | const videoRef = useRef();
28 |
29 | useEffect(() => {
30 | navigator.mediaDevices
31 | .getUserMedia({ video: true, audio: false })
32 | .then((stream) => {
33 | videoRef.current.srcObject = stream;
34 | videoRef.current.play();
35 | });
36 | }, []);
37 |
38 | return (
39 |
40 | HI FIBER
41 | A great way to visually see the rendering of the Fiber Tree
42 |
43 | setData(initialData.children[0])}>
44 | Update data
45 |
46 |
47 | );
48 | }
49 |
50 | export default App;
--------------------------------------------------------------------------------
/d3-integration/TreeChart.js:
--------------------------------------------------------------------------------
1 | import React, { useRef, useEffect } from 'react';
2 | import { select, hierarchy, tree, linkVertical, linkHorizontal } from 'd3-integration/D3';
3 | import useResizeObserver from './useResizeObserver';
4 |
5 | function TreeChart({ data }) {
6 | const svgRef = useRef();
7 | const wrapperRef = useRef();
8 | const dimensions = useResizeObserver(wrapperRef);
9 |
10 |
11 | useEffect(() => {
12 | const svg = select(svgRef.current);
13 | if (!dimensions) return;
14 |
15 | const root = hierarchy(data);
16 | const treeLayout = tree().size([dimensions.width, dimensions.height]);
17 |
18 |
19 | treeLayout(root);
20 |
21 | console.log('Descendants', root.descendants());
22 | console.log('Links', root.links());
23 |
24 |
25 | const linkGenerator = linkHorizontal()
26 | .x((node) => node.y)
27 | .y((node) => node.x);
28 |
29 | // nodes
30 | svg
31 | .selectAll('.node')
32 | .data(root.descendants())
33 | .join('circle')
34 | .attr('class', 'node')
35 | .attr('r', 12)
36 | .attr('fill', 'blue')
37 | .attr('cx', (node) => node.y)
38 | .attr('cy', (node) => node.x);
39 |
40 | // links
41 | svg
42 | .selectAll('.link')
43 | .data(root.links())
44 | .join('path')
45 | .attr('class', 'link')
46 | .attr('fill', 'none')
47 | .attr('stroke', 'black')
48 | .attr('d', linkGenerator)
49 | .style('stroke', 'red')
50 | .attr('stroke-dasharray', function () {
51 | const length = this.getTotalLength();
52 | return `${length} ${length}`;
53 | })
54 | .attr('stroke-dashoffset', function () {
55 | const length = this.getTotalLength();
56 | return length;
57 | })
58 | .transition()
59 | .duration(1000)
60 | .delay((linkObj) => linkObj.source.depth * 1000)
61 | .attr('stroke-dashoffset', 0);
62 |
63 | // labels
64 | svg
65 | .attr('stroke', 'white')
66 | .selectAll('.label')
67 | .data(root.descendants())
68 | .join('text')
69 | .attr('class', 'label')
70 | .text((node) => node.data.name)
71 | .attr('text-anchor', 'middle')
72 | .attr('font-size', 15)
73 | .attr('x', (node) => node.y)
74 | .attr('y', (node) => node.x - 10)
75 | .transition()
76 | .duration(1000)
77 | .delay((node) => node.depth * 1000)
78 | .attr('opcacity', 1);
79 | }, [data, dimensions]);
80 |
81 | return (
82 |
83 |
84 |
85 | );
86 | }
87 |
88 | export default TreeChart;
89 |
90 |
--------------------------------------------------------------------------------
/d3-integration/useResizeObserver.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import ResizeObserver from 'resize-observer-polyfill';
3 |
4 | const useResizeObserver = (ref) => {
5 | const [dimensions, setDimensions] = useState(null);
6 | useEffect(() => {
7 | const observeTarget = ref.current;
8 | const resizeObserver = new ResizeObserver((entries) => {
9 | entries.forEach((entry) => {
10 | setDimensions(entry.contentRect);
11 | });
12 | });
13 | resizeObserver.observe(observeTarget);
14 | return () => {
15 | resizeObserver.unobserve(observeTarget);
16 | };
17 | }, [ref]);
18 | return dimensions;
19 | };
20 |
21 | export default useResizeObserver;
22 |
--------------------------------------------------------------------------------
/devtools/background.js:
--------------------------------------------------------------------------------
1 | //this file loads when the extension is launched and
2 | //wont be terminated until extension removed or browser shutdown
3 | //Background code has access to all Chrome APIs, but doesn't have a UI and cannot access DOM.
4 |
--------------------------------------------------------------------------------
/devtools/contentscript.js:
--------------------------------------------------------------------------------
1 | //import {rootNode} from "./script";
2 |
3 | // TRYING TO LOG THE ROOT NODE FROM THE CONTENTSCRIPT.JS BUT CAN'T YET
4 | // console.log('This is the root node from the contentscript.js');
5 | //console.log(rootNode);
6 |
7 | // CAN WE IMPORT THE ROOT NODE FROM SCRIPT.JS AND EXPORT IT, OR MOUNT IT, TO THE DOM? TO THE APP?
8 |
9 | // const mainPage = document.getElementsByTagName('body')[0];
10 |
11 | // WHAT IF THIS WAS THE BUNDLE.JS?
12 | // const mountedScript = document.createElement('script');
13 | // mountedScript.setAttribute('type', 'module');
14 | // mountedScript.setAttribute('src', 'chrome-extension://gbaoapbpjbfkjacnebobjbpmnnidhjno/script.js');
15 | // mountedScript.setAttribute('src', 'chrome-extension://gbaoapbpjbfkjacnebobjbpmnnidhjno/build/bundle.js');
16 | // mainPage.appendChild(mountedScript);
17 |
18 | //if you want to manipulate the DOM, you need the content script.
19 | //this script runs in the browser - on all URLs in our case
20 | //console.log('This is the contentscript. The window document object, ran at document_idle, should be below...');
21 | // console.dir(Object.values(window.document.body));
22 | // console.dir(window.document.body);
23 |
24 |
--------------------------------------------------------------------------------
/devtools/devtools.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | HiFiber
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/devtools/devtools.js:
--------------------------------------------------------------------------------
1 | chrome.devtools.panels.create(
2 | 'HiFiber',
3 | '/images/hifiber_16.png',
4 | 'panel.html',
5 | () => {}
6 | );
7 |
--------------------------------------------------------------------------------
/devtools/panel.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | HiFiber
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "HiFiber",
3 | "description": "A handy tool that dynamically visualizes the React Fiber tree on any React app",
4 | "version": "1.0",
5 | "minimum_chrome_version": "10.0",
6 | "manifest_version": 3,
7 | "devtools_page": "./devtools/devtools.html",
8 | "background": {
9 | "service_worker": "./devtools/background.js"
10 | },
11 | "content_scripts": [
12 | {
13 | "matches": ["http://*/*"],
14 | "js": ["./devtools/contentscript.js"],
15 | "css": ["src/styles.css"]
16 | }
17 | ],
18 | "web_accessible_resources": [
19 | {
20 | "resources": ["./src/script.js"],
21 | "matches": ["http://*/*"],
22 | "run_at": "document_idle"
23 | }
24 | ],
25 | "permissions": ["storage", "activeTab", "scripting", "tabs"],
26 | "host_permissions": [""],
27 |
28 | "action": {
29 | "default_popup": "popup.html",
30 | "default_icon": {
31 | "16": "/assets/images/hifiber-16-new.png",
32 | "32": "/assets/images/hifiber-32-new.png",
33 | "48": "/assets/images/hifiber-48-new.png",
34 | "128": "/assets/images/hifiber-128-new.png"
35 | }
36 | },
37 | "icons": {
38 | "16": "/assets/images/hifiber-16-new.png",
39 | "32": "/assets/images/hifiber-32-new.png",
40 | "48": "/assets/images/hifiber-48-new.png",
41 | "128": "/assets/images/hifiber-128-new.png"
42 | }
43 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "@babel/polyfill": "^7.12.1",
4 | "autoprefixer": "10.4.5",
5 | "docker": "^1.0.0",
6 | "eslint": "^8.20.0",
7 | "install": "0.13.0",
8 | "nodemon": "^2.0.19",
9 | "react": "^18.2.0",
10 | "react-dom": "^18.2.0",
11 | "react-scripts": "^5.0.1"
12 | },
13 | "scripts": {
14 | "build": "webpack --config webpack.config.js",
15 | "start": "react-scripts start"
16 | },
17 | "browserslist": {
18 | "production": [
19 | ">0.2%",
20 | "not dead",
21 | "not op_mini all"
22 | ],
23 | "development": [
24 | "last 1 chrome version",
25 | "last 1 firefox version",
26 | "last 1 safari version"
27 | ]
28 | },
29 | "devDependencies": {
30 | "sass": "^1.53.0",
31 | "webpack-cli": "^4.10.0"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/HiFiber/2b32cd02fc1f44290538d62d35ef164051765dbb/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | HiFiber
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/App.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef, Component } from 'react';
2 | import './styles.css';
3 |
4 |
5 | const App = () => {
6 | const [demo, setDemo] = useState(1);
7 | const [divide, setDivide] = useState(null);
8 | const [variable, setVariable] = useState(0);
9 | const [array, setArray] = useState([]);
10 | const [array2, setArray2] = useState([]);
11 | const [textBox, setTextBox] = useState(null);
12 | const [boxVisibility, setBoxVisibility] = useState("hidden");
13 | const tempClick= useRef();
14 | const nullArrTracker = useRef(0);
15 | const newObj = ;
16 |
17 |
18 | function demoButton() {
19 | setDemo(2);
20 | }
21 |
22 | //Quits out of node text box
23 | // useEffect(() =>
24 | // window.addEventListener("keydown", (e) => {
25 | // if (e.key === "Escape") {
26 | // setBoxVisibility("hidden");
27 | // if (tempClick.current || tempClick.current === 0) {
28 | // const prevNode = document.getElementById(tempClick.current);
29 | // prevNode.style.borderColor = "black";
30 | // }
31 | // }
32 | // }), []);
33 |
34 |
35 | //Traverses the fibernode starting at the root node.
36 | //As it traverses it creates three arrays to be displayed by clicking "DEMO".
37 | //One array displays the node buttons. One displays the lines connecting them. And the last displays an information card for the node.
38 | //the information card contains information about the node and is displayed by clicking on the nodes
39 | useEffect(() => {
40 | //NOT IMPORTANT for DEV TOOL, only neccesarry for demo application
41 | if (variable == 2) {
42 | let current = newObj._owner;
43 | console.log(current)
44 | const head = current;
45 | class NodeMaker {
46 | constructor(key, x, y, duration, tag, lineNumber, parent, type, alternate) {
47 | this.key = key;
48 | this.x = x;
49 | this.y = y;
50 | this.duration= duration;
51 | this.tag = tag;
52 | this.lineNumber = lineNumber;
53 | this.parent = parent;
54 | this.type = type;
55 | this.alternate=alternate;
56 | }
57 | }
58 | let nodeMade = null;
59 | let nodeTracker = [];
60 | let arr = [head];
61 | let lineList = [
62 |
,
70 | ];
71 |
72 | let nodeList = [
73 |
85 | APP
86 | {current.type}
87 | ,
88 | ];
89 |
90 | current = current.child;
91 |
92 | let yDepth = 3;
93 | let xDepth = 1;
94 | let previousX = 0;
95 | let initX = 1;
96 | let i = 0;
97 |
98 | while (current !== head) {
99 | if (arr.indexOf(current) === -1) {
100 | if (current.key){
101 | nullArrTracker.current = nullArrTracker.current + 1;
102 | nodeMade = new NodeMaker(
103 | current.key,
104 | xDepth,
105 | yDepth,
106 | parseFloat(current.actualDuration.toFixed(6)),
107 | current.tag,
108 | current._debugSource.lineNumber,
109 | current.return.key,
110 | current.type.toUpperCase(),
111 | current.alternate
112 | );
113 | }
114 | if (!current.key) {
115 | nullArrTracker.current = nullArrTracker.current + 1;
116 |
117 | nodeMade = new NodeMaker(
118 | nullArrTracker.current,
119 | xDepth,
120 | yDepth,
121 | parseFloat(current.actualDuration.toFixed(6)),
122 | current.tag,
123 | current.return._debugSource.lineNumber,
124 | current.return.key,
125 | "TEXT",
126 | current.alternate
127 | );
128 | }
129 | nodeTracker.push(nodeMade);
130 | arr.push(current);
131 | nodeList.push(
132 |
146 | {current.type != null ? current.type : ''}{' '}
147 | {current.key != null ? current.key : 'Text'}{' '}
148 |
149 | );
150 | i++;
151 | if (current.child) {
152 | initX = xDepth;
153 | current = current.child;
154 |
155 | lineList.push(
156 |
164 | );
165 | yDepth++;
166 | continue;
167 | }
168 | if (current.sibling) {
169 | current = current.sibling;
170 |
171 | lineList.push(
172 |
180 | );
181 |
182 | xDepth++;
183 | continue;
184 | }
185 | if (!current.sibling && !current.child) {
186 | previousX = xDepth;
187 | current = current.return;
188 | initX = nodeTracker.filter(
189 | (el) => el.alternate === current.alternate
190 | )[0].x;
191 | yDepth--;
192 | continue;
193 | }
194 | }
195 | if (arr.indexOf(current) !== -1) {
196 | if (current.sibling) {
197 | lineList.push(
198 |
207 | );
208 | current = current.sibling;
209 | xDepth++;
210 | continue;
211 | }
212 |
213 | current = current.return;
214 | continue;
215 | }
216 | }
217 |
218 | let variable = null;
219 |
220 | setTimeout(() => {
221 | for (let j = 0; j < nodeList.length; j++) {
222 | variable = document.getElementById(j);
223 | variable.addEventListener('click', () => {
224 | loadModal(j);
225 | })
226 | }
227 | }, 10)
228 |
229 | //this function is pushing html elements into modalArr
230 | //and then it is setting this array as the textbox
231 | function loadModal(j) {
232 | //check if there is a previous node that was clicked
233 | if (tempClick.current || tempClick.current === 0){
234 | const prevNode = document.getElementById(tempClick.current);
235 | prevNode.style.borderColor = "black";
236 | }
237 |
238 | const currentNode = document.getElementById(j);
239 | currentNode.style.borderColor = "white";
240 | tempClick.current=j;
241 |
242 | let modalArr = [];
243 | modalArr.push(
244 |
245 | Type: {nodeTracker[j].type}
246 | ,
247 | key: {nodeTracker[j].key}
, duration: {nodeTracker[j].duration}
, lineNumber: {nodeTracker[j].lineNumber}
, parent: {nodeTracker[j].parent}
);
248 |
249 | if (nodeTracker[j].duration===0) {
250 | modalArr.push(Idle
)
251 | }
252 | if (nodeTracker[j].duration!=0) {
253 | modalArr.push(Active
)
254 | }
255 |
256 | const tagRef= {
257 | 0: "Functional Component",
258 | 1: "Class Copmonent",
259 | 5: "HTML Element",
260 | 6: "Text Node",
261 | 7: "Text Node"
262 |
263 | }
264 | let tempVal= nodeTracker[j].tag;
265 |
266 | modalArr.push(tag: {tagRef[tempVal]}
)
267 | setTextBox(modalArr)
268 | console.log(tempVal)
269 |
270 | }
271 |
272 |
273 | setArray(nodeList);
274 | setArray2(lineList);
275 | }
276 | }, [demo]);
277 |
278 | function redX() {
279 | setBoxVisibility("hidden");
280 | if (tempClick.current || tempClick.current === 0){
281 | const prevNode = document.getElementById(tempClick.current);
282 | prevNode.style.borderColor = "black";
283 | }
284 | }
285 |
286 | //turn on node box
287 | useEffect(() => {
288 | if (demo>1) {
289 | setBoxVisibility("visible");
290 | }
291 | }, [textBox])
292 |
293 | useEffect(() => {
294 | setVariable((x) => x + 1);
295 | }, [demo]);
296 |
297 |
298 | //The Div with the key "contain" is the true functionality of the tool. Everything else is used to construct data for the tree;
299 | //if you want to expand the node tree, add more html components to be rendered to see their relation
300 | return (
301 |
302 |
303 |
304 |
305 |
306 |
307 | {' '}
308 | {divide} {demo}
309 |
310 | Subtract
311 |
312 |
{
314 | demoButton();
315 | }}
316 | key="Add"
317 | className='demo'
318 | style={{borderColor: ''}}
319 | >
320 | Demo
321 |
322 |
323 | {array}
324 | {array2}
325 |
326 | redX()} className="xButton">x
327 | {textBox}
328 |
329 |
330 |
331 |
332 |
333 | );
334 | };
335 |
336 |
337 | export default App;
338 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { StrictMode } from "react";
3 | import { createRoot } from "react-dom/client";
4 | import ReactDOM from 'react-dom/client'
5 | import App from './App.jsx';
6 |
7 |
8 | // const rootElement = document.getElementById("root");
9 | // const root = createRoot(rootElement);
10 |
11 | const root = ReactDOM.createRoot(document.getElementById("root"));
12 |
13 | root.render(
14 |
15 |
16 |
17 | );
--------------------------------------------------------------------------------
/src/script.js:
--------------------------------------------------------------------------------
1 | /** The 'alternate' of the current fiber is the fiber in progress, and the 'alternate' of the fiber in progress is the current fiber. The current fiber represents what is rendered already, and the in-progress fiber is conceptually the stack frame that has not returned. * */
2 |
3 | /** The main disadvantage of console.dir is that console.dir accepts only one object as an argument, but console.log can log multiple arguments at once * */
4 |
5 | // DEFINE THE ROOT NODE, WHICH POINTS TO THE VALUES OF THE FIRST CHILD IN THE DIFFED DOM
6 |
7 |
8 | // CONSOLE.LOG THE ROOT FIBER NODE FROM THE CONSOLE OF THE APP VIA SCRIPT.JS
9 | // THIS SCRIPT ALSO SEEMS TO RUN TWICE IN THE CONSOLE EACH INITIALIZATION OF THE APP AND EXTENSION -- WHY?
10 | // FIRST IT LOGS AS undefined, THEN THE SCRIPT FROM APP.JSX RUNS, THEN THIS SCRIPT RUNS AGAIN WITH THE ROOT NODE.
11 |
12 |
13 | // EXPORT THE ROOT NODE FOR OTHER PARTS OF THE APP TO ACCESS IT
14 | //export { rootNode }
15 | // const current = Object.values(window.document.body.children[0])[0].alternate;
16 |
17 | // console.dir(window.document.body);
18 | // console.log(current);
19 |
20 |
21 | // const rootNode = Object.values(window.document.body.children[0])[0];
22 |
23 |
24 | // CONSOLE.LOG THE ROOT FIBER NODE FROM THE CONSOLE OF THE APP VIA SCRIPT.JS
25 | // THIS SCRIPT ALSO SEEMS TO RUN TWICE IN THE CONSOLE EACH INITIALIZATION OF THE APP AND EXTENSION -- WHY?
26 | // FIRST IT LOGS AS undefined, THEN THE SCRIPT FROM APP.JSX RUNS, THEN THIS SCRIPT RUNS AGAIN WITH THE ROOT NODE.
27 | // console.log('This is the root node from script.js');
28 | // console.log(rootNode);
29 |
30 |
--------------------------------------------------------------------------------
/src/styles.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: rgb(31, 31, 31);
3 | }
4 |
5 | .nodes {
6 | border: 2px solid rgb(11, 8, 0);
7 | height: 100%;
8 | width: 100%;
9 | border-radius: 15%;
10 | color: rgb(255, 250, 250);
11 | background-color: #636060;
12 | box-shadow:
13 | 0 0 2px .25px rgb(255, 255, 255),
14 | 0 0 2px .75px rgb(84, 83, 83);
15 | font-family: SF Pro Display, SF Pro Icons, Helvetica Neue, Helvetica, Arial, sans-serif;
16 | font-size: 10%;
17 | z-index: 0;
18 | opacity: 1;
19 | transform: scale(1);
20 | }
21 |
22 | .nodes:active {
23 | background-color: rgb(101, 101, 248);
24 | box-shadow: 0 2px #666;
25 | transform: translateY(4px);
26 | opacity: 2;
27 | box-shadow:
28 | 0 0 2px 2px #fff,
29 | 0 0 5px 5px #f0f,
30 | 0 0 7px 7px #0ff;
31 | }
32 |
33 | .nodes:hover {
34 | transition: all .1s ease-in-out;
35 | transform: scale(1.2);
36 | }
37 |
38 | .container {
39 | display: grid;
40 | grid-template-columns: repeat(30, 50px);;
41 | grid-template-rows: repeat(10, 50px);
42 | row-gap: 8%;
43 | column-gap: 2%;
44 | }
45 |
46 | @keyframes blinker {
47 | 0% {
48 | opacity: 0}
49 | 80% {
50 | opacity: 0}
51 | 90% {
52 | opacity: 1;
53 | }
54 | }
55 |
56 | @keyframes example {
57 | 0% {
58 | width: 0;
59 | opacity: 0}
60 | 80% {
61 | width: 0;
62 | opacity: 0
63 | }
64 | 100% {
65 | opacity: 1;
66 | width: 100%;
67 | }
68 | }
69 |
70 | @keyframes example2 {
71 | 0% {
72 | height: 0;
73 | opacity: 0}
74 | 80% {
75 | height: 0;
76 | opacity: 0
77 | }
78 | 100% {
79 | opacity: 1;
80 | height: 100%;
81 | }
82 | }
83 |
84 | .reverse {
85 | animation-direction: reverse;
86 | }
87 |
88 | .lineHorizontal {
89 | z-index: -1;
90 | width: 100%;
91 | align-self: center;
92 | justify-self: end;
93 | box-shadow:
94 | 0 0 1px .5px rgb(250, 117, 246),
95 | 0 0 1px 1px rgb(250,
96 | 117,
97 | 246);
98 | }
99 |
100 | .lineVertical {
101 | z-index: -1;
102 | height: 100%;
103 | width: .01px;
104 | align-self: center;
105 | justify-self: center;
106 |
107 | box-shadow:
108 | 0 0 1px .5px rgb(101, 101, 248),
109 | 0 0 1px 1px rgb(101, 101, 248);
110 | }
111 |
112 | .textBox {
113 | border: 2px solid rgb(11, 8, 0);
114 | opacity: .9;
115 | height: 500%;
116 | width: 400%;
117 | border-radius: 5%;
118 | color: rgba(255, 250, 250, 0.865);
119 | background-color: #282727;
120 | box-shadow:
121 | 0 0 4px .25px rgb(255, 255, 255),
122 | 0 0 2px .75px rgb(84, 83, 83);
123 | font-family: SF Pro Display, SF Pro Icons, Helvetica Neue, Helvetica, Arial, sans-serif;
124 | font-size: 15px;
125 | z-index: 0;
126 | opacity: 1;
127 | grid-column-end: 8;
128 | }
129 |
130 | .demo {
131 | border: 2px solid rgb(11, 8, 0);
132 |
133 | height: 60px;
134 | width: 60px;
135 | border-radius: 100%;
136 | color: rgb(255, 250, 250);
137 | background-color: #282727;
138 | font-family: SF Pro Display, SF Pro Icons, Helvetica Neue, Helvetica, Arial, sans-serif;
139 | font-size: 15px;
140 | box-shadow: 0 2px #999;
141 | }
142 |
143 | .demo:hover {
144 | background-color: #3e8e41
145 | }
146 |
147 | .demo:active {
148 | background-color: #3e8e41;
149 | box-shadow: 0 2px #666;
150 | transform: translateY(4px);
151 | }
152 |
153 | .xButton {
154 | border: 1px solid rgb(11, 8, 0);
155 | opacity: .4;
156 | height: 22px;
157 | width: 22px;
158 | position: absolute;
159 | border-radius: 5%;
160 | margin-top: -50px;
161 | margin-left: 65px;
162 | background-color: rgba(147, 145, 145, 0.823);
163 | font-family: SF Pro Display, SF Pro Icons, Helvetica Neue, Helvetica, Arial, sans-serif;
164 | font-size: 14px;
165 | }
166 |
167 | .xButton:active {
168 | background-color: red;
169 | box-shadow: 0 1px #666;
170 | transform: translateY(1px);
171 | }
172 |
173 | .xButton:hover {
174 | background-color: red;
175 | }
176 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const HtmlWebpackPlugin = require('html-webpack-plugin');
3 | const miniCSS = require('mini-css-extract-plugin');
4 |
5 | module.exports = {
6 | entry: ['@babel/polyfill', './src/index.js'],
7 | stats: {
8 | children: true,
9 | },
10 | output: {
11 | path: path.resolve(__dirname, 'build'),
12 | publicPath: '/',
13 | filename: 'bundle.js',
14 | },
15 |
16 | devtool: 'inline-source-map',
17 | mode: 'development',
18 |
19 | devServer: {
20 | host: 'localhost',
21 | port: 8084,
22 | static: {
23 | publicPath: '/build',
24 | directory: path.resolve(__dirname, 'build'),
25 | },
26 | open: true,
27 | hot: true,
28 | proxy: {
29 | '/api/**': {
30 | target: 'http://localhost:3030',
31 | secure: false,
32 | },
33 | '/client/stylesheets/**': {
34 | target: 'http://localhost:3030',
35 | secure: false,
36 | },
37 | },
38 | },
39 |
40 | module: {
41 | rules: [
42 | {
43 | test: /.(js|jsx)$/,
44 | exclude: /node_modules/,
45 | use: [
46 | {
47 | loader: 'babel-loader',
48 | options: {
49 | presets: ['@babel/preset-env', '@babel/preset-react'],
50 | },
51 | },
52 | ],
53 | },
54 | {
55 | test: /\.(css|scss)$/,
56 | exclude: /node_modules/,
57 | use: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader'],
58 | },
59 | {
60 | test: /\.(svg|webp|ico|png|jpg|jpe?g|gif)$/i,
61 | type: 'asset/resource',
62 | },
63 | {
64 | test: /\.mp3$/,
65 | use: {
66 | loader: 'url-loader',
67 | },
68 | },
69 | ],
70 | },
71 |
72 | plugins: [
73 | new HtmlWebpackPlugin({
74 | template: './public/index.html',
75 | }),
76 | new miniCSS(),
77 | ],
78 | };
79 |
--------------------------------------------------------------------------------