Skip to content

Commit 8446278

Browse files
authored
Merge pull request #153 from reactide/3.0-release
3.0 release
2 parents 860b6bf + 30e5e99 commit 8446278

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

81 files changed

+3821
-1277
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,5 @@ yarn.lock
77
/lib/new-project-template/new-project/.DS_Store
88
/lib/new-project/**
99
/lib/projInfo.js
10+
package-lock.json
11+
release-builds

README.md

Lines changed: 14 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,17 @@ Reactide is a cross-platform desktop application that offers a simulator, made f
77

88
##
99
<p align="center">
10-
<img alt="Reactide Screenshot" src="https://farm5.staticflickr.com/4911/44158127700_b8c3246b72_k.jpg">
10+
<img alt="Reactide Screenshot" src="https://live.staticflickr.com/65535/48503465947_77a9c3be2b_k.jpg">
1111

1212
</p>
1313

1414
## Get right to coding
15-
Reactide runs an integrated Node server and custom browser simulator, which works best with Webpack and Webpack dev-server. As projects evolve, the developer can continually track changes through live reloading directly in the development environment without the need for constant flipping to the browser. Reactide also offers integration with Create React App for faster project boilerplate configuration. The simulator and component tree are both functioning for Create-React-App made applications.
15+
Reactide runs an integrated Node server and custom browser simulator. As projects evolve, the developer can continually track changes through live reloading directly in the development environment without the need for constant flipping to the browser. Reactide also offers integration with Create React App for faster project boilerplate configuration. The simulator and component tree are both functioning for all React applications.
1616

1717
## State flow visualization.
1818
Managing state across a complex React application is the biggest pain point of developing React apps. Reactide offers a visual component tree that dynamically loads and changes based on components within the working directory while giving information about props and state at every component. By navigating through a live-representation of the architecture of a project, developers can quickly identify and pinpoint the parent-child relationships of even the most complex applications.
1919

20-
The component tree works by finding the entry point to your React application from the webpack.config.js file. It works out-of-the-box with Create React App.
20+
The component tree works out-of-the-box by finding the entry point to your React application that you provide inside the reactide.config.js file.
2121

2222
## Integrated Terminal for powerful commands and workflows
2323
The terminal is the life and blood of any IDE, allowing for complex manipulation of the file system, node, and even build-tools. Reactide offers a compatible terminal for running commands in bin/bash for Unix, and cmd for Windows to provide powerful workflows to even seasoned developers.
@@ -26,46 +26,30 @@ The terminal is the life and blood of any IDE, allowing for complex manipulation
2626
The Reactide IDE can be set up in two ways, the first is to bundle the electron app and run it as a native desktop App. The instructions are as follows:
2727

2828
1. go to your terminal and type the following:
29-
```bash
30-
git checkout 2.0-release
29+
```
30+
git checkout 3.0-release
3131
npm install
32-
npm run webpack
32+
npm run webpack-production
3333
npm run electron-packager
3434
```
3535
2. in your Reactide folder, navigate to the release-builds folder and double-click on Reactide (executable).
3636

3737
## To check out Reactide in developer mode follow these instructions:
3838
1. go to your terminal and type the following:
39-
```bash
40-
git checkout 2.0-release
39+
```
40+
git checkout 3.0-release
4141
npm install
42-
npm run webpack
42+
npm run webpack-production
4343
npm start
4444
```
4545

46-
Beware: the close simulator button works by creating a child node process and executing killall node on close, clicking on the 'close simulator' button in developer mode will cause the electron app to close as well.
46+
## Setting up the Simulator
47+
In order to take advantage of the live simulator, please follow the below steps in your project directory.
4748

48-
## Setting up Webpack dev-server to work with Simulator
49-
In order to take advantage of the live simulator, please follow the below steps in your project directory. We are assuming you have a basic webpack config file, which you can find an example of in our repo under the example folder.
49+
1. Go to the reactide.config.js file and change the .html and .js entry points to the relative path of your respective files.
50+
2. In the terminal run: `npm run reactide-server`
5051

51-
1. `npm install webpack dev-server -D`
52-
2. Go to your webpack.config.js file and add the following lines of code. Make sure you set the port to 8085.
53-
```js
54-
devServer: {
55-
publicPath: path.resolve(__dirname, '/build/'),
56-
port: 8085,
57-
hot: true,
58-
},
59-
plugins: [
60-
new webpack.HotModuleReplacementPlugin(),
61-
],
62-
mode: 'development',
63-
```
64-
3. Go to your package.json and add the following scripts under the "scripts" object:
65-
```js
66-
"dev-server": "webpack-dev-server"
67-
```
6852
For any questions, please look at the example project in the example folder for how to set-up webpack and dev-server.
6953

7054
## Contributors
71-
[Jin Choi](https://github.com/jinihendrix) | [Mark Marcelo](https://github.com/markmarcelo) | [Bita Djaghouri](https://github.com/bitadj) | [Pablo Lee](https://github.com/pablytolee) | [Ryan Yang](https://github.com/ryany1819) | [Oscar Chan](https://github.com/chanoscar0)
55+
[Jin Choi](https://github.com/jinihendrix) | [Mark Marcelo](https://github.com/markmarcelo) | [Bita Djaghouri](https://github.com/bitadj) | [Pablo Lee](https://github.com/pablytolee) | [Ryan Yang](https://github.com/ryany1819) | [Oscar Chan](https://github.com/chanoscar0) | [Juan Hart](https://github.com/juanhart1) | [Eric Pham](https://github.com/EP36) | [Khalid Umar](https://github.com/khalid050) | [Rocky Liao](https://github.com/seemsrocky)

example/build/index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ <h1>Build Tools Challenge</h1>
1313
<!-- <script src="build/bundle.js"></script> -->
1414

1515
<!-- uncomment below and comment out the tag above for webpack challenge -->
16-
<script src="build/webpack-bundle.js"></script>
16+
<!-- <script src="build/webpack-bundle.js"></script> -->
1717

18-
<script type="text/javascript" src="webpack-bundle.js"></script>
18+
<!-- <script type="text/javascript" src="webpack-bundle.js"></script> -->
1919
</body>
2020
</html>

icons/icon.icns

96.4 KB
Binary file not shown.

icons/icon.png

96.4 KB
Loading

icons/mac/icon.icns

79.7 KB
Binary file not shown.
79.7 KB
Binary file not shown.

importPath.js

Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
2+
const fs = require('fs');
3+
const path = require('path');
4+
//The Flow Parser is a JavaScript parser written in OCaml. It produces an AST that conforms to the ESTree spec and that mostly matches what esprima produces.
5+
const flowParser = require('flow-parser');
6+
7+
/**
8+
* Iterates through AST Object and returns the entry point for the Class or Function Block;
9+
* @param {Object} obj AST JSON Object
10+
*/
11+
function getClassEntry(obj) {
12+
let entry = null;
13+
// Start lookup if Program body has ClassDeclaration or inside ExportDefaultDeclaration has ClassDeclaration
14+
for (let elem of obj.body) {
15+
if (elem.type === 'ClassDeclaration') {
16+
return entry = elem.body;
17+
}
18+
else if (elem.type === 'ExportDefaultDeclaration' && elem.declaration.type === 'ClassDeclaration') {
19+
return entry = elem.declaration.body;
20+
}
21+
}
22+
return entry;
23+
}
24+
/**
25+
* grabs state of stateful Component if available;
26+
* @param {Object} obj AST object created from file at Class Block
27+
*/
28+
function grabState(obj) {
29+
let ret = [];
30+
let entry = getClassEntry(obj);
31+
if (entry) ret = digStateInClassBody(entry);
32+
return ret;
33+
}
34+
/**
35+
* traverses through AST object and returns entry point for constructor Object;
36+
* @param {Object} obj - classBody object from AST
37+
*/
38+
function digStateInClassBody(obj) {
39+
if (obj.type !== 'ClassBody')
40+
return;
41+
let ret = [];
42+
obj.body.forEach((elem) => {
43+
if (elem.type = "MethodDefinition" && elem.key.name === "constructor") {
44+
ret = digStateInBlockStatement(elem.value.body);
45+
}
46+
});
47+
return ret;
48+
}
49+
50+
function parseNestedObjects(stateValue, nested=false){
51+
//check if the array is nested. Only use for arrays
52+
const curr = nested ? stateValue : stateValue.value;
53+
if(curr.type === 'ObjectExpression'){
54+
//iterate through object properties and store them in values
55+
let values = {}
56+
for(let key in stateValue.value.properties){
57+
values[key] = parseNestedObjects(stateValue.value.properties[key])
58+
}
59+
return values
60+
}
61+
else if(curr.type === 'ArrayExpression'){
62+
let values = []
63+
let currObj = curr.elements;
64+
for(let key in currObj){
65+
values.push(parseNestedObjects(currObj[key],true))
66+
}
67+
return values
68+
}
69+
else {
70+
return curr.value;
71+
}
72+
}
73+
/**
74+
* traverses through AST BlockStatement object and returns the state of Component;
75+
* @param {*} obj
76+
*/
77+
function digStateInBlockStatement(obj) {
78+
if (obj.type !== 'BlockStatement') return;
79+
let ret = {};
80+
obj.body.forEach((element) => {
81+
if (element.type === "ExpressionStatement" && element.expression.type === "AssignmentExpression")
82+
if (element.expression.left.property.name === 'state') {
83+
if (element.expression.right.type === "ObjectExpression"){
84+
element.expression.right.properties.forEach(elem => {
85+
// ret[elem.key.name] = elem.value.value;
86+
ret[elem.key.name] = parseNestedObjects(elem)
87+
// console.log('parseNestedObjects return value', parseNestedObjects(elem))
88+
});
89+
}
90+
}
91+
});
92+
// console.log('return' ,ret)
93+
return ret;
94+
}
95+
96+
/**
97+
* parses through AST Object and returns an object of props
98+
* @param {Array} arrOfAttr - Array of AST Object attributes
99+
*/
100+
function grabAttr(arrOfAttr) {
101+
return arrOfAttr.reduce((acc, curr) => {
102+
if (curr.value.type === 'JSXExpressionContainer') {
103+
if (curr.value.expression.type === 'ArrowFunctionExpression' || curr.value.expression.type === 'FunctionExpression') {
104+
if(curr.value.expression.body.body) {
105+
acc[curr.name.name] = curr.value.expression.body.body[0].expression.callee.name
106+
} else {
107+
acc[curr.name.name] = curr.value.expression.body.callee.name
108+
}
109+
} else if (curr.value.expression.type === 'Literal') {
110+
acc[curr.name.name] = curr.value.expression.value;
111+
} else if (curr.value.expression.type === 'MemberExpression') {
112+
acc[curr.name.name] = curr.value.expression.property.name;
113+
} else if (curr.value.expression.type === 'ConditionalExpression') {
114+
let condition, consequent, alternate;
115+
if(curr.value.expression.test.type === 'MemberExpression') {
116+
condition = curr.value.expression.test.property.name;
117+
} else{
118+
condition = curr.value.expression.test.name;
119+
}
120+
if(curr.value.expression.consequent.type === 'MemberExpression') {
121+
consequent = curr.value.expression.consequent.property.name;
122+
} else {
123+
consequent = curr.value.expression.consequent.name;
124+
}
125+
if(curr.value.expression.alternate.type === 'MemberExpression') {
126+
alternate = curr.value.expression.alternate.property.name;
127+
} else{
128+
alternate = curr.value.expression.consequent.name;
129+
}
130+
acc[curr.name.name + 'True'] = {condition: condition, value:consequent};
131+
132+
acc[curr.name.name + 'False'] = {condition: condition, value: alternate}
133+
} else {
134+
acc[curr.name.name] = curr.value.expression.name;
135+
}
136+
} else {
137+
acc[curr.name.name] = curr.value.value;
138+
}
139+
return acc;
140+
},{})
141+
};
142+
/**
143+
* returns an Array of Objects with the name and path of IMPORT objects
144+
* @param {Object} json- AST Object
145+
*/
146+
function grabImportNameAndPath(json) {
147+
let output;
148+
//inputted AST has a property named body that is an array of objects
149+
//importObjectArr is filtered
150+
const importObjectArr = json.body.filter((importObj) => {
151+
//whatever importObjs have a 'type' value of 'ImportDeclaration' are what are returned
152+
if (importObj.type === 'ImportDeclaration') {
153+
return {
154+
importObj
155+
}
156+
}
157+
})
158+
//importObjectArr is mappped over and if the importObj.specifiers[0] is true/truthy, a new object is returned
159+
// console.log(importObjectArr, 'XXX');
160+
output = importObjectArr.map((importObj) => {
161+
if (importObj.specifiers[0]) {
162+
return {
163+
name: importObj.specifiers[0].local.name,
164+
path: importObj.source.value,
165+
}
166+
}
167+
})
168+
// output is reassigned to the array returned by filtering anything that isn't true/truthy
169+
// console.log(output, '***');
170+
output = output.filter((obj) => {
171+
if (obj) {
172+
return obj;
173+
}
174+
})
175+
// console.log(output, 'SSS');
176+
//an array of imported objects is returned
177+
return output;
178+
}
179+
/**
180+
* returns an Object with Component name and props from AST Object;
181+
* @param {Object} returnObj - AST object
182+
*/
183+
const constructComponentProps = (returnObj) => {
184+
const output = {};
185+
output[returnObj.openingElement.name.name] = grabAttr(returnObj.openingElement.attributes)
186+
return output;
187+
}
188+
/**
189+
* returns Object with name and props of current Component;
190+
* @param {String} jsxPath - Path of file to convert into a AST object
191+
*/
192+
function constructSingleLevel(jsxPath) {
193+
let reactObj = {};
194+
// fileContent stores the file at the jsxPath
195+
const fileContent = fs.readFileSync(jsxPath, { encoding: 'utf-8' });
196+
//jsonObj uses flowParser to turn the file into an AST
197+
let jsonObj = flowParser.parse(fileContent);
198+
// checks for Components in imports section
199+
let imports = grabImportNameAndPath(jsonObj);
200+
let componentTags = grabChildComponents(imports, fileContent);
201+
// checks if component is Stateful and grabs state;
202+
let state = grabState(jsonObj);
203+
// iterates through components array and creates object with Component name, props and path;
204+
if (componentTags !== null){
205+
componentTags.forEach(elem => {
206+
let ast = flowParser.parse(elem).body[0].expression
207+
reactObj = Object.assign(reactObj, constructComponentProps(ast));
208+
});
209+
imports = imports.filter(comp => {
210+
comp.props = reactObj[comp.name]
211+
return Object.keys(reactObj).includes(comp.name);
212+
});
213+
} else {
214+
imports = [];
215+
}
216+
let outputObj = {
217+
name: path.basename(jsxPath).split('.')[0],
218+
childProps: imports,
219+
stateProps: state,
220+
children: []
221+
}
222+
return outputObj;
223+
}
224+
/**
225+
* recursively traverses through all folders given from filePath and creates JSON Object;
226+
* @param {String} filePath Path to Component folder
227+
* @param {String} rootPath - name of File
228+
*/
229+
function constructComponentTree(filePath, rootPath) {
230+
// create object at current level;
231+
let result = constructSingleLevel(path.join(rootPath, filePath));
232+
// checks if current Object has children and traverses through children to create Object;
233+
if(result && Object.keys(result.childProps).length > 0){
234+
for(let childProp of result.childProps) {
235+
//creates new path for children components - if girootPath doesnt have an extension adds .js extension
236+
let fullPath = path.join(rootPath, childProp.path);
237+
let newRootPath = path.dirname(fullPath);
238+
let newFileName = path.basename(fullPath);
239+
let childPathSplit = newFileName.split('.');
240+
if (childPathSplit.length === 1)
241+
newFileName += '.js';
242+
let newFullPath = path.join(newRootPath, newFileName);
243+
//traverses through children
244+
result.children.push(constructComponentTree(newFileName, newRootPath));
245+
}
246+
}
247+
return result;
248+
}
249+
/**
250+
* returns an array of React Components in String Format, checks imports array and filters fileContent to find Components;
251+
* @param {Array} imports - Array of Objects with name and path of all Import Objects;
252+
* @param {String} fileContent - String of File Content;
253+
*/
254+
function grabChildComponents(imports, fileContent) {
255+
// console.log(imports, fileContent, '***');
256+
// grab all import object name from import array;
257+
let compNames = imports.reduce((arr, cur) => {
258+
// skips <Provider/> component from redux
259+
if (cur.name !== 'Provider') {
260+
arr.push(cur.name);
261+
}
262+
return arr;
263+
}, []);
264+
// console.log(compNames, 'XXX');
265+
// format all import names for regex
266+
compNames = compNames.join('|');
267+
let pattern = '<\s*(' + compNames + ')(>|(.|[\r\n])*?[^?]>)'
268+
const regExp = new RegExp(pattern, 'g');
269+
// finds all components that match imports;
270+
let matchedComponents = fileContent.match(regExp);
271+
272+
return matchedComponents;
273+
}
274+
275+
module.exports = {grabChildComponents, constructComponentTree, constructSingleLevel, constructComponentProps, grabImportNameAndPath, grabAttr, digStateInBlockStatement, digStateInClassBody, grabState, getClassEntry}

0 commit comments

Comments
 (0)