Skip to content

Commit 15f4f3c

Browse files
authored
Merge pull request #16 from oslabs-beta/dev
Support for Context API, initial support for useState and useReducer hooks
2 parents 1f2a808 + 7e25ec7 commit 15f4f3c

20 files changed

+318
-44
lines changed

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
FROM node:10.16.2
2-
WORKDIR /usr/src/app
2+
WORKDIR /usr/src/app
33
COPY package*.json ./
44
RUN npm i

package-lock.json

Lines changed: 45 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "reacttime-extension",
2+
"name": "reactime",
33
"description": "build web extension bundle.js",
44
"scripts": {
55
"build": "webpack --mode production",
@@ -20,10 +20,20 @@
2020
"type": "git",
2121
"url": "https://github.com/oslabs-beta/reactime"
2222
},
23-
"author": "Bryan Lee, Josh Kim, Ryan Dang, Sierra Swaby",
23+
"contributors": [
24+
"Andy Wong",
25+
"Bryan Lee",
26+
"David Chai",
27+
"Josh Kim",
28+
"Ruthba Anam",
29+
"Ryan Dang",
30+
"Sierra Swaby",
31+
"Yujin Kang"
32+
],
2433
"license": "ISC",
2534
"devDependencies": {
2635
"@babel/core": "^7.5.5",
36+
"@babel/plugin-proposal-class-properties": "^7.5.5",
2737
"@babel/plugin-proposal-decorators": "^7.4.4",
2838
"@babel/preset-env": "^7.5.5",
2939
"@babel/preset-react": "^7.0.0",
@@ -49,6 +59,8 @@
4959
"webpack-cli": "^3.3.6"
5060
},
5161
"dependencies": {
62+
"acorn": "^7.1.0",
63+
"acorn-jsx": "^5.0.2",
5264
"d3": "^3.5.17",
5365
"immer": "^3.2.0",
5466
"jsondiffpatch": "^0.3.11",

package/__tests__/linkFiber.test.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* eslint-disable react/jsx-filename-extension */
2-
import React from 'react';
2+
import React, { Component, useState } from 'react';
33
import { render } from 'react-dom';
44

55
const linkFiberRequire = require('../linkFiber');
@@ -8,7 +8,7 @@ let linkFiber;
88
let mode;
99
let snapShot;
1010

11-
class App extends React.Component {
11+
class App extends Component {
1212
constructor(props) {
1313
super(props);
1414
this.state = { foo: 'bar' };
@@ -20,6 +20,17 @@ class App extends React.Component {
2020
}
2121
}
2222

23+
// Need to create a functioanl component instance to test
24+
// Would need to be revised but here's the gist
25+
// const funcComponent = () => {
26+
// const [ number, setNumber ] = useState(0);
27+
// const newNumber = setNumber(1);
28+
29+
// return (
30+
// <div>{newNumber}</div>
31+
// )
32+
// }
33+
2334
describe('unit test for linkFiber', () => {
2435
beforeEach(() => {
2536
snapShot = { tree: null };

package/__tests__/timeJump.test.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ class FiberNode {
1919
}
2020
}
2121

22+
// MVP FEATURE: Additional Testing
23+
// Testing for useState and useContext
24+
25+
2226
describe('unit testing for timeJump.js', () => {
2327
let timeJump;
2428
let snapShot;

package/astParser.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
const acorn = require('acorn');
2+
const jsx = require('acorn-jsx');
3+
const JSXParser = acorn.Parser.extend(jsx());
4+
5+
// Helper function to recursively traverse through the user's codebase
6+
// INSERT HERE
7+
8+
module.exports = file => {
9+
// Initialize empty object to store the setters and getter
10+
const hookState = {};
11+
const ast = JSXParser.parse(file).body;
12+
// Iterate through AST of every function declaration
13+
// Check within each function declaration if there are hook declarations
14+
ast.forEach(func => {
15+
const { body } = func.body;
16+
const statements = [];
17+
// Traverse through the function's funcDecs and Expression Statements
18+
body.forEach(program => {
19+
if (program.type === 'VariableDeclaration') {
20+
program.declarations.forEach(dec => {
21+
statements.push(dec.id.name);
22+
});
23+
}
24+
});
25+
// Iterate through the array and determine getter/setters based on pattern
26+
for (let i = 0; i < statements.length; i += 1) {
27+
if (statements[i].match(/_use/)) {
28+
hookState[statements[i]] = statements[i + 2];
29+
}
30+
}
31+
});
32+
// Return the object with setters and getters
33+
return hookState;
34+
};

package/linkFiber.js

Lines changed: 62 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
1+
/* eslint-disable func-names */
12
/* eslint-disable no-use-before-define */
23
/* eslint-disable no-param-reassign */
34
// links component state tree to library
45
// changes the setState method to also update our snapshot
56
const Tree = require('./tree');
7+
const astParser = require('./astParser.js');
68

79
module.exports = (snap, mode) => {
810
let fiberRoot = null;
11+
let astHooks;
912

1013
function sendSnapshot() {
1114
// don't send messages while jumping or while paused
15+
// DEV: So that when we are jumping to an old snapshot it wouldn't think we want to create new snapshots
1216
if (mode.jumping || mode.paused) return;
1317
const payload = snap.tree.getCopy();
18+
// console.log('payload', payload);
1419
window.postMessage({
1520
action: 'recordSnap',
1621
payload,
@@ -20,14 +25,12 @@ module.exports = (snap, mode) => {
2025
function changeSetState(component) {
2126
// check that setState hasn't been changed yet
2227
if (component.setState.linkFiberChanged) return;
23-
2428
// make a copy of setState
2529
const oldSetState = component.setState.bind(component);
26-
2730
// replace component's setState so developer doesn't change syntax
2831
// component.setState = newSetState.bind(component);
29-
component.setState = (state, callback = () => {}) => {
30-
// dont do anything if state is locked
32+
component.setState = (state, callback = () => { }) => {
33+
// don't do anything if state is locked
3134
// UNLESS we are currently jumping through time
3235
if (mode.locked && !mode.jumping) return;
3336
// continue normal setState functionality, except add sending message middleware
@@ -40,10 +43,51 @@ module.exports = (snap, mode) => {
4043
component.setState.linkFiberChanged = true;
4144
}
4245

46+
function changeUseState(component) {
47+
if (component.queue.dispatch.linkFiberChanged) return;
48+
// store the original dispatch function definition
49+
const oldDispatch = component.queue.dispatch.bind(component.queue);;
50+
// redefine the dispatch function so we can inject our code
51+
component.queue.dispatch = (fiber, queue, action) => {
52+
// don't do anything if state is locked
53+
if (mode.locked && !mode.jumping) return;
54+
oldDispatch(fiber, queue, action);
55+
setTimeout(() => {
56+
updateSnapShotTree();
57+
sendSnapshot();
58+
}, 100);
59+
};
60+
component.queue.dispatch.linkFiberChanged = true;
61+
}
62+
63+
// Helper function to traverse through the memoized state
64+
// TODO: WE NEED TO CLEAN IT UP A BIT
65+
function traverseHooks(memoizedState) {
66+
// Declare variables and assigned to 0th index and an empty object, respectively
67+
const memoized = {};
68+
let index = 0;
69+
astHooks = Object.values(astHooks);
70+
// while memoizedState is truthy, save the value to the object
71+
while (memoizedState && astHooks) {
72+
changeUseState(memoizedState);
73+
memoized[astHooks[index]] = memoizedState.memoizedState;
74+
// Reassign memoizedState to its next value
75+
memoizedState = memoizedState.next;
76+
// Increment the index by 2
77+
index += 2;
78+
}
79+
return memoized;
80+
}
81+
4382
function createTree(currentFiber, tree = new Tree('root')) {
4483
if (!currentFiber) return tree;
4584

46-
const { sibling, stateNode, child } = currentFiber;
85+
const {
86+
sibling,
87+
stateNode,
88+
child,
89+
memoizedState,
90+
} = currentFiber;
4791

4892
let nextTree = tree;
4993
// check if stateful component
@@ -53,7 +97,13 @@ module.exports = (snap, mode) => {
5397
// change setState functionality
5498
changeSetState(stateNode);
5599
}
56-
100+
// Check if the component uses hooks
101+
if (memoizedState && memoizedState.hasOwnProperty('baseState')) {
102+
// Add a traversed property and initialize to the evaluated result
103+
// of invoking traverseHooks, and reassign nextTree
104+
memoizedState.traversed = traverseHooks(memoizedState);
105+
nextTree = tree.appendChild(memoizedState);
106+
}
57107
// iterate through siblings
58108
createTree(sibling, tree);
59109
// iterate through children
@@ -67,18 +117,20 @@ module.exports = (snap, mode) => {
67117
snap.tree = createTree(current);
68118
}
69119

70-
return container => {
120+
return (container, entryFile) => {
71121
const {
72122
_reactRootContainer: { _internalRoot },
73123
_reactRootContainer,
74124
} = container;
75-
// only assign internal root if it actually exists
125+
// only assign internal rootp if it actually exists
76126
fiberRoot = _internalRoot || _reactRootContainer;
77-
updateSnapShotTree();
127+
// If hooks are implemented, traverse through the source code
128+
if (entryFile) astHooks = astParser(entryFile);
78129

130+
updateSnapShotTree();
79131
// send the initial snapshot once the content script has started up
80132
window.addEventListener('message', ({ data: { action } }) => {
81133
if (action === 'contentScriptStarted') sendSnapshot();
82134
});
83-
};
135+
}
84136
};

package/package-lock.json

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)