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:
Dan Abramov
2016-10-25 08:36:37 +01:00
committed by GitHub
parent 265ab83667
commit 225325eada
22 changed files with 6340 additions and 10 deletions
+6
View File
@@ -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',
]);
+15
View File
@@ -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
View File
@@ -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
View File
@@ -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

+13
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+329
View File
@@ -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
};
}
+15
View File
@@ -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;
}
+9
View File
@@ -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
+52
View File
@@ -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,
};
+1 -1
View File
@@ -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
View File
@@ -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))
);
});
+4
View File
@@ -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
View File
@@ -0,0 +1,3 @@
'use strict';
module.exports = require('./lib/ReactNoop');
+20
View File
@@ -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) {