mirror of
https://github.com/zebrajr/react.git
synced 2026-01-15 12:15:22 +00:00
Split ReactNoop into normal and persistent exports (#12793)
* Copy-paste ReactNoop into ReactNoopPersistent * Split ReactNoop into normal and persistent exports * ReactNoopShared -> createReactNoop
This commit is contained in:
7
packages/react-noop-renderer/npm/persistent.js
vendored
Normal file
7
packages/react-noop-renderer/npm/persistent.js
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports = require('./cjs/react-noop-renderer-persistent.production.min.js');
|
||||
} else {
|
||||
module.exports = require('./cjs/react-noop-renderer-persistent.development.js');
|
||||
}
|
||||
@@ -20,6 +20,7 @@
|
||||
"LICENSE",
|
||||
"README.md",
|
||||
"index.js",
|
||||
"persistent.js",
|
||||
"cjs/"
|
||||
]
|
||||
}
|
||||
|
||||
18
packages/react-noop-renderer/persistent.js
vendored
Normal file
18
packages/react-noop-renderer/persistent.js
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Copyright (c) 2013-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const ReactNoopPersistent = require('./src/ReactNoopPersistent');
|
||||
|
||||
// TODO: decide on the top-level export form.
|
||||
// This is hacky but makes it work with both Rollup and Jest.
|
||||
module.exports = ReactNoopPersistent.default
|
||||
? ReactNoopPersistent.default
|
||||
: ReactNoopPersistent;
|
||||
579
packages/react-noop-renderer/src/ReactNoop.js
vendored
579
packages/react-noop-renderer/src/ReactNoop.js
vendored
@@ -14,581 +14,10 @@
|
||||
* environment.
|
||||
*/
|
||||
|
||||
import type {Fiber} from 'react-reconciler/src/ReactFiber';
|
||||
import type {UpdateQueue} from 'react-reconciler/src/ReactUpdateQueue';
|
||||
import type {ReactNodeList} from 'shared/ReactTypes';
|
||||
import ReactFiberReconciler from 'react-reconciler';
|
||||
import {enablePersistentReconciler} from 'shared/ReactFeatureFlags';
|
||||
import * as ReactPortal from 'shared/ReactPortal';
|
||||
import emptyObject from 'fbjs/lib/emptyObject';
|
||||
import expect from 'expect';
|
||||
import createReactNoop from './createReactNoop';
|
||||
|
||||
const UPDATE_SIGNAL = {};
|
||||
|
||||
let scheduledCallback = null;
|
||||
|
||||
type Container = {rootID: string, children: Array<Instance | TextInstance>};
|
||||
type Props = {prop: any, hidden?: boolean};
|
||||
type Instance = {|
|
||||
type: string,
|
||||
id: number,
|
||||
children: Array<Instance | TextInstance>,
|
||||
prop: any,
|
||||
|};
|
||||
type TextInstance = {|text: string, id: number|};
|
||||
|
||||
let instanceCounter = 0;
|
||||
let failInBeginPhase = false;
|
||||
|
||||
function appendChild(
|
||||
parentInstance: Instance | Container,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
const index = parentInstance.children.indexOf(child);
|
||||
if (index !== -1) {
|
||||
parentInstance.children.splice(index, 1);
|
||||
}
|
||||
parentInstance.children.push(child);
|
||||
}
|
||||
|
||||
function insertBefore(
|
||||
parentInstance: Instance | Container,
|
||||
child: Instance | TextInstance,
|
||||
beforeChild: Instance | TextInstance,
|
||||
): void {
|
||||
const index = parentInstance.children.indexOf(child);
|
||||
if (index !== -1) {
|
||||
parentInstance.children.splice(index, 1);
|
||||
}
|
||||
const beforeIndex = parentInstance.children.indexOf(beforeChild);
|
||||
if (beforeIndex === -1) {
|
||||
throw new Error('This child does not exist.');
|
||||
}
|
||||
parentInstance.children.splice(beforeIndex, 0, child);
|
||||
}
|
||||
|
||||
function removeChild(
|
||||
parentInstance: Instance | Container,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
const index = parentInstance.children.indexOf(child);
|
||||
if (index === -1) {
|
||||
throw new Error('This child does not exist.');
|
||||
}
|
||||
parentInstance.children.splice(index, 1);
|
||||
}
|
||||
|
||||
let elapsedTimeInMs = 0;
|
||||
|
||||
let SharedHostConfig = {
|
||||
getRootHostContext() {
|
||||
if (failInBeginPhase) {
|
||||
throw new Error('Error in host config.');
|
||||
}
|
||||
return emptyObject;
|
||||
},
|
||||
|
||||
getChildHostContext() {
|
||||
return emptyObject;
|
||||
},
|
||||
|
||||
getPublicInstance(instance) {
|
||||
return instance;
|
||||
},
|
||||
|
||||
createInstance(type: string, props: Props): Instance {
|
||||
const inst = {
|
||||
id: instanceCounter++,
|
||||
type: type,
|
||||
children: [],
|
||||
prop: props.prop,
|
||||
};
|
||||
// Hide from unit tests
|
||||
Object.defineProperty(inst, 'id', {value: inst.id, enumerable: false});
|
||||
return inst;
|
||||
},
|
||||
|
||||
appendInitialChild(
|
||||
parentInstance: Instance,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
parentInstance.children.push(child);
|
||||
},
|
||||
|
||||
finalizeInitialChildren(
|
||||
domElement: Instance,
|
||||
type: string,
|
||||
props: Props,
|
||||
): boolean {
|
||||
return false;
|
||||
},
|
||||
|
||||
prepareUpdate(
|
||||
instance: Instance,
|
||||
type: string,
|
||||
oldProps: Props,
|
||||
newProps: Props,
|
||||
): null | {} {
|
||||
if (oldProps === null) {
|
||||
throw new Error('Should have old props');
|
||||
}
|
||||
if (newProps === null) {
|
||||
throw new Error('Should have new props');
|
||||
}
|
||||
return UPDATE_SIGNAL;
|
||||
},
|
||||
|
||||
shouldSetTextContent(type: string, props: Props): boolean {
|
||||
return (
|
||||
typeof props.children === 'string' || typeof props.children === 'number'
|
||||
);
|
||||
},
|
||||
|
||||
shouldDeprioritizeSubtree(type: string, props: Props): boolean {
|
||||
return !!props.hidden;
|
||||
},
|
||||
|
||||
createTextInstance(
|
||||
text: string,
|
||||
rootContainerInstance: Container,
|
||||
hostContext: Object,
|
||||
internalInstanceHandle: Object,
|
||||
): TextInstance {
|
||||
const inst = {text: text, id: instanceCounter++};
|
||||
// Hide from unit tests
|
||||
Object.defineProperty(inst, 'id', {value: inst.id, enumerable: false});
|
||||
return inst;
|
||||
},
|
||||
|
||||
scheduleDeferredCallback(callback) {
|
||||
if (scheduledCallback) {
|
||||
throw new Error(
|
||||
'Scheduling a callback twice is excessive. Instead, keep track of ' +
|
||||
'whether the callback has already been scheduled.',
|
||||
);
|
||||
}
|
||||
scheduledCallback = callback;
|
||||
return 0;
|
||||
},
|
||||
|
||||
cancelDeferredCallback() {
|
||||
if (scheduledCallback === null) {
|
||||
throw new Error('No callback is scheduled.');
|
||||
}
|
||||
scheduledCallback = null;
|
||||
},
|
||||
|
||||
prepareForCommit(): void {},
|
||||
|
||||
resetAfterCommit(): void {},
|
||||
|
||||
now(): number {
|
||||
return elapsedTimeInMs;
|
||||
},
|
||||
|
||||
isPrimaryRenderer: true,
|
||||
};
|
||||
|
||||
const NoopRenderer = ReactFiberReconciler({
|
||||
...SharedHostConfig,
|
||||
mutation: {
|
||||
commitMount(instance: Instance, type: string, newProps: Props): void {
|
||||
// Noop
|
||||
},
|
||||
|
||||
commitUpdate(
|
||||
instance: Instance,
|
||||
updatePayload: Object,
|
||||
type: string,
|
||||
oldProps: Props,
|
||||
newProps: Props,
|
||||
): void {
|
||||
if (oldProps === null) {
|
||||
throw new Error('Should have old props');
|
||||
}
|
||||
instance.prop = newProps.prop;
|
||||
},
|
||||
|
||||
commitTextUpdate(
|
||||
textInstance: TextInstance,
|
||||
oldText: string,
|
||||
newText: string,
|
||||
): void {
|
||||
textInstance.text = newText;
|
||||
},
|
||||
|
||||
appendChild: appendChild,
|
||||
appendChildToContainer: appendChild,
|
||||
insertBefore: insertBefore,
|
||||
insertInContainerBefore: insertBefore,
|
||||
removeChild: removeChild,
|
||||
removeChildFromContainer: removeChild,
|
||||
|
||||
resetTextContent(instance: Instance): void {},
|
||||
},
|
||||
});
|
||||
|
||||
const PersistentNoopRenderer = enablePersistentReconciler
|
||||
? ReactFiberReconciler({
|
||||
...SharedHostConfig,
|
||||
persistence: {
|
||||
cloneInstance(
|
||||
instance: Instance,
|
||||
updatePayload: null | Object,
|
||||
type: string,
|
||||
oldProps: Props,
|
||||
newProps: Props,
|
||||
internalInstanceHandle: Object,
|
||||
keepChildren: boolean,
|
||||
recyclableInstance: null | Instance,
|
||||
): Instance {
|
||||
const clone = {
|
||||
id: instance.id,
|
||||
type: type,
|
||||
children: keepChildren ? instance.children : [],
|
||||
prop: newProps.prop,
|
||||
};
|
||||
Object.defineProperty(clone, 'id', {
|
||||
value: clone.id,
|
||||
enumerable: false,
|
||||
});
|
||||
return clone;
|
||||
},
|
||||
|
||||
createContainerChildSet(
|
||||
container: Container,
|
||||
): Array<Instance | TextInstance> {
|
||||
return [];
|
||||
},
|
||||
|
||||
appendChildToContainerChildSet(
|
||||
childSet: Array<Instance | TextInstance>,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
childSet.push(child);
|
||||
},
|
||||
|
||||
finalizeContainerChildren(
|
||||
container: Container,
|
||||
newChildren: Array<Instance | TextInstance>,
|
||||
): void {},
|
||||
|
||||
replaceContainerChildren(
|
||||
container: Container,
|
||||
newChildren: Array<Instance | TextInstance>,
|
||||
): void {
|
||||
container.children = newChildren;
|
||||
},
|
||||
},
|
||||
})
|
||||
: null;
|
||||
|
||||
const rootContainers = new Map();
|
||||
const roots = new Map();
|
||||
const persistentRoots = new Map();
|
||||
const DEFAULT_ROOT_ID = '<default>';
|
||||
|
||||
let yieldedValues = null;
|
||||
|
||||
let unitsRemaining;
|
||||
|
||||
function* flushUnitsOfWork(n: number): Generator<Array<mixed>, void, void> {
|
||||
let didStop = false;
|
||||
while (!didStop && scheduledCallback !== null) {
|
||||
let cb = scheduledCallback;
|
||||
scheduledCallback = null;
|
||||
unitsRemaining = n;
|
||||
cb({
|
||||
timeRemaining() {
|
||||
if (yieldedValues !== null) {
|
||||
return 0;
|
||||
}
|
||||
if (unitsRemaining-- > 0) {
|
||||
return 999;
|
||||
}
|
||||
didStop = true;
|
||||
return 0;
|
||||
},
|
||||
// React's scheduler has its own way of keeping track of expired
|
||||
// work and doesn't read this, so don't bother setting it to the
|
||||
// correct value.
|
||||
didTimeout: false,
|
||||
});
|
||||
|
||||
if (yieldedValues !== null) {
|
||||
const values = yieldedValues;
|
||||
yieldedValues = null;
|
||||
yield values;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ReactNoop = {
|
||||
getChildren(rootID: string = DEFAULT_ROOT_ID) {
|
||||
const container = rootContainers.get(rootID);
|
||||
if (container) {
|
||||
return container.children;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
createPortal(
|
||||
children: ReactNodeList,
|
||||
container: Container,
|
||||
key: ?string = null,
|
||||
) {
|
||||
return ReactPortal.createPortal(children, container, null, key);
|
||||
},
|
||||
|
||||
// Shortcut for testing a single root
|
||||
render(element: React$Element<any>, callback: ?Function) {
|
||||
ReactNoop.renderToRootWithID(element, DEFAULT_ROOT_ID, callback);
|
||||
},
|
||||
|
||||
renderToRootWithID(
|
||||
element: React$Element<any>,
|
||||
rootID: string,
|
||||
callback: ?Function,
|
||||
) {
|
||||
let root = roots.get(rootID);
|
||||
if (!root) {
|
||||
const container = {rootID: rootID, children: []};
|
||||
rootContainers.set(rootID, container);
|
||||
root = NoopRenderer.createContainer(container, true, false);
|
||||
roots.set(rootID, root);
|
||||
}
|
||||
NoopRenderer.updateContainer(element, root, null, callback);
|
||||
},
|
||||
|
||||
renderToPersistentRootWithID(
|
||||
element: React$Element<any>,
|
||||
rootID: string,
|
||||
callback: ?Function,
|
||||
) {
|
||||
if (PersistentNoopRenderer === null) {
|
||||
throw new Error(
|
||||
'Enable ReactFeatureFlags.enablePersistentReconciler to use it in tests.',
|
||||
);
|
||||
}
|
||||
let root = persistentRoots.get(rootID);
|
||||
if (!root) {
|
||||
const container = {rootID: rootID, children: []};
|
||||
rootContainers.set(rootID, container);
|
||||
root = PersistentNoopRenderer.createContainer(container, true, false);
|
||||
persistentRoots.set(rootID, root);
|
||||
}
|
||||
PersistentNoopRenderer.updateContainer(element, root, null, callback);
|
||||
},
|
||||
|
||||
unmountRootWithID(rootID: string) {
|
||||
const root = roots.get(rootID);
|
||||
if (root) {
|
||||
NoopRenderer.updateContainer(null, root, null, () => {
|
||||
roots.delete(rootID);
|
||||
rootContainers.delete(rootID);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
findInstance(
|
||||
componentOrElement: Element | ?React$Component<any, any>,
|
||||
): null | Instance | TextInstance {
|
||||
if (componentOrElement == null) {
|
||||
return null;
|
||||
}
|
||||
// Unsound duck typing.
|
||||
const component = (componentOrElement: any);
|
||||
if (typeof component.id === 'number') {
|
||||
return component;
|
||||
}
|
||||
return NoopRenderer.findHostInstance(component);
|
||||
},
|
||||
|
||||
flushDeferredPri(timeout: number = Infinity): Array<mixed> {
|
||||
// The legacy version of this function decremented the timeout before
|
||||
// returning the new time.
|
||||
// TODO: Convert tests to use flushUnitsOfWork or flushAndYield instead.
|
||||
const n = timeout / 5 - 1;
|
||||
|
||||
let values = [];
|
||||
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
|
||||
for (const value of flushUnitsOfWork(n)) {
|
||||
values.push(...value);
|
||||
}
|
||||
return values;
|
||||
},
|
||||
|
||||
flush(): Array<mixed> {
|
||||
return ReactNoop.flushUnitsOfWork(Infinity);
|
||||
},
|
||||
|
||||
flushAndYield(
|
||||
unitsOfWork: number = Infinity,
|
||||
): Generator<Array<mixed>, void, void> {
|
||||
return flushUnitsOfWork(unitsOfWork);
|
||||
},
|
||||
|
||||
flushUnitsOfWork(n: number): Array<mixed> {
|
||||
let values = yieldedValues || [];
|
||||
yieldedValues = null;
|
||||
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
|
||||
for (const value of flushUnitsOfWork(n)) {
|
||||
values.push(...value);
|
||||
}
|
||||
return values;
|
||||
},
|
||||
|
||||
flushThrough(expected: Array<mixed>): void {
|
||||
let actual = [];
|
||||
if (expected.length !== 0) {
|
||||
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
|
||||
for (const value of flushUnitsOfWork(Infinity)) {
|
||||
actual.push(...value);
|
||||
if (actual.length >= expected.length) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
expect(actual).toEqual(expected);
|
||||
},
|
||||
|
||||
expire(ms: number): void {
|
||||
elapsedTimeInMs += ms;
|
||||
},
|
||||
|
||||
flushExpired(): Array<mixed> {
|
||||
return ReactNoop.flushUnitsOfWork(0);
|
||||
},
|
||||
|
||||
yield(value: mixed) {
|
||||
if (yieldedValues === null) {
|
||||
yieldedValues = [value];
|
||||
} else {
|
||||
yieldedValues.push(value);
|
||||
}
|
||||
},
|
||||
|
||||
clearYields() {
|
||||
const values = yieldedValues;
|
||||
yieldedValues = null;
|
||||
return values;
|
||||
},
|
||||
|
||||
hasScheduledCallback() {
|
||||
return !!scheduledCallback;
|
||||
},
|
||||
|
||||
batchedUpdates: NoopRenderer.batchedUpdates,
|
||||
|
||||
deferredUpdates: NoopRenderer.deferredUpdates,
|
||||
|
||||
unbatchedUpdates: NoopRenderer.unbatchedUpdates,
|
||||
|
||||
interactiveUpdates: NoopRenderer.interactiveUpdates,
|
||||
|
||||
flushSync(fn: () => mixed) {
|
||||
yieldedValues = [];
|
||||
NoopRenderer.flushSync(fn);
|
||||
return yieldedValues;
|
||||
},
|
||||
|
||||
// Logs the current state of the tree.
|
||||
dumpTree(rootID: string = DEFAULT_ROOT_ID) {
|
||||
const root = roots.get(rootID);
|
||||
const rootContainer = rootContainers.get(rootID);
|
||||
if (!root || !rootContainer) {
|
||||
console.log('Nothing rendered yet.');
|
||||
return;
|
||||
}
|
||||
|
||||
let bufferedLog = [];
|
||||
function log(...args) {
|
||||
bufferedLog.push(...args, '\n');
|
||||
}
|
||||
|
||||
function logHostInstances(children: Array<Instance | TextInstance>, depth) {
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i];
|
||||
const indent = ' '.repeat(depth);
|
||||
if (typeof child.text === 'string') {
|
||||
log(indent + '- ' + child.text);
|
||||
} else {
|
||||
// $FlowFixMe - The child should've been refined now.
|
||||
log(indent + '- ' + child.type + '#' + child.id);
|
||||
// $FlowFixMe - The child should've been refined now.
|
||||
logHostInstances(child.children, depth + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
function logContainer(container: Container, depth) {
|
||||
log(' '.repeat(depth) + '- [root#' + container.rootID + ']');
|
||||
logHostInstances(container.children, depth + 1);
|
||||
}
|
||||
|
||||
function logUpdateQueue(updateQueue: UpdateQueue<mixed>, depth) {
|
||||
log(' '.repeat(depth + 1) + 'QUEUED UPDATES');
|
||||
const firstUpdate = updateQueue.firstUpdate;
|
||||
if (!firstUpdate) {
|
||||
return;
|
||||
}
|
||||
|
||||
log(' '.repeat(depth + 1) + '~', '[' + firstUpdate.expirationTime + ']');
|
||||
while (firstUpdate.next) {
|
||||
log(
|
||||
' '.repeat(depth + 1) + '~',
|
||||
'[' + firstUpdate.expirationTime + ']',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function logFiber(fiber: Fiber, depth) {
|
||||
log(
|
||||
' '.repeat(depth) +
|
||||
'- ' +
|
||||
// need to explicitly coerce Symbol to a string
|
||||
(fiber.type ? fiber.type.name || fiber.type.toString() : '[root]'),
|
||||
'[' + fiber.expirationTime + (fiber.pendingProps ? '*' : '') + ']',
|
||||
);
|
||||
if (fiber.updateQueue) {
|
||||
logUpdateQueue(fiber.updateQueue, depth);
|
||||
}
|
||||
// const childInProgress = fiber.progressedChild;
|
||||
// if (childInProgress && childInProgress !== fiber.child) {
|
||||
// log(
|
||||
// ' '.repeat(depth + 1) + 'IN PROGRESS: ' + fiber.pendingWorkPriority,
|
||||
// );
|
||||
// logFiber(childInProgress, depth + 1);
|
||||
// if (fiber.child) {
|
||||
// log(' '.repeat(depth + 1) + 'CURRENT');
|
||||
// }
|
||||
// } else if (fiber.child && fiber.updateQueue) {
|
||||
// log(' '.repeat(depth + 1) + 'CHILDREN');
|
||||
// }
|
||||
if (fiber.child) {
|
||||
logFiber(fiber.child, depth + 1);
|
||||
}
|
||||
if (fiber.sibling) {
|
||||
logFiber(fiber.sibling, depth);
|
||||
}
|
||||
}
|
||||
|
||||
log('HOST INSTANCES:');
|
||||
logContainer(rootContainer, 0);
|
||||
log('FIBERS:');
|
||||
logFiber(root.current, 0);
|
||||
|
||||
console.log(...bufferedLog);
|
||||
},
|
||||
|
||||
simulateErrorInHostConfig(fn: () => void) {
|
||||
failInBeginPhase = true;
|
||||
try {
|
||||
fn();
|
||||
} finally {
|
||||
failInBeginPhase = false;
|
||||
}
|
||||
},
|
||||
};
|
||||
const ReactNoop = createReactNoop(
|
||||
true, // useMutation
|
||||
);
|
||||
|
||||
export default ReactNoop;
|
||||
|
||||
23
packages/react-noop-renderer/src/ReactNoopPersistent.js
vendored
Normal file
23
packages/react-noop-renderer/src/ReactNoopPersistent.js
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Copyright (c) 2013-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
/**
|
||||
* This is a renderer of React that doesn't have a render target output.
|
||||
* It is useful to demonstrate the internals of the reconciler in isolation
|
||||
* and for testing semantics of reconciliation separate from the host
|
||||
* environment.
|
||||
*/
|
||||
|
||||
import createReactNoop from './createReactNoop';
|
||||
|
||||
const ReactNoopPersistent = createReactNoop(
|
||||
false, // useMutation
|
||||
);
|
||||
|
||||
export default ReactNoopPersistent;
|
||||
582
packages/react-noop-renderer/src/createReactNoop.js
vendored
Normal file
582
packages/react-noop-renderer/src/createReactNoop.js
vendored
Normal file
@@ -0,0 +1,582 @@
|
||||
/**
|
||||
* Copyright (c) 2013-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
/**
|
||||
* This is a renderer of React that doesn't have a render target output.
|
||||
* It is useful to demonstrate the internals of the reconciler in isolation
|
||||
* and for testing semantics of reconciliation separate from the host
|
||||
* environment.
|
||||
*/
|
||||
|
||||
import type {Fiber} from 'react-reconciler/src/ReactFiber';
|
||||
import type {UpdateQueue} from 'react-reconciler/src/ReactUpdateQueue';
|
||||
import type {ReactNodeList} from 'shared/ReactTypes';
|
||||
|
||||
import ReactFiberReconciler from 'react-reconciler';
|
||||
import * as ReactPortal from 'shared/ReactPortal';
|
||||
import emptyObject from 'fbjs/lib/emptyObject';
|
||||
import expect from 'expect';
|
||||
|
||||
type Container = {rootID: string, children: Array<Instance | TextInstance>};
|
||||
type Props = {prop: any, hidden?: boolean};
|
||||
type Instance = {|
|
||||
type: string,
|
||||
id: number,
|
||||
children: Array<Instance | TextInstance>,
|
||||
prop: any,
|
||||
|};
|
||||
type TextInstance = {|text: string, id: number|};
|
||||
|
||||
function createReactNoop(useMutation: boolean) {
|
||||
const UPDATE_SIGNAL = {};
|
||||
let scheduledCallback = null;
|
||||
|
||||
let instanceCounter = 0;
|
||||
let failInBeginPhase = false;
|
||||
|
||||
function appendChild(
|
||||
parentInstance: Instance | Container,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
const index = parentInstance.children.indexOf(child);
|
||||
if (index !== -1) {
|
||||
parentInstance.children.splice(index, 1);
|
||||
}
|
||||
parentInstance.children.push(child);
|
||||
}
|
||||
|
||||
function insertBefore(
|
||||
parentInstance: Instance | Container,
|
||||
child: Instance | TextInstance,
|
||||
beforeChild: Instance | TextInstance,
|
||||
): void {
|
||||
const index = parentInstance.children.indexOf(child);
|
||||
if (index !== -1) {
|
||||
parentInstance.children.splice(index, 1);
|
||||
}
|
||||
const beforeIndex = parentInstance.children.indexOf(beforeChild);
|
||||
if (beforeIndex === -1) {
|
||||
throw new Error('This child does not exist.');
|
||||
}
|
||||
parentInstance.children.splice(beforeIndex, 0, child);
|
||||
}
|
||||
|
||||
function removeChild(
|
||||
parentInstance: Instance | Container,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
const index = parentInstance.children.indexOf(child);
|
||||
if (index === -1) {
|
||||
throw new Error('This child does not exist.');
|
||||
}
|
||||
parentInstance.children.splice(index, 1);
|
||||
}
|
||||
|
||||
let elapsedTimeInMs = 0;
|
||||
|
||||
const sharedHostConfig = {
|
||||
getRootHostContext() {
|
||||
if (failInBeginPhase) {
|
||||
throw new Error('Error in host config.');
|
||||
}
|
||||
return emptyObject;
|
||||
},
|
||||
|
||||
getChildHostContext() {
|
||||
return emptyObject;
|
||||
},
|
||||
|
||||
getPublicInstance(instance) {
|
||||
return instance;
|
||||
},
|
||||
|
||||
createInstance(type: string, props: Props): Instance {
|
||||
const inst = {
|
||||
id: instanceCounter++,
|
||||
type: type,
|
||||
children: [],
|
||||
prop: props.prop,
|
||||
};
|
||||
// Hide from unit tests
|
||||
Object.defineProperty(inst, 'id', {value: inst.id, enumerable: false});
|
||||
return inst;
|
||||
},
|
||||
|
||||
appendInitialChild(
|
||||
parentInstance: Instance,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
parentInstance.children.push(child);
|
||||
},
|
||||
|
||||
finalizeInitialChildren(
|
||||
domElement: Instance,
|
||||
type: string,
|
||||
props: Props,
|
||||
): boolean {
|
||||
return false;
|
||||
},
|
||||
|
||||
prepareUpdate(
|
||||
instance: Instance,
|
||||
type: string,
|
||||
oldProps: Props,
|
||||
newProps: Props,
|
||||
): null | {} {
|
||||
if (oldProps === null) {
|
||||
throw new Error('Should have old props');
|
||||
}
|
||||
if (newProps === null) {
|
||||
throw new Error('Should have new props');
|
||||
}
|
||||
return UPDATE_SIGNAL;
|
||||
},
|
||||
|
||||
shouldSetTextContent(type: string, props: Props): boolean {
|
||||
return (
|
||||
typeof props.children === 'string' || typeof props.children === 'number'
|
||||
);
|
||||
},
|
||||
|
||||
shouldDeprioritizeSubtree(type: string, props: Props): boolean {
|
||||
return !!props.hidden;
|
||||
},
|
||||
|
||||
createTextInstance(
|
||||
text: string,
|
||||
rootContainerInstance: Container,
|
||||
hostContext: Object,
|
||||
internalInstanceHandle: Object,
|
||||
): TextInstance {
|
||||
const inst = {text: text, id: instanceCounter++};
|
||||
// Hide from unit tests
|
||||
Object.defineProperty(inst, 'id', {value: inst.id, enumerable: false});
|
||||
return inst;
|
||||
},
|
||||
|
||||
scheduleDeferredCallback(callback) {
|
||||
if (scheduledCallback) {
|
||||
throw new Error(
|
||||
'Scheduling a callback twice is excessive. Instead, keep track of ' +
|
||||
'whether the callback has already been scheduled.',
|
||||
);
|
||||
}
|
||||
scheduledCallback = callback;
|
||||
return 0;
|
||||
},
|
||||
|
||||
cancelDeferredCallback() {
|
||||
if (scheduledCallback === null) {
|
||||
throw new Error('No callback is scheduled.');
|
||||
}
|
||||
scheduledCallback = null;
|
||||
},
|
||||
|
||||
prepareForCommit(): void {},
|
||||
|
||||
resetAfterCommit(): void {},
|
||||
|
||||
now(): number {
|
||||
return elapsedTimeInMs;
|
||||
},
|
||||
|
||||
isPrimaryRenderer: true,
|
||||
};
|
||||
|
||||
const hostConfig = useMutation
|
||||
? {
|
||||
...sharedHostConfig,
|
||||
mutation: {
|
||||
commitMount(instance: Instance, type: string, newProps: Props): void {
|
||||
// Noop
|
||||
},
|
||||
|
||||
commitUpdate(
|
||||
instance: Instance,
|
||||
updatePayload: Object,
|
||||
type: string,
|
||||
oldProps: Props,
|
||||
newProps: Props,
|
||||
): void {
|
||||
if (oldProps === null) {
|
||||
throw new Error('Should have old props');
|
||||
}
|
||||
instance.prop = newProps.prop;
|
||||
},
|
||||
|
||||
commitTextUpdate(
|
||||
textInstance: TextInstance,
|
||||
oldText: string,
|
||||
newText: string,
|
||||
): void {
|
||||
textInstance.text = newText;
|
||||
},
|
||||
|
||||
appendChild: appendChild,
|
||||
appendChildToContainer: appendChild,
|
||||
insertBefore: insertBefore,
|
||||
insertInContainerBefore: insertBefore,
|
||||
removeChild: removeChild,
|
||||
removeChildFromContainer: removeChild,
|
||||
|
||||
resetTextContent(instance: Instance): void {},
|
||||
},
|
||||
}
|
||||
: {
|
||||
...sharedHostConfig,
|
||||
persistence: {
|
||||
cloneInstance(
|
||||
instance: Instance,
|
||||
updatePayload: null | Object,
|
||||
type: string,
|
||||
oldProps: Props,
|
||||
newProps: Props,
|
||||
internalInstanceHandle: Object,
|
||||
keepChildren: boolean,
|
||||
recyclableInstance: null | Instance,
|
||||
): Instance {
|
||||
const clone = {
|
||||
id: instance.id,
|
||||
type: type,
|
||||
children: keepChildren ? instance.children : [],
|
||||
prop: newProps.prop,
|
||||
};
|
||||
Object.defineProperty(clone, 'id', {
|
||||
value: clone.id,
|
||||
enumerable: false,
|
||||
});
|
||||
return clone;
|
||||
},
|
||||
|
||||
createContainerChildSet(
|
||||
container: Container,
|
||||
): Array<Instance | TextInstance> {
|
||||
return [];
|
||||
},
|
||||
|
||||
appendChildToContainerChildSet(
|
||||
childSet: Array<Instance | TextInstance>,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
childSet.push(child);
|
||||
},
|
||||
|
||||
finalizeContainerChildren(
|
||||
container: Container,
|
||||
newChildren: Array<Instance | TextInstance>,
|
||||
): void {},
|
||||
|
||||
replaceContainerChildren(
|
||||
container: Container,
|
||||
newChildren: Array<Instance | TextInstance>,
|
||||
): void {
|
||||
container.children = newChildren;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const NoopRenderer = ReactFiberReconciler(hostConfig);
|
||||
|
||||
const rootContainers = new Map();
|
||||
const roots = new Map();
|
||||
const DEFAULT_ROOT_ID = '<default>';
|
||||
|
||||
let yieldedValues = null;
|
||||
|
||||
let unitsRemaining;
|
||||
|
||||
function* flushUnitsOfWork(n: number): Generator<Array<mixed>, void, void> {
|
||||
let didStop = false;
|
||||
while (!didStop && scheduledCallback !== null) {
|
||||
let cb = scheduledCallback;
|
||||
scheduledCallback = null;
|
||||
unitsRemaining = n;
|
||||
cb({
|
||||
timeRemaining() {
|
||||
if (yieldedValues !== null) {
|
||||
return 0;
|
||||
}
|
||||
if (unitsRemaining-- > 0) {
|
||||
return 999;
|
||||
}
|
||||
didStop = true;
|
||||
return 0;
|
||||
},
|
||||
// React's scheduler has its own way of keeping track of expired
|
||||
// work and doesn't read this, so don't bother setting it to the
|
||||
// correct value.
|
||||
didTimeout: false,
|
||||
});
|
||||
|
||||
if (yieldedValues !== null) {
|
||||
const values = yieldedValues;
|
||||
yieldedValues = null;
|
||||
yield values;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ReactNoop = {
|
||||
getChildren(rootID: string = DEFAULT_ROOT_ID) {
|
||||
const container = rootContainers.get(rootID);
|
||||
if (container) {
|
||||
return container.children;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
createPortal(
|
||||
children: ReactNodeList,
|
||||
container: Container,
|
||||
key: ?string = null,
|
||||
) {
|
||||
return ReactPortal.createPortal(children, container, null, key);
|
||||
},
|
||||
|
||||
// Shortcut for testing a single root
|
||||
render(element: React$Element<any>, callback: ?Function) {
|
||||
ReactNoop.renderToRootWithID(element, DEFAULT_ROOT_ID, callback);
|
||||
},
|
||||
|
||||
renderToRootWithID(
|
||||
element: React$Element<any>,
|
||||
rootID: string,
|
||||
callback: ?Function,
|
||||
) {
|
||||
let root = roots.get(rootID);
|
||||
if (!root) {
|
||||
const container = {rootID: rootID, children: []};
|
||||
rootContainers.set(rootID, container);
|
||||
root = NoopRenderer.createContainer(container, true, false);
|
||||
roots.set(rootID, root);
|
||||
}
|
||||
NoopRenderer.updateContainer(element, root, null, callback);
|
||||
},
|
||||
|
||||
unmountRootWithID(rootID: string) {
|
||||
const root = roots.get(rootID);
|
||||
if (root) {
|
||||
NoopRenderer.updateContainer(null, root, null, () => {
|
||||
roots.delete(rootID);
|
||||
rootContainers.delete(rootID);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
findInstance(
|
||||
componentOrElement: Element | ?React$Component<any, any>,
|
||||
): null | Instance | TextInstance {
|
||||
if (componentOrElement == null) {
|
||||
return null;
|
||||
}
|
||||
// Unsound duck typing.
|
||||
const component = (componentOrElement: any);
|
||||
if (typeof component.id === 'number') {
|
||||
return component;
|
||||
}
|
||||
return NoopRenderer.findHostInstance(component);
|
||||
},
|
||||
|
||||
flushDeferredPri(timeout: number = Infinity): Array<mixed> {
|
||||
// The legacy version of this function decremented the timeout before
|
||||
// returning the new time.
|
||||
// TODO: Convert tests to use flushUnitsOfWork or flushAndYield instead.
|
||||
const n = timeout / 5 - 1;
|
||||
|
||||
let values = [];
|
||||
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
|
||||
for (const value of flushUnitsOfWork(n)) {
|
||||
values.push(...value);
|
||||
}
|
||||
return values;
|
||||
},
|
||||
|
||||
flush(): Array<mixed> {
|
||||
return ReactNoop.flushUnitsOfWork(Infinity);
|
||||
},
|
||||
|
||||
flushAndYield(
|
||||
unitsOfWork: number = Infinity,
|
||||
): Generator<Array<mixed>, void, void> {
|
||||
return flushUnitsOfWork(unitsOfWork);
|
||||
},
|
||||
|
||||
flushUnitsOfWork(n: number): Array<mixed> {
|
||||
let values = yieldedValues || [];
|
||||
yieldedValues = null;
|
||||
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
|
||||
for (const value of flushUnitsOfWork(n)) {
|
||||
values.push(...value);
|
||||
}
|
||||
return values;
|
||||
},
|
||||
|
||||
flushThrough(expected: Array<mixed>): void {
|
||||
let actual = [];
|
||||
if (expected.length !== 0) {
|
||||
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
|
||||
for (const value of flushUnitsOfWork(Infinity)) {
|
||||
actual.push(...value);
|
||||
if (actual.length >= expected.length) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
expect(actual).toEqual(expected);
|
||||
},
|
||||
|
||||
expire(ms: number): void {
|
||||
elapsedTimeInMs += ms;
|
||||
},
|
||||
|
||||
flushExpired(): Array<mixed> {
|
||||
return ReactNoop.flushUnitsOfWork(0);
|
||||
},
|
||||
|
||||
yield(value: mixed) {
|
||||
if (yieldedValues === null) {
|
||||
yieldedValues = [value];
|
||||
} else {
|
||||
yieldedValues.push(value);
|
||||
}
|
||||
},
|
||||
|
||||
clearYields() {
|
||||
const values = yieldedValues;
|
||||
yieldedValues = null;
|
||||
return values;
|
||||
},
|
||||
|
||||
hasScheduledCallback() {
|
||||
return !!scheduledCallback;
|
||||
},
|
||||
|
||||
batchedUpdates: NoopRenderer.batchedUpdates,
|
||||
|
||||
deferredUpdates: NoopRenderer.deferredUpdates,
|
||||
|
||||
unbatchedUpdates: NoopRenderer.unbatchedUpdates,
|
||||
|
||||
interactiveUpdates: NoopRenderer.interactiveUpdates,
|
||||
|
||||
flushSync(fn: () => mixed) {
|
||||
yieldedValues = [];
|
||||
NoopRenderer.flushSync(fn);
|
||||
return yieldedValues;
|
||||
},
|
||||
|
||||
// Logs the current state of the tree.
|
||||
dumpTree(rootID: string = DEFAULT_ROOT_ID) {
|
||||
const root = roots.get(rootID);
|
||||
const rootContainer = rootContainers.get(rootID);
|
||||
if (!root || !rootContainer) {
|
||||
console.log('Nothing rendered yet.');
|
||||
return;
|
||||
}
|
||||
|
||||
let bufferedLog = [];
|
||||
function log(...args) {
|
||||
bufferedLog.push(...args, '\n');
|
||||
}
|
||||
|
||||
function logHostInstances(
|
||||
children: Array<Instance | TextInstance>,
|
||||
depth,
|
||||
) {
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i];
|
||||
const indent = ' '.repeat(depth);
|
||||
if (typeof child.text === 'string') {
|
||||
log(indent + '- ' + child.text);
|
||||
} else {
|
||||
// $FlowFixMe - The child should've been refined now.
|
||||
log(indent + '- ' + child.type + '#' + child.id);
|
||||
// $FlowFixMe - The child should've been refined now.
|
||||
logHostInstances(child.children, depth + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
function logContainer(container: Container, depth) {
|
||||
log(' '.repeat(depth) + '- [root#' + container.rootID + ']');
|
||||
logHostInstances(container.children, depth + 1);
|
||||
}
|
||||
|
||||
function logUpdateQueue(updateQueue: UpdateQueue<mixed>, depth) {
|
||||
log(' '.repeat(depth + 1) + 'QUEUED UPDATES');
|
||||
const firstUpdate = updateQueue.firstUpdate;
|
||||
if (!firstUpdate) {
|
||||
return;
|
||||
}
|
||||
|
||||
log(
|
||||
' '.repeat(depth + 1) + '~',
|
||||
'[' + firstUpdate.expirationTime + ']',
|
||||
);
|
||||
while (firstUpdate.next) {
|
||||
log(
|
||||
' '.repeat(depth + 1) + '~',
|
||||
'[' + firstUpdate.expirationTime + ']',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function logFiber(fiber: Fiber, depth) {
|
||||
log(
|
||||
' '.repeat(depth) +
|
||||
'- ' +
|
||||
// need to explicitly coerce Symbol to a string
|
||||
(fiber.type ? fiber.type.name || fiber.type.toString() : '[root]'),
|
||||
'[' + fiber.expirationTime + (fiber.pendingProps ? '*' : '') + ']',
|
||||
);
|
||||
if (fiber.updateQueue) {
|
||||
logUpdateQueue(fiber.updateQueue, depth);
|
||||
}
|
||||
// const childInProgress = fiber.progressedChild;
|
||||
// if (childInProgress && childInProgress !== fiber.child) {
|
||||
// log(
|
||||
// ' '.repeat(depth + 1) + 'IN PROGRESS: ' + fiber.pendingWorkPriority,
|
||||
// );
|
||||
// logFiber(childInProgress, depth + 1);
|
||||
// if (fiber.child) {
|
||||
// log(' '.repeat(depth + 1) + 'CURRENT');
|
||||
// }
|
||||
// } else if (fiber.child && fiber.updateQueue) {
|
||||
// log(' '.repeat(depth + 1) + 'CHILDREN');
|
||||
// }
|
||||
if (fiber.child) {
|
||||
logFiber(fiber.child, depth + 1);
|
||||
}
|
||||
if (fiber.sibling) {
|
||||
logFiber(fiber.sibling, depth);
|
||||
}
|
||||
}
|
||||
|
||||
log('HOST INSTANCES:');
|
||||
logContainer(rootContainer, 0);
|
||||
log('FIBERS:');
|
||||
logFiber(root.current, 0);
|
||||
|
||||
console.log(...bufferedLog);
|
||||
},
|
||||
|
||||
simulateErrorInHostConfig(fn: () => void) {
|
||||
failInBeginPhase = true;
|
||||
try {
|
||||
fn();
|
||||
} finally {
|
||||
failInBeginPhase = false;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
return ReactNoop;
|
||||
}
|
||||
|
||||
export default createReactNoop;
|
||||
@@ -11,7 +11,7 @@
|
||||
'use strict';
|
||||
|
||||
let React;
|
||||
let ReactNoop;
|
||||
let ReactNoopPersistent;
|
||||
let ReactPortal;
|
||||
|
||||
describe('ReactPersistent', () => {
|
||||
@@ -24,14 +24,12 @@ describe('ReactPersistent', () => {
|
||||
ReactFeatureFlags.enableNoopReconciler = false;
|
||||
|
||||
React = require('react');
|
||||
ReactNoop = require('react-noop-renderer');
|
||||
ReactNoopPersistent = require('react-noop-renderer/persistent');
|
||||
ReactPortal = require('shared/ReactPortal');
|
||||
});
|
||||
|
||||
const DEFAULT_ROOT_ID = 'persistent-test';
|
||||
|
||||
function render(element) {
|
||||
ReactNoop.renderToPersistentRootWithID(element, DEFAULT_ROOT_ID);
|
||||
ReactNoopPersistent.render(element);
|
||||
}
|
||||
|
||||
function div(...children) {
|
||||
@@ -44,7 +42,7 @@ describe('ReactPersistent', () => {
|
||||
}
|
||||
|
||||
function getChildren() {
|
||||
return ReactNoop.getChildren(DEFAULT_ROOT_ID);
|
||||
return ReactNoopPersistent.getChildren();
|
||||
}
|
||||
|
||||
it('can update child nodes of a host instance', () => {
|
||||
@@ -62,12 +60,12 @@ describe('ReactPersistent', () => {
|
||||
}
|
||||
|
||||
render(<Foo text="Hello" />);
|
||||
ReactNoop.flush();
|
||||
ReactNoopPersistent.flush();
|
||||
const originalChildren = getChildren();
|
||||
expect(originalChildren).toEqual([div(span())]);
|
||||
|
||||
render(<Foo text="World" />);
|
||||
ReactNoop.flush();
|
||||
ReactNoopPersistent.flush();
|
||||
const newChildren = getChildren();
|
||||
expect(newChildren).toEqual([div(span(), span())]);
|
||||
|
||||
@@ -96,12 +94,12 @@ describe('ReactPersistent', () => {
|
||||
}
|
||||
|
||||
render(<Foo text="Hello" />);
|
||||
ReactNoop.flush();
|
||||
ReactNoopPersistent.flush();
|
||||
const originalChildren = getChildren();
|
||||
expect(originalChildren).toEqual([div(span('Hello'))]);
|
||||
|
||||
render(<Foo text="World" />);
|
||||
ReactNoop.flush();
|
||||
ReactNoopPersistent.flush();
|
||||
const newChildren = getChildren();
|
||||
expect(newChildren).toEqual([div(span('Hello'), span('World'))]);
|
||||
|
||||
@@ -122,12 +120,12 @@ describe('ReactPersistent', () => {
|
||||
}
|
||||
|
||||
render(<Foo text="Hello" />);
|
||||
ReactNoop.flush();
|
||||
ReactNoopPersistent.flush();
|
||||
const originalChildren = getChildren();
|
||||
expect(originalChildren).toEqual([div('Hello', span())]);
|
||||
|
||||
render(<Foo text="World" />);
|
||||
ReactNoop.flush();
|
||||
ReactNoopPersistent.flush();
|
||||
const newChildren = getChildren();
|
||||
expect(newChildren).toEqual([div('World', span())]);
|
||||
|
||||
@@ -167,7 +165,7 @@ describe('ReactPersistent', () => {
|
||||
{ReactPortal.createPortal(<Child />, portalContainer, null)}
|
||||
</Parent>,
|
||||
);
|
||||
ReactNoop.flush();
|
||||
ReactNoopPersistent.flush();
|
||||
|
||||
expect(emptyPortalChildSet).toEqual([]);
|
||||
|
||||
@@ -185,7 +183,7 @@ describe('ReactPersistent', () => {
|
||||
)}
|
||||
</Parent>,
|
||||
);
|
||||
ReactNoop.flush();
|
||||
ReactNoopPersistent.flush();
|
||||
|
||||
const newChildren = getChildren();
|
||||
expect(newChildren).toEqual([div()]);
|
||||
@@ -202,7 +200,7 @@ describe('ReactPersistent', () => {
|
||||
|
||||
// Deleting the Portal, should clear its children
|
||||
render(<Parent />);
|
||||
ReactNoop.flush();
|
||||
ReactNoopPersistent.flush();
|
||||
|
||||
const clearedPortalChildren = portalContainer.children;
|
||||
expect(clearedPortalChildren).toEqual([]);
|
||||
|
||||
@@ -275,6 +275,29 @@ const bundles = [
|
||||
}),
|
||||
},
|
||||
|
||||
/******* React Noop Persistent Renderer (used for tests) *******/
|
||||
{
|
||||
label: 'noop-persistent',
|
||||
bundleTypes: [NODE_DEV, NODE_PROD],
|
||||
moduleType: RENDERER,
|
||||
entry: 'react-noop-renderer/persistent',
|
||||
global: 'ReactNoopRendererPersistent',
|
||||
externals: ['react', 'expect'],
|
||||
// React Noop uses generators. However GCC currently
|
||||
// breaks when we attempt to use them in the output.
|
||||
// So we precompile them with regenerator, and include
|
||||
// it as a runtime dependency of React Noop. In practice
|
||||
// this isn't an issue because React Noop is only used
|
||||
// in our tests. We wouldn't want to do this for any
|
||||
// public package though.
|
||||
babel: opts =>
|
||||
Object.assign({}, opts, {
|
||||
plugins: opts.plugins.concat([
|
||||
require.resolve('babel-plugin-transform-regenerator'),
|
||||
]),
|
||||
}),
|
||||
},
|
||||
|
||||
/******* React Reconciler *******/
|
||||
{
|
||||
label: 'react-reconciler',
|
||||
|
||||
Reference in New Issue
Block a user