Skip to content

Commit 111731d

Browse files
iamdustangaearon
authored andcommitted
React reconciler package (facebook#10758)
* Initial commit of react-reconciler bundle * I think it’s working 🙀 * React reconciler: slightly better description and README * Drop react-reconciler version to an unstable release number * Convert to moduleType enum and fix packaging * eslint * s/Renderer/Reconciler in docs * yarn prettier * change names of things in the react-reconciler readme * change predicate * rollup: flip object-assign shimming check * copy noop renderer into react-reconciler fixture * Change reconciler fixture test * prettier * Remove a bunch of Noop test renderer * Delete a bunch of stuff we don’t care about for reconciler teesting. Add flow pragmas for future flow pragma testing * Remove PATENTS * Update Reconciler fixture docs * ReactDOMUnstableNativeDependencies should be ISOMORPHIC * Inline fixture renderer * Make it "RENDERER" * There is no UMD build. It also doesn't need propTypes. * Tweak how the reconciler is built * Record sizes * Update README.md
1 parent fb0199b commit 111731d

File tree

11 files changed

+620
-38
lines changed

11 files changed

+620
-38
lines changed

fixtures/reconciler/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# React Reconciler Test Fixture
2+
3+
This folder exists for **React contributors** only.
4+
If you use React you don't need to worry about it.
5+
6+
These fixtures are a smoke-screen verification that the built React Reconciler distribution works.
7+
8+
To test, from the project root:
9+
* `yarn build`
10+
* `cd fixtures/reconciler`
11+
* `yarn test`
12+
13+
They should run as part of the CI check.

fixtures/reconciler/index.js

Lines changed: 366 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,366 @@
1+
/**
2+
* This is a renderer of React that doesn't have a render target output.
3+
* It is used to test that the react-reconciler package doesn't blow up.
4+
*
5+
* @flow
6+
*/
7+
'use strict';
8+
9+
var React = require('react');
10+
var assert = require('assert');
11+
var ReactFiberReconciler = require('react-reconciler');
12+
var emptyObject = require('fbjs/lib/emptyObject');
13+
var assert = require('assert');
14+
15+
const UPDATE_SIGNAL = {};
16+
17+
var scheduledCallback = null;
18+
19+
type Container = {rootID: string, children: Array<Instance | TextInstance>};
20+
type Props = {prop: any, hidden?: boolean};
21+
type Instance = {|
22+
type: string,
23+
id: number,
24+
children: Array<Instance | TextInstance>,
25+
prop: any,
26+
|};
27+
type TextInstance = {|text: string, id: number|};
28+
29+
var instanceCounter = 0;
30+
31+
function appendChild(
32+
parentInstance: Instance | Container,
33+
child: Instance | TextInstance
34+
): void {
35+
const index = parentInstance.children.indexOf(child);
36+
if (index !== -1) {
37+
parentInstance.children.splice(index, 1);
38+
}
39+
parentInstance.children.push(child);
40+
}
41+
42+
function insertBefore(
43+
parentInstance: Instance | Container,
44+
child: Instance | TextInstance,
45+
beforeChild: Instance | TextInstance
46+
): void {
47+
const index = parentInstance.children.indexOf(child);
48+
if (index !== -1) {
49+
parentInstance.children.splice(index, 1);
50+
}
51+
const beforeIndex = parentInstance.children.indexOf(beforeChild);
52+
if (beforeIndex === -1) {
53+
throw new Error('This child does not exist.');
54+
}
55+
parentInstance.children.splice(beforeIndex, 0, child);
56+
}
57+
58+
function removeChild(
59+
parentInstance: Instance | Container,
60+
child: Instance | TextInstance
61+
): void {
62+
const index = parentInstance.children.indexOf(child);
63+
if (index === -1) {
64+
throw new Error('This child does not exist.');
65+
}
66+
parentInstance.children.splice(index, 1);
67+
}
68+
69+
var NoopRenderer = ReactFiberReconciler({
70+
getRootHostContext() {
71+
return emptyObject;
72+
},
73+
74+
getChildHostContext() {
75+
return emptyObject;
76+
},
77+
78+
getPublicInstance(instance) {
79+
return instance;
80+
},
81+
82+
createInstance(type: string, props: Props): Instance {
83+
const inst = {
84+
id: instanceCounter++,
85+
type: type,
86+
children: [],
87+
prop: props.prop,
88+
};
89+
// Hide from unit tests
90+
Object.defineProperty(inst, 'id', {value: inst.id, enumerable: false});
91+
return inst;
92+
},
93+
94+
appendInitialChild(
95+
parentInstance: Instance,
96+
child: Instance | TextInstance
97+
): void {
98+
parentInstance.children.push(child);
99+
},
100+
101+
finalizeInitialChildren(
102+
domElement: Instance,
103+
type: string,
104+
props: Props
105+
): boolean {
106+
return false;
107+
},
108+
109+
prepareUpdate(
110+
instance: Instance,
111+
type: string,
112+
oldProps: Props,
113+
newProps: Props
114+
): null | {} {
115+
return UPDATE_SIGNAL;
116+
},
117+
118+
commitMount(instance: Instance, type: string, newProps: Props): void {
119+
// Noop
120+
},
121+
122+
commitUpdate(
123+
instance: Instance,
124+
updatePayload: Object,
125+
type: string,
126+
oldProps: Props,
127+
newProps: Props
128+
): void {
129+
instance.prop = newProps.prop;
130+
},
131+
132+
shouldSetTextContent(type: string, props: Props): boolean {
133+
return (
134+
typeof props.children === 'string' || typeof props.children === 'number'
135+
);
136+
},
137+
138+
resetTextContent(instance: Instance): void {},
139+
140+
shouldDeprioritizeSubtree(type: string, props: Props): boolean {
141+
return !!props.hidden;
142+
},
143+
144+
now: Date.now,
145+
146+
createTextInstance(
147+
text: string,
148+
rootContainerInstance: Container,
149+
hostContext: Object,
150+
internalInstanceHandle: Object
151+
): TextInstance {
152+
var inst = {text: text, id: instanceCounter++};
153+
// Hide from unit tests
154+
Object.defineProperty(inst, 'id', {value: inst.id, enumerable: false});
155+
return inst;
156+
},
157+
158+
commitTextUpdate(
159+
textInstance: TextInstance,
160+
oldText: string,
161+
newText: string
162+
): void {
163+
textInstance.text = newText;
164+
},
165+
166+
appendChild: appendChild,
167+
appendChildToContainer: appendChild,
168+
insertBefore: insertBefore,
169+
insertInContainerBefore: insertBefore,
170+
removeChild: removeChild,
171+
removeChildFromContainer: removeChild,
172+
173+
scheduleDeferredCallback(callback) {
174+
if (scheduledCallback) {
175+
throw new Error(
176+
'Scheduling a callback twice is excessive. Instead, keep track of ' +
177+
'whether the callback has already been scheduled.'
178+
);
179+
}
180+
scheduledCallback = callback;
181+
},
182+
183+
prepareForCommit(): void {},
184+
185+
resetAfterCommit(): void {},
186+
});
187+
188+
var rootContainers = new Map();
189+
var roots = new Map();
190+
var DEFAULT_ROOT_ID = '<default>';
191+
192+
let yieldedValues = null;
193+
194+
function* flushUnitsOfWork(n: number): Generator<Array<mixed>, void, void> {
195+
var didStop = false;
196+
while (!didStop && scheduledCallback !== null) {
197+
var cb = scheduledCallback;
198+
scheduledCallback = null;
199+
yieldedValues = null;
200+
var unitsRemaining = n;
201+
cb({
202+
timeRemaining() {
203+
if (yieldedValues !== null) {
204+
return 0;
205+
}
206+
if (unitsRemaining-- > 0) {
207+
return 999;
208+
}
209+
didStop = true;
210+
return 0;
211+
},
212+
});
213+
214+
if (yieldedValues !== null) {
215+
const values = yieldedValues;
216+
yieldedValues = null;
217+
yield values;
218+
}
219+
}
220+
}
221+
222+
var Noop = {
223+
getChildren(rootID: string = DEFAULT_ROOT_ID) {
224+
const container = rootContainers.get(rootID);
225+
if (container) {
226+
return container.children;
227+
} else {
228+
return null;
229+
}
230+
},
231+
232+
// Shortcut for testing a single root
233+
render(element: React$Element<any>, callback: ?Function) {
234+
Noop.renderToRootWithID(element, DEFAULT_ROOT_ID, callback);
235+
},
236+
237+
renderToRootWithID(
238+
element: React$Element<any>,
239+
rootID: string,
240+
callback: ?Function
241+
) {
242+
let root = roots.get(rootID);
243+
if (!root) {
244+
const container = {rootID: rootID, children: []};
245+
rootContainers.set(rootID, container);
246+
root = NoopRenderer.createContainer(container);
247+
roots.set(rootID, root);
248+
}
249+
NoopRenderer.updateContainer(element, root, null, callback);
250+
},
251+
252+
flush(): Array<mixed> {
253+
return Noop.flushUnitsOfWork(Infinity);
254+
},
255+
256+
flushUnitsOfWork(n: number): Array<mixed> {
257+
let values = [];
258+
for (const value of flushUnitsOfWork(n)) {
259+
values.push(...value);
260+
}
261+
return values;
262+
},
263+
264+
batchedUpdates: NoopRenderer.batchedUpdates,
265+
266+
deferredUpdates: NoopRenderer.deferredUpdates,
267+
268+
unbatchedUpdates: NoopRenderer.unbatchedUpdates,
269+
270+
flushSync: NoopRenderer.flushSync,
271+
};
272+
273+
type TestProps = {|
274+
active: boolean,
275+
|};
276+
type TestState = {|
277+
counter: number,
278+
|};
279+
280+
let instance = null;
281+
class Test extends React.Component<TestProps, TestState> {
282+
state = {counter: 0};
283+
increment() {
284+
this.setState(({counter}) => ({
285+
counter: counter + 1,
286+
}));
287+
}
288+
render() {
289+
return [this.props.active ? 'Active' : 'Inactive', this.state.counter];
290+
}
291+
}
292+
const Children = props => props.children;
293+
Noop.render(
294+
<main>
295+
<div>Hello</div>
296+
<Children>
297+
Hello world
298+
<span>{'Number '}{42}</span>
299+
<Test active={true} ref={t => (instance = t)} />
300+
</Children>
301+
</main>
302+
);
303+
Noop.flush();
304+
const actual1 = Noop.getChildren();
305+
const expected1 = [
306+
{
307+
type: 'main',
308+
children: [
309+
{type: 'div', children: [], prop: undefined},
310+
{text: 'Hello world'},
311+
{
312+
type: 'span',
313+
children: [{text: 'Number '}, {text: '42'}],
314+
prop: undefined,
315+
},
316+
{text: 'Active'},
317+
{text: '0'},
318+
],
319+
prop: undefined,
320+
},
321+
];
322+
assert.deepEqual(
323+
actual1,
324+
expected1,
325+
'Error. Noop.getChildren() returned unexpected value.\nExpected:\ ' +
326+
JSON.stringify(expected1, null, 2) +
327+
'\n\nActual:\n ' +
328+
JSON.stringify(actual1, null, 2)
329+
);
330+
331+
if (instance === null) {
332+
throw new Error('Expected instance to exist.');
333+
}
334+
335+
instance.increment();
336+
Noop.flush();
337+
const actual2 = Noop.getChildren();
338+
const expected2 = [
339+
{
340+
type: 'main',
341+
children: [
342+
{type: 'div', children: [], prop: undefined},
343+
{text: 'Hello world'},
344+
{
345+
type: 'span',
346+
children: [{text: 'Number '}, {text: '42'}],
347+
prop: undefined,
348+
},
349+
{text: 'Active'},
350+
{text: '1'},
351+
],
352+
prop: undefined,
353+
},
354+
];
355+
assert.deepEqual(
356+
actual2,
357+
expected2,
358+
'Error. Noop.getChildren() returned unexpected value.\nExpected:\ ' +
359+
JSON.stringify(expected2, null, 2) +
360+
'\n\nActual:\n ' +
361+
JSON.stringify(actual2, null, 2)
362+
);
363+
364+
const beginGreen = '\u001b[32m';
365+
const endGreen = '\u001b[39m';
366+
console.log(beginGreen + 'Reconciler package is OK!' + endGreen);

fixtures/reconciler/package.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"name": "react-fixtures",
3+
"version": "0.1.0",
4+
"private": true,
5+
"scripts": {
6+
"test:dev": "NODE_PATH=../../build/packages ../../node_modules/.bin/babel-node ./index",
7+
"test:prod": "NODE_PATH=../../build/packages NODE_ENV=production ../../node_modules/.bin/babel-node ./index",
8+
"test": "npm run test:dev && npm run test:prod"
9+
}
10+
}

0 commit comments

Comments
 (0)