mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Add Fiber Debugger (#8033)
* Build react-noop as a package This lets us consume it from the debugger. * Add instrumentation to Fiber * Check in Fiber Debugger
This commit is contained in:
@@ -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());
|
||||
@@ -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',
|
||||
]);
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -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 not shown.
|
After Width: | Height: | Size: 24 KiB |
@@ -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>
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -0,0 +1,329 @@
|
||||
import React from 'react';
|
||||
import { Motion, spring } from 'react-motion';
|
||||
import dagre from 'dagre';
|
||||
import prettyFormat from 'pretty-format';
|
||||
import reactElement from 'pretty-format/plugins/ReactElement';
|
||||
|
||||
function getFiberColor(fibers, id) {
|
||||
if (fibers.currentIDs.indexOf(id) > -1) {
|
||||
return 'lightgreen';
|
||||
}
|
||||
if (id === fibers.workInProgressID) {
|
||||
return 'yellow';
|
||||
}
|
||||
return 'lightyellow';
|
||||
}
|
||||
|
||||
function Graph(props) {
|
||||
var g = new dagre.graphlib.Graph();
|
||||
g.setGraph({
|
||||
width: 1000,
|
||||
height: 1000,
|
||||
nodesep: 50,
|
||||
edgesep: 150,
|
||||
ranksep: 150,
|
||||
marginx: 100,
|
||||
marginy: 100,
|
||||
});
|
||||
|
||||
var edgeLabels = {};
|
||||
React.Children.forEach(props.children, function(child) {
|
||||
if (!child) {
|
||||
return;
|
||||
}
|
||||
if (child.type.isVertex) {
|
||||
g.setNode(child.key, {
|
||||
label: child,
|
||||
width: child.props.width,
|
||||
height: child.props.height
|
||||
});
|
||||
} else if (child.type.isEdge) {
|
||||
const relationshipKey = child.props.source + ':' + child.props.target;
|
||||
if (!edgeLabels[relationshipKey]) {
|
||||
edgeLabels[relationshipKey] = [];
|
||||
}
|
||||
edgeLabels[relationshipKey].push(child);
|
||||
}
|
||||
});
|
||||
|
||||
Object.keys(edgeLabels).forEach(key => {
|
||||
const children = edgeLabels[key];
|
||||
const child = children[0];
|
||||
g.setEdge(child.props.source, child.props.target, {
|
||||
label: child,
|
||||
allChildren: children.map(c => c.props.children),
|
||||
weight: child.props.weight
|
||||
});
|
||||
});
|
||||
|
||||
dagre.layout(g);
|
||||
|
||||
var nodes = g.nodes().map(v => {
|
||||
var node = g.node(v);
|
||||
return (
|
||||
<Motion style={{
|
||||
x: props.isDragging ? node.x : spring(node.x),
|
||||
y: props.isDragging ? node.y : spring(node.y)
|
||||
}} key={node.label.key}>
|
||||
{interpolatingStyle =>
|
||||
React.cloneElement(node.label, {
|
||||
x: interpolatingStyle.x + props.dx,
|
||||
y: interpolatingStyle.y + props.dy
|
||||
})
|
||||
}
|
||||
</Motion>
|
||||
);
|
||||
});
|
||||
|
||||
var edges = g.edges().map(e => {
|
||||
var edge = g.edge(e);
|
||||
let idx = 0;
|
||||
return (
|
||||
<Motion style={edge.points.reduce((bag, point) => {
|
||||
bag[idx + ':x'] = props.isDragging ? point.x : spring(point.x);
|
||||
bag[idx + ':y'] = props.isDragging ? point.y : spring(point.y);
|
||||
idx++;
|
||||
return bag;
|
||||
}, {})} key={edge.label.key}>
|
||||
{interpolatedStyle => {
|
||||
let points = [];
|
||||
Object.keys(interpolatedStyle).forEach(key => {
|
||||
const [idx, prop] = key.split(':');
|
||||
if (!points[idx]) {
|
||||
points[idx] = { x: props.dx, y: props.dy };
|
||||
}
|
||||
points[idx][prop] += interpolatedStyle[key];
|
||||
});
|
||||
return React.cloneElement(edge.label, {
|
||||
points,
|
||||
id: edge.label.key,
|
||||
children: edge.allChildren.join(', ')
|
||||
});
|
||||
}}
|
||||
</Motion>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
position: 'relative',
|
||||
height: '100%'
|
||||
}}>
|
||||
{edges}
|
||||
{nodes}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Vertex(props) {
|
||||
return (
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
border: '1px solid black',
|
||||
left: (props.x-(props.width/2)),
|
||||
top: (props.y-(props.height/2)),
|
||||
width: props.width,
|
||||
height: props.height,
|
||||
overflow: 'hidden',
|
||||
padding: '4px',
|
||||
wordWrap: 'break-word'
|
||||
}} {...props} />
|
||||
);
|
||||
}
|
||||
Vertex.isVertex = true;
|
||||
|
||||
const strokes = {
|
||||
alt: 'blue',
|
||||
child: 'green',
|
||||
sibling: 'darkgreen',
|
||||
return: 'red',
|
||||
fx: 'purple',
|
||||
progressedChild: 'cyan',
|
||||
progressedDel: 'brown'
|
||||
};
|
||||
|
||||
function Edge(props) {
|
||||
var points = props.points;
|
||||
var path = "M" + points[0].x + " " + points[0].y + " ";
|
||||
|
||||
if (!points[0].x || !points[0].y) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (var i = 1; i < points.length; i++) {
|
||||
path += "L" + points[i].x + " " + points[i].y + " ";
|
||||
if (!points[i].x || !points[i].y) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
var lineID = props.id;
|
||||
|
||||
return (
|
||||
<svg width="100%" height="100%" style={{
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
}}>
|
||||
<defs>
|
||||
<path d={path} id={lineID} />
|
||||
<marker id="markerCircle" markerWidth="8" markerHeight="8" refX="5" refY="5">
|
||||
<circle cx="5" cy="5" r="3" style={{stroke: 'none', fill:'black'}}/>
|
||||
</marker>
|
||||
<marker id="markerArrow" markerWidth="13" markerHeight="13" refX="2" refY="6"
|
||||
orient="auto">
|
||||
<path d="M2,2 L2,11 L10,6 L2,2" style={{fill: 'black'}} />
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<use xlinkHref={`#${lineID}`} fill="none" stroke={strokes[props.kind]} style={{
|
||||
markerStart: 'url(#markerCircle)',
|
||||
markerEnd: 'url(#markerArrow)'
|
||||
}} />
|
||||
<text>
|
||||
<textPath xlinkHref={`#${lineID}`}>
|
||||
{' '}{props.children}
|
||||
</textPath>
|
||||
</text>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
Edge.isEdge = true;
|
||||
|
||||
export default function Fibers({ fibers, show, ...rest }) {
|
||||
const items = Object.keys(fibers.descriptions).map(id =>
|
||||
fibers.descriptions[id]
|
||||
);
|
||||
|
||||
const isDragging = rest.className.indexOf('dragging') > -1;
|
||||
const [_, sdx, sdy] = rest.style.transform.match(/translate\((\-?\d+)px,(\-?\d+)px\)/) || [];
|
||||
const dx = Number(sdx);
|
||||
const dy = Number(sdy);
|
||||
|
||||
return (
|
||||
<div {...rest} style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
...rest.style,
|
||||
transform: null
|
||||
}}>
|
||||
<Graph className="graph" dx={dx} dy={dy} isDragging={isDragging}>
|
||||
{items.map(fiber => [
|
||||
<Vertex
|
||||
key={fiber.id}
|
||||
width={200}
|
||||
height={100}>
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
backgroundColor: getFiberColor(fibers, fiber.id)
|
||||
}}
|
||||
title={prettyFormat(fiber, { plugins: [reactElement ]})}>
|
||||
<small>{fiber.tag} #{fiber.id}</small>
|
||||
<br />
|
||||
{fiber.type}
|
||||
<br />
|
||||
<small>Priority: {fiber.pendingWorkPriority} pend, {fiber.progressedPriority} prog</small>
|
||||
</div>
|
||||
</Vertex>,
|
||||
fiber.child && show.child &&
|
||||
<Edge
|
||||
source={fiber.id}
|
||||
target={fiber.child}
|
||||
kind="child"
|
||||
weight={1000}
|
||||
key={`${fiber.id}-${fiber.child}-child`}>
|
||||
child
|
||||
</Edge>,
|
||||
fiber.progressedChild && show.progressedChild &&
|
||||
<Edge
|
||||
source={fiber.id}
|
||||
target={fiber.progressedChild}
|
||||
kind="progressedChild"
|
||||
weight={1000}
|
||||
key={`${fiber.id}-${fiber.progressedChild}-pChild`}>
|
||||
pChild
|
||||
</Edge>,
|
||||
fiber.sibling && show.sibling &&
|
||||
<Edge
|
||||
source={fiber.id}
|
||||
target={fiber.sibling}
|
||||
kind="sibling"
|
||||
weight={2000}
|
||||
key={`${fiber.id}-${fiber.sibling}-sibling`}>
|
||||
sibling
|
||||
</Edge>,
|
||||
fiber.return && show.return &&
|
||||
<Edge
|
||||
source={fiber.id}
|
||||
target={fiber.return}
|
||||
kind="return"
|
||||
weight={1000}
|
||||
key={`${fiber.id}-${fiber.return}-return`}>
|
||||
return
|
||||
</Edge>,
|
||||
fiber.nextEffect && show.fx &&
|
||||
<Edge
|
||||
source={fiber.id}
|
||||
target={fiber.nextEffect}
|
||||
kind="fx"
|
||||
weight={100}
|
||||
key={`${fiber.id}-${fiber.nextEffect}-nextEffect`}>
|
||||
nextFx
|
||||
</Edge>,
|
||||
fiber.firstEffect && show.fx &&
|
||||
<Edge
|
||||
source={fiber.id}
|
||||
target={fiber.firstEffect}
|
||||
kind="fx"
|
||||
weight={100}
|
||||
key={`${fiber.id}-${fiber.firstEffect}-firstEffect`}>
|
||||
firstFx
|
||||
</Edge>,
|
||||
fiber.lastEffect && show.fx &&
|
||||
<Edge
|
||||
source={fiber.id}
|
||||
target={fiber.lastEffect}
|
||||
kind="fx"
|
||||
weight={100}
|
||||
key={`${fiber.id}-${fiber.lastEffect}-lastEffect`}>
|
||||
lastFx
|
||||
</Edge>,
|
||||
fiber.progressedFirstDeletion && show.progressedDel &&
|
||||
<Edge
|
||||
source={fiber.id}
|
||||
target={fiber.progressedFirstDeletion}
|
||||
kind="progressedDel"
|
||||
weight={100}
|
||||
key={`${fiber.id}-${fiber.progressedFirstDeletion}-pFD`}>
|
||||
pFDel
|
||||
</Edge>,
|
||||
fiber.progressedLastDeletion && show.progressedDel &&
|
||||
<Edge
|
||||
source={fiber.id}
|
||||
target={fiber.progressedLastDeletion}
|
||||
kind="progressedDel"
|
||||
weight={100}
|
||||
key={`${fiber.id}-${fiber.progressedLastDeletion}-pLD`}>
|
||||
pLDel
|
||||
</Edge>,
|
||||
fiber.alternate && show.alt &&
|
||||
<Edge
|
||||
source={fiber.id}
|
||||
target={fiber.alternate}
|
||||
kind="alt"
|
||||
weight={10}
|
||||
key={`${fiber.id}-${fiber.alternate}-alt`}>
|
||||
alt
|
||||
</Edge>,
|
||||
])}
|
||||
</Graph>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
let nextFiberID = 1;
|
||||
const fiberIDMap = new WeakMap();
|
||||
|
||||
function getFiberUniqueID(fiber) {
|
||||
if (!fiberIDMap.has(fiber)) {
|
||||
fiberIDMap.set(fiber, nextFiberID++);
|
||||
}
|
||||
return fiberIDMap.get(fiber);
|
||||
}
|
||||
|
||||
function getFriendlyTag(tag) {
|
||||
switch (tag) {
|
||||
case 0:
|
||||
return '[indeterminate]';
|
||||
case 1:
|
||||
return '[fn]';
|
||||
case 2:
|
||||
return '[class]';
|
||||
case 3:
|
||||
return '[root]';
|
||||
case 4:
|
||||
return '[host]';
|
||||
case 5:
|
||||
return '[text]';
|
||||
case 6:
|
||||
return '[coroutine]';
|
||||
case 7:
|
||||
return '[handler]';
|
||||
case 8:
|
||||
return '[yield]';
|
||||
case 9:
|
||||
return '[frag]';
|
||||
default:
|
||||
throw new Error('Unknown tag.');
|
||||
}
|
||||
}
|
||||
|
||||
export default function describeFibers(rootFiber, workInProgress) {
|
||||
let descriptions = {};
|
||||
function acknowledgeFiber(fiber) {
|
||||
if (!fiber) {
|
||||
return null;
|
||||
}
|
||||
const id = getFiberUniqueID(fiber);
|
||||
if (descriptions[id]) {
|
||||
return id;
|
||||
}
|
||||
descriptions[id] = {};
|
||||
Object.assign(descriptions[id], {
|
||||
...fiber,
|
||||
id: id,
|
||||
tag: getFriendlyTag(fiber.tag),
|
||||
type: (fiber.type && ('<' + (fiber.type.name || fiber.type) + '>')),
|
||||
stateNode: `[${typeof fiber.stateNode}]`,
|
||||
output: `[${typeof fiber.output}]`,
|
||||
return: acknowledgeFiber(fiber.return),
|
||||
child: acknowledgeFiber(fiber.child),
|
||||
sibling: acknowledgeFiber(fiber.sibling),
|
||||
nextEffect: acknowledgeFiber(fiber.nextEffect),
|
||||
firstEffect: acknowledgeFiber(fiber.firstEffect),
|
||||
lastEffect: acknowledgeFiber(fiber.lastEffect),
|
||||
progressedChild: acknowledgeFiber(fiber.progressedChild),
|
||||
progressedFirstDeletion: acknowledgeFiber(fiber.progressedFirstDeletion),
|
||||
progressedLastDeletion: acknowledgeFiber(fiber.progressedLastDeletion),
|
||||
alternate: acknowledgeFiber(fiber.alternate),
|
||||
});
|
||||
return id;
|
||||
}
|
||||
|
||||
const rootID = acknowledgeFiber(rootFiber);
|
||||
const workInProgressID = acknowledgeFiber(workInProgress);
|
||||
|
||||
let currentIDs = new Set();
|
||||
function markAsCurent(id) {
|
||||
currentIDs.add(id);
|
||||
const fiber = descriptions[id];
|
||||
if (fiber.sibling) {
|
||||
markAsCurent(fiber.sibling);
|
||||
}
|
||||
if (fiber.child) {
|
||||
markAsCurent(fiber.child);
|
||||
}
|
||||
}
|
||||
markAsCurent(rootID);
|
||||
|
||||
return {
|
||||
descriptions,
|
||||
rootID,
|
||||
currentIDs: Array.from(currentIDs),
|
||||
workInProgressID
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: sans-serif;
|
||||
height: 100vh;
|
||||
cursor: -webkit-grab; cursor: -moz-grab;
|
||||
}
|
||||
|
||||
#root {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
import './index.css';
|
||||
|
||||
ReactDOM.render(
|
||||
<App />,
|
||||
document.getElementById('root')
|
||||
);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,52 @@
|
||||
'use strict';
|
||||
|
||||
var fs = require('fs');
|
||||
var grunt = require('grunt');
|
||||
|
||||
var src = 'packages/react-noop-renderer/';
|
||||
var dest = 'build/packages/react-noop-renderer/';
|
||||
var modSrc = 'build/node_modules/react-noop-renderer/lib';
|
||||
var lib = dest + 'lib/';
|
||||
|
||||
function buildRelease() {
|
||||
if (grunt.file.exists(dest)) {
|
||||
grunt.file.delete(dest);
|
||||
}
|
||||
|
||||
// Copy to build/packages/react-noop-renderer
|
||||
var mappings = [].concat(
|
||||
grunt.file.expandMapping('**/*', dest, {cwd: src}),
|
||||
grunt.file.expandMapping('**/*', lib, {cwd: modSrc}),
|
||||
grunt.file.expandMapping('{LICENSE,PATENTS}', dest)
|
||||
);
|
||||
mappings.forEach(function(mapping) {
|
||||
var mappingSrc = mapping.src[0];
|
||||
var mappingDest = mapping.dest;
|
||||
if (grunt.file.isDir(mappingSrc)) {
|
||||
grunt.file.mkdir(mappingDest);
|
||||
} else {
|
||||
grunt.file.copy(mappingSrc, mappingDest);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function packRelease() {
|
||||
var done = this.async();
|
||||
var spawnCmd = {
|
||||
cmd: 'npm',
|
||||
args: ['pack', 'packages/react-noop-renderer'],
|
||||
opts: {
|
||||
cwd: 'build/',
|
||||
},
|
||||
};
|
||||
grunt.util.spawn(spawnCmd, function() {
|
||||
var buildSrc = 'build/react-noop-renderer-' + grunt.config.data.pkg.version + '.tgz';
|
||||
var buildDest = 'build/packages/react-noop-renderer.tgz';
|
||||
fs.rename(buildSrc, buildDest, done);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
buildRelease: buildRelease,
|
||||
packRelease: packRelease,
|
||||
};
|
||||
@@ -13,7 +13,7 @@ function buildRelease() {
|
||||
grunt.file.delete(dest);
|
||||
}
|
||||
|
||||
// Copy to build/packages/react-native-renderer
|
||||
// Copy to build/packages/react-test-renderer
|
||||
var mappings = [].concat(
|
||||
grunt.file.expandMapping('**/*', dest, {cwd: src}),
|
||||
grunt.file.expandMapping('**/*', lib, {cwd: modSrc}),
|
||||
|
||||
+39
-2
@@ -92,6 +92,20 @@ var paths = {
|
||||
],
|
||||
lib: 'build/node_modules/react-test-renderer/lib',
|
||||
},
|
||||
reactNoopRenderer: {
|
||||
src: [
|
||||
'src/renderers/noop/**/*.js',
|
||||
'src/renderers/shared/**/*.js',
|
||||
|
||||
'src/ReactVersion.js',
|
||||
'src/shared/**/*.js',
|
||||
'!src/shared/vendor/**/*.js',
|
||||
'!src/**/__benchmarks__/**/*.js',
|
||||
'!src/**/__tests__/**/*.js',
|
||||
'!src/**/__mocks__/**/*.js',
|
||||
],
|
||||
lib: 'build/node_modules/react-noop-renderer/lib',
|
||||
},
|
||||
};
|
||||
|
||||
var moduleMapBase = Object.assign(
|
||||
@@ -148,6 +162,13 @@ var moduleMapReactTestRenderer = Object.assign(
|
||||
moduleMapBase
|
||||
);
|
||||
|
||||
var moduleMapReactNoopRenderer = Object.assign(
|
||||
{},
|
||||
rendererSharedState,
|
||||
moduleMapBase
|
||||
);
|
||||
|
||||
|
||||
var errorCodeOpts = {
|
||||
errorMapFilePath: 'scripts/error-codes/codes.json',
|
||||
};
|
||||
@@ -180,6 +201,13 @@ var babelOptsReactTestRenderer = {
|
||||
],
|
||||
};
|
||||
|
||||
var babelOptsReactNoopRenderer = {
|
||||
plugins: [
|
||||
devExpressionWithCodes, // this pass has to run before `rewrite-modules`
|
||||
[babelPluginModules, {map: moduleMapReactNoopRenderer}],
|
||||
],
|
||||
};
|
||||
|
||||
gulp.task('eslint', getTask('eslint'));
|
||||
|
||||
gulp.task('lint', ['eslint']);
|
||||
@@ -194,6 +222,7 @@ gulp.task('react:clean', function() {
|
||||
paths.reactDOM.lib,
|
||||
paths.reactNative.lib,
|
||||
paths.reactTestRenderer.lib,
|
||||
paths.reactNoopRenderer.lib,
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -225,7 +254,14 @@ gulp.task('react:modules', function() {
|
||||
.pipe(stripProvidesModule())
|
||||
.pipe(babel(babelOptsReactTestRenderer))
|
||||
.pipe(flatten())
|
||||
.pipe(gulp.dest(paths.reactTestRenderer.lib))
|
||||
.pipe(gulp.dest(paths.reactTestRenderer.lib)),
|
||||
|
||||
gulp
|
||||
.src(paths.reactNoopRenderer.src)
|
||||
.pipe(stripProvidesModule())
|
||||
.pipe(babel(babelOptsReactNoopRenderer))
|
||||
.pipe(flatten())
|
||||
.pipe(gulp.dest(paths.reactNoopRenderer.lib))
|
||||
);
|
||||
});
|
||||
|
||||
@@ -234,7 +270,8 @@ gulp.task('react:extract-errors', function() {
|
||||
gulp.src(paths.react.src).pipe(extractErrors(errorCodeOpts)),
|
||||
gulp.src(paths.reactDOM.src).pipe(extractErrors(errorCodeOpts)),
|
||||
gulp.src(paths.reactNative.src).pipe(extractErrors(errorCodeOpts)),
|
||||
gulp.src(paths.reactTestRenderer.src).pipe(extractErrors(errorCodeOpts))
|
||||
gulp.src(paths.reactTestRenderer.src).pipe(extractErrors(errorCodeOpts)),
|
||||
gulp.src(paths.reactNoopRenderer.src).pipe(extractErrors(errorCodeOpts))
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# `react-noop-renderer`
|
||||
|
||||
This package is the renderer we use for debugging [Fiber](https://github.com/facebook/react/issues/6170).
|
||||
It is not intended to be used directly.
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = require('./lib/ReactNoop');
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "react-noop-renderer",
|
||||
"version": "16.0.0-alpha",
|
||||
"private": true,
|
||||
"description": "React package for testing the Fiber reconciler.",
|
||||
"main": "index.js",
|
||||
"repository": "facebook/react",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"fbjs": "^0.8.4",
|
||||
"object-assign": "^4.1.0"
|
||||
},
|
||||
"files": [
|
||||
"LICENSE",
|
||||
"PATENTS",
|
||||
"README.md",
|
||||
"index.js",
|
||||
"lib/"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactFiberInstrumentation
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// This lets us hook into Fiber to debug what it's doing.
|
||||
// See https://github.com/facebook/react/pull/8033.
|
||||
// This is not part of the public API, not even for React DevTools.
|
||||
// You may only inject a debugTool if you work on React Fiber itself.
|
||||
var ReactFiberInstrumentation = {
|
||||
debugTool: null,
|
||||
};
|
||||
|
||||
module.exports = ReactFiberInstrumentation;
|
||||
@@ -20,6 +20,10 @@ import type { PriorityLevel } from 'ReactPriorityLevel';
|
||||
var { createFiberRoot } = require('ReactFiberRoot');
|
||||
var ReactFiberScheduler = require('ReactFiberScheduler');
|
||||
|
||||
if (__DEV__) {
|
||||
var ReactFiberInstrumentation = require('ReactFiberInstrumentation');
|
||||
}
|
||||
|
||||
type Deadline = {
|
||||
timeRemaining : () => number
|
||||
};
|
||||
@@ -80,6 +84,10 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) :
|
||||
|
||||
scheduleWork(root);
|
||||
|
||||
if (__DEV__ && ReactFiberInstrumentation.debugTool) {
|
||||
ReactFiberInstrumentation.debugTool.onMountContainer(root);
|
||||
}
|
||||
|
||||
// It may seem strange that we don't return the root here, but that will
|
||||
// allow us to have containers that are in the middle of the tree instead
|
||||
// of being roots.
|
||||
@@ -93,6 +101,10 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) :
|
||||
root.current.pendingProps = element;
|
||||
|
||||
scheduleWork(root);
|
||||
|
||||
if (__DEV__ && ReactFiberInstrumentation.debugTool) {
|
||||
ReactFiberInstrumentation.debugTool.onUpdateContainer(root);
|
||||
}
|
||||
},
|
||||
|
||||
unmountContainer(container : OpaqueNode) : void {
|
||||
@@ -102,6 +114,10 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) :
|
||||
root.current.pendingProps = [];
|
||||
|
||||
scheduleWork(root);
|
||||
|
||||
if (__DEV__ && ReactFiberInstrumentation.debugTool) {
|
||||
ReactFiberInstrumentation.debugTool.onUnmountContainer(root);
|
||||
}
|
||||
},
|
||||
|
||||
performWithPriority,
|
||||
|
||||
@@ -42,6 +42,10 @@ var {
|
||||
HostContainer,
|
||||
} = require('ReactTypeOfWork');
|
||||
|
||||
if (__DEV__) {
|
||||
var ReactFiberInstrumentation = require('ReactFiberInstrumentation');
|
||||
}
|
||||
|
||||
var timeHeuristicForUnitOfWork = 1;
|
||||
|
||||
module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
|
||||
@@ -273,15 +277,28 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
|
||||
// means that we don't need an additional field on the work in
|
||||
// progress.
|
||||
const current = workInProgress.alternate;
|
||||
const next = beginWork(current, workInProgress, nextPriorityLevel);
|
||||
|
||||
if (next) {
|
||||
// If this spawns new work, do that next.
|
||||
return next;
|
||||
} else {
|
||||
// Otherwise, complete the current work.
|
||||
return completeUnitOfWork(workInProgress);
|
||||
if (__DEV__ && ReactFiberInstrumentation.debugTool) {
|
||||
ReactFiberInstrumentation.debugTool.onWillBeginWork(workInProgress);
|
||||
}
|
||||
// See if beginning this work spawns more work.
|
||||
let next = beginWork(current, workInProgress, nextPriorityLevel);
|
||||
if (__DEV__ && ReactFiberInstrumentation.debugTool) {
|
||||
ReactFiberInstrumentation.debugTool.onDidBeginWork(workInProgress);
|
||||
}
|
||||
|
||||
if (!next) {
|
||||
if (__DEV__ && ReactFiberInstrumentation.debugTool) {
|
||||
ReactFiberInstrumentation.debugTool.onWillCompleteWork(workInProgress);
|
||||
}
|
||||
// If this doesn't spawn new work, complete the current work.
|
||||
next = completeUnitOfWork(workInProgress);
|
||||
if (__DEV__ && ReactFiberInstrumentation.debugTool) {
|
||||
ReactFiberInstrumentation.debugTool.onDidCompleteWork(workInProgress);
|
||||
}
|
||||
}
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
function performDeferredWork(deadline) {
|
||||
|
||||
Reference in New Issue
Block a user