Skip to content

Commit

Permalink
Add Fiber Debugger (facebook#8033)
Browse files Browse the repository at this point in the history
* Build react-noop as a package

This lets us consume it from the debugger.

* Add instrumentation to Fiber

* Check in Fiber Debugger
  • Loading branch information
gaearon authored and acusti committed Mar 15, 2017
1 parent a2f99b0 commit 4089f5f
Show file tree
Hide file tree
Showing 22 changed files with 6,340 additions and 10 deletions.
6 changes: 6 additions & 0 deletions Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ module.exports = function(grunt) {
grunt.registerTask('npm-react-test:release', npmReactTestRendererTasks.buildRelease);
grunt.registerTask('npm-react-test:pack', npmReactTestRendererTasks.packRelease);

var npmReactNoopRendererTasks = require('./grunt/tasks/npm-react-noop');
grunt.registerTask('npm-react-noop:release', npmReactNoopRendererTasks.buildRelease);
grunt.registerTask('npm-react-noop:pack', npmReactNoopRendererTasks.packRelease);

grunt.registerTask('version-check', function() {
// Use gulp here.
spawnGulp(['version-check'], null, this.async());
Expand Down Expand Up @@ -186,6 +190,8 @@ module.exports = function(grunt) {
'npm-react-addons:pack',
'npm-react-test:release',
'npm-react-test:pack',
'npm-react-noop:release',
'npm-react-noop:pack',
'compare_size',
]);

Expand Down
15 changes: 15 additions & 0 deletions examples/fiber/debugger/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.

# dependencies
node_modules

# testing
coverage

# production
build

# misc
.DS_Store
.env
npm-debug.log
25 changes: 25 additions & 0 deletions examples/fiber/debugger/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Fiber Debugger

This is a debugger handy for visualizing how [Fiber](https://github.com/facebook/react/issues/6170) works internally.

**It is only meant to be used by React contributors, and not by React users.**

It is likely that it might get broken at some point. If it's broken, ping [Dan](https://twitter.com/dan_abramov).

### Running

First, `npm run build` in React root repo folder.

Then `npm install` and `npm start` in this folder.

Open `http://localhost:3000` in Chrome.

### Features

* Edit code that uses `ReactNoop` renderer
* Visualize how relationships between fibers change over time
* Current tree is displayed in green

![fiber debugger](https://d17oy1vhnax1f7.cloudfront.net/items/3R2W1H2M3a0h3p1l133r/Screen%20Recording%202016-10-21%20at%2020.41.gif?v=e4323e51)


20 changes: 20 additions & 0 deletions examples/fiber/debugger/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "react-fiber-debugger",
"version": "0.0.1",
"private": true,
"devDependencies": {
"react-scripts": "0.6.1"
},
"dependencies": {
"dagre": "^0.7.4",
"pretty-format": "^4.2.1",
"react": "^15.3.2",
"react-dom": "^15.3.2",
"react-draggable": "^2.2.2",
"react-motion": "^0.4.5"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build"
}
}
Binary file added examples/fiber/debugger/public/favicon.ico
Binary file not shown.
13 changes: 13 additions & 0 deletions examples/fiber/debugger/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.14.0/babel.min.js"></script>
<title>React App</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
215 changes: 215 additions & 0 deletions examples/fiber/debugger/src/App.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
import React, { Component } from 'react';
import Draggable from 'react-draggable';
import ReactNoop from '../../../../build/packages/react-noop-renderer';
import ReactFiberInstrumentation from '../../../../build/packages/react-noop-renderer/lib/ReactFiberInstrumentation';
import Editor from './Editor';
import Fibers from './Fibers';
import describeFibers from './describeFibers';

function getFiberState(root, workInProgress) {
if (!root) {
return null;
}
return describeFibers(root.current, workInProgress);
}

const defaultCode = `
log('Render <div>Hello</div>');
ReactNoop.render(<div>Hello</div>);
ReactNoop.flush();
log('Render <h1>Goodbye</h1>');
ReactNoop.render(<h1>Goodbye</h1>);
ReactNoop.flush();
`;

class App extends Component {
constructor(props) {
super(props);
this.state = {
code: defaultCode,
isEditing: false,
history: [],
currentStep: 0,
show: {
alt: false,
child: true,
sibling: true,
return: false,
fx: false,
progressedChild: false,
progressedDel: false
}
};
}

componentDidMount() {
this.runCode(this.state.code);
}

runCode(code) {
let currentStage;
let currentRoot;

ReactFiberInstrumentation.debugTool = {
onMountContainer: (root) => {
currentRoot = root;
},
onUpdateContainer: (root) => {
currentRoot = root;
},
onWillBeginWork: (fiber) => {
const fibers = getFiberState(currentRoot, fiber);
const stage = currentStage;
this.setState(({ history }) => ({
history: [
...history, {
action: 'willBeginWork',
fibers,
stage
}
]
}));
},
onDidBeginWork: (fiber) => {
const fibers = getFiberState(currentRoot, fiber);
const stage = currentStage;
this.setState(({ history }) => ({
history: [
...history, {
action: 'didBeginWork',
fibers,
stage
}
]
}));
},
onWillCompleteWork: (fiber) => {
const fibers = getFiberState(currentRoot, fiber);
const stage = currentStage;
this.setState(({ history }) => ({
history: [
...history, {
action: 'willCompleteWork',
fibers,
stage
}
]
}));
},
onDidCompleteWork: (fiber) => {
const fibers = getFiberState(currentRoot, fiber);
const stage = currentStage;
this.setState(({ history }) => ({
history: [
...history, {
action: 'didCompleteWork',
fibers,
stage
}
]
}));
},
};
window.React = React;
window.ReactNoop = ReactNoop;
window.log = s => currentStage = s;
// eslint-disable-next-line
eval(window.Babel.transform(code, {
presets: ['react', 'es2015']
}).code);
}

handleEdit = (e) => {
e.preventDefault();
this.setState({
isEditing: true
});
}

handleCloseEdit = (nextCode) => {
this.setState({
isEditing: false,
history: [],
currentStep: 0,
code: nextCode
});
this.runCode(nextCode);
}

render() {
const { history, currentStep, isEditing, code } = this.state;
if (isEditing) {
return <Editor code={code} onClose={this.handleCloseEdit} />;
}

const { fibers, action, stage } = history[currentStep] || {};
let friendlyAction;

if (fibers) {
let wipFiber = fibers.descriptions[fibers.workInProgressID];
let friendlyFiber = wipFiber.type || wipFiber.tag + ' #' + wipFiber.id;
switch (action) {
case 'willBeginWork':
friendlyAction = 'Before BEGIN phase on ' + friendlyFiber;
break;
case 'didBeginWork':
friendlyAction = 'After BEGIN phase on ' + friendlyFiber;
break;
case 'willCompleteWork':
friendlyAction = 'Before COMPLETE phase on ' + friendlyFiber;
break;
case 'didCompleteWork':
friendlyAction = 'After COMPLETE phase on ' + friendlyFiber;
break;
default:
throw new Error('Unknown action');
}
}

return (
<div style={{ height: '100%' }}>
{fibers &&
<Draggable>
<Fibers fibers={fibers} show={this.state.show} />
</Draggable>
}
<div style={{
width: '100%',
textAlign: 'center',
position: 'fixed',
bottom: 0,
padding: 10,
zIndex: 1,
backgroundColor: '#fafafa',
border: '1px solid #ccc'
}}>
<input
type="range"
min={0}
max={history.length - 1}
value={currentStep}
onChange={e => this.setState({ currentStep: Number(e.target.value) })}
/>
<p>Step {currentStep}: {friendlyAction} (<a style={{ color: 'gray' }} onClick={this.handleEdit} href='#'>Edit</a>)</p>
{stage && <p>Stage: {stage}</p>}
{Object.keys(this.state.show).map(key =>
<label style={{ marginRight: '10px' }} key={key}>
<input
type="checkbox"
checked={this.state.show[key]}
onChange={e => {
this.setState(({ show }) => ({
show: {...show, [key]: !show[key]}
}));
}} />
{key}
</label>
)}
</div>
</div>
);
}
}

export default App;
35 changes: 35 additions & 0 deletions examples/fiber/debugger/src/Editor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React, { Component } from 'react';

class Editor extends Component {
constructor(props) {
super(props);
this.state = {
code: props.code
};
}

render() {
return (
<div style={{
height: '100%',
width: '100%'
}}>
<textarea
value={this.state.code}
onChange={e => this.setState({ code: e.target.value })}
style={{
height: '80%',
width: '100%',
fontSize: '15px'
}} />
<div style={{ height: '20%', textAlign: 'center' }}>
<button onClick={() => this.props.onClose(this.state.code)} style={{ fontSize: 'large' }}>
Run
</button>
</div>
</div>
)
}
}

export default Editor;
Loading

0 comments on commit 4089f5f

Please sign in to comment.