From a293d75f753b01db2cf47ddbaaae68bb08e755f2 Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Wed, 1 Mar 2017 12:48:02 -0800 Subject: [PATCH 01/32] Test that ReactErrorUtils module can be shimmed We do this in www --- scripts/fiber/tests-passing.txt | 2 ++ .../utils/__tests__/ReactErrorUtils-test.js | 25 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/scripts/fiber/tests-passing.txt b/scripts/fiber/tests-passing.txt index ade485d62d..e8f18715b8 100644 --- a/scripts/fiber/tests-passing.txt +++ b/scripts/fiber/tests-passing.txt @@ -1695,6 +1695,7 @@ src/renderers/shared/utils/__tests__/ReactErrorUtils-test.js * should return null if no error is thrown (development) * can nest with same debug name (development) * does not return nested errors (development) +* can be shimmed (development) * it should rethrow errors caught by invokeGuardedCallbackAndCatchFirstError (production) * should call the callback the passed arguments (production) * should call the callback with the provided context (production) @@ -1702,6 +1703,7 @@ src/renderers/shared/utils/__tests__/ReactErrorUtils-test.js * should return null if no error is thrown (production) * can nest with same debug name (production) * does not return nested errors (production) +* can be shimmed (production) src/renderers/shared/utils/__tests__/accumulateInto-test.js * throws if the second item is null diff --git a/src/renderers/shared/utils/__tests__/ReactErrorUtils-test.js b/src/renderers/shared/utils/__tests__/ReactErrorUtils-test.js index 74069e5900..c540a2bb1e 100644 --- a/src/renderers/shared/utils/__tests__/ReactErrorUtils-test.js +++ b/src/renderers/shared/utils/__tests__/ReactErrorUtils-test.js @@ -106,5 +106,30 @@ describe('ReactErrorUtils', () => { expect(err3).toBe(null); // Returns null because inner error was already captured expect(err2).toBe(err1); }); + + it(`can be shimmed (${environment})`, () => { + const ops = []; + // Override the original invokeGuardedCallback + ReactErrorUtils.invokeGuardedCallback = function(name, func, context, a) { + ops.push(a); + try { + func.call(context, a); + } catch (error) { + return error; + } + return null; + }; + + var err = new Error('foo'); + var callback = function() { + throw err; + }; + ReactErrorUtils.invokeGuardedCallbackAndCatchFirstError('foo', callback, null, 'somearg'); + expect(() => ReactErrorUtils.rethrowCaughtError()).toThrow(err); + // invokeGuardedCallbackAndCatchFirstError and rethrowCaughtError close + // over ReactErrorUtils.invokeGuardedCallback so should use the + // shimmed version. + expect(ops).toEqual(['somearg']); + }); } }); From b1a565f8fa976f7b4b772084ab97b58b7bf28ebf Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Wed, 1 Mar 2017 14:38:29 -0800 Subject: [PATCH 02/32] Convert shorthanded syntax for function Flow types to expanded form Something in www's test pipeline isn't able to parse this. --- src/renderers/shared/utils/ReactErrorUtils.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderers/shared/utils/ReactErrorUtils.js b/src/renderers/shared/utils/ReactErrorUtils.js index c2cd671767..f53bdddc5a 100644 --- a/src/renderers/shared/utils/ReactErrorUtils.js +++ b/src/renderers/shared/utils/ReactErrorUtils.js @@ -26,7 +26,7 @@ let caughtError = null; const ReactErrorUtils = { invokeGuardedCallback: function( name: string | null, - func: (A, B, C, D, E, F) => void, + func: (a: A, b: B, c: C, d: D, e: E, f: F) => void, context: Context, a: A, b: B, @@ -55,7 +55,7 @@ const ReactErrorUtils = { */ invokeGuardedCallbackAndCatchFirstError: function( name: string | null, - func: (A, B, C, D, E, F) => void, + func: (a: A, b: B, c: C, d: D, e: E, f: F) => void, context: Context, a: A, b: B, From 0e23042e4bba436dceb8ec8ae42856429d2ea69e Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 2 Mar 2017 16:15:37 +0000 Subject: [PATCH 03/32] refactor of ReactComponentTreeHook to isolate dev methods --- .../classic/element/ReactDebugCurrentFrame.js | 13 +- .../hooks/ReactComponentTreeHook.js | 740 +++++++++--------- src/renderers/shared/ReactDebugTool.js | 22 +- .../shared/stack/reconciler/ReactRef.js | 2 +- src/shared/utils/flattenChildren.js | 2 +- 5 files changed, 393 insertions(+), 386 deletions(-) diff --git a/src/isomorphic/classic/element/ReactDebugCurrentFrame.js b/src/isomorphic/classic/element/ReactDebugCurrentFrame.js index f4efdd672b..9cfbab11c4 100644 --- a/src/isomorphic/classic/element/ReactDebugCurrentFrame.js +++ b/src/isomorphic/classic/element/ReactDebugCurrentFrame.js @@ -18,12 +18,7 @@ import type { DebugID } from 'ReactInstanceType'; const ReactDebugCurrentFrame = {}; if (__DEV__) { - var { - getStackAddendumByID, - getStackAddendumByWorkInProgressFiber, - getCurrentStackAddendum, - } = require('ReactComponentTreeHook'); - + var ReactComponentTreeHook = require('ReactComponentTreeHook'); // Component that is being worked on ReactDebugCurrentFrame.current = (null : Fiber | DebugID | null); @@ -38,16 +33,16 @@ if (__DEV__) { if (typeof current === 'number') { // DebugID from Stack. const debugID = current; - stack = getStackAddendumByID(debugID); + stack = (ReactComponentTreeHook: any).getStackAddendumByID(debugID); } else if (typeof current.tag === 'number') { // This is a Fiber. // The stack will only be correct if this is a work in progress // version and we're calling it during reconciliation. const workInProgress = current; - stack = getStackAddendumByWorkInProgressFiber(workInProgress); + stack = ReactComponentTreeHook.getStackAddendumByWorkInProgressFiber(workInProgress); } } else if (element !== null) { - stack = getCurrentStackAddendum(element); + stack = (ReactComponentTreeHook: any).getCurrentStackAddendum(element); } return stack; }; diff --git a/src/isomorphic/hooks/ReactComponentTreeHook.js b/src/isomorphic/hooks/ReactComponentTreeHook.js index 2359fd1c34..40c9a50279 100644 --- a/src/isomorphic/hooks/ReactComponentTreeHook.js +++ b/src/isomorphic/hooks/ReactComponentTreeHook.js @@ -12,7 +12,11 @@ 'use strict'; -var ReactCurrentOwner = require('ReactCurrentOwner'); +import type { ReactElement, Source } from 'ReactElementType'; +import type { DebugID } from 'ReactInstanceType'; +import type { Fiber } from 'ReactFiber'; + +var getComponentName = require('getComponentName'); var ReactTypeOfWork = require('ReactTypeOfWork'); var { IndeterminateComponent, @@ -21,144 +25,6 @@ var { HostComponent, } = ReactTypeOfWork; -var getComponentName = require('getComponentName'); -var invariant = require('fbjs/lib/invariant'); -var warning = require('fbjs/lib/warning'); - -import type { ReactElement, Source } from 'ReactElementType'; -import type { DebugID } from 'ReactInstanceType'; -import type { Fiber } from 'ReactFiber'; - -function isNative(fn) { - // Based on isNative() from Lodash - var funcToString = Function.prototype.toString; - var hasOwnProperty = Object.prototype.hasOwnProperty; - var reIsNative = RegExp('^' + funcToString - // Take an example native function source for comparison - .call(hasOwnProperty) - // Strip regex characters so we can use it for regex - .replace(/[\\^$.*+?()[\]{}|]/g, '\\$&') - // Remove hasOwnProperty from the template to make it generic - .replace( - /hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, - '$1.*?' - ) + '$' - ); - try { - var source = funcToString.call(fn); - return reIsNative.test(source); - } catch (err) { - return false; - } -} - -var canUseCollections = ( - // Array.from - typeof Array.from === 'function' && - // Map - typeof Map === 'function' && - isNative(Map) && - // Map.prototype.keys - Map.prototype != null && - typeof Map.prototype.keys === 'function' && - isNative(Map.prototype.keys) && - // Set - typeof Set === 'function' && - isNative(Set) && - // Set.prototype.keys - Set.prototype != null && - typeof Set.prototype.keys === 'function' && - isNative(Set.prototype.keys) -); - -var setItem; -var getItem; -var removeItem; -var getItemIDs; -var addRoot; -var removeRoot; -var getRootIDs; - -if (canUseCollections) { - var itemMap = new Map(); - var rootIDSet = new Set(); - - setItem = function(id, item) { - itemMap.set(id, item); - }; - getItem = function(id) { - return itemMap.get(id); - }; - removeItem = function(id) { - itemMap.delete(id); - }; - getItemIDs = function() { - return Array.from(itemMap.keys()); - }; - - addRoot = function(id) { - rootIDSet.add(id); - }; - removeRoot = function(id) { - rootIDSet.delete(id); - }; - getRootIDs = function() { - return Array.from(rootIDSet.keys()); - }; - -} else { - var itemByKey = {}; - var rootByKey = {}; - - // Use non-numeric keys to prevent V8 performance issues: - // https://github.com/facebook/react/pull/7232 - var getKeyFromID = function(id: DebugID): string { - return '.' + id; - }; - var getIDFromKey = function(key: string): DebugID { - return parseInt(key.substr(1), 10); - }; - - setItem = function(id, item) { - var key = getKeyFromID(id); - itemByKey[key] = item; - }; - getItem = function(id) { - var key = getKeyFromID(id); - return itemByKey[key]; - }; - removeItem = function(id) { - var key = getKeyFromID(id); - delete itemByKey[key]; - }; - getItemIDs = function() { - return Object.keys(itemByKey).map(getIDFromKey); - }; - - addRoot = function(id) { - var key = getKeyFromID(id); - rootByKey[key] = true; - }; - removeRoot = function(id) { - var key = getKeyFromID(id); - delete rootByKey[key]; - }; - getRootIDs = function() { - return Object.keys(rootByKey).map(getIDFromKey); - }; -} - -var unmountedIDs: Array = []; - -function purgeDeep(id) { - var item = getItem(id); - if (item) { - var {childIDs} = item; - removeItem(id); - childIDs.forEach(purgeDeep); - } -} - function describeComponentFrame(name, source, ownerName) { return '\n in ' + (name || 'Unknown') + ( source ? @@ -170,35 +36,6 @@ function describeComponentFrame(name, source, ownerName) { ); } -function getDisplayName(element: ?ReactElement): string { - if (element == null) { - return '#empty'; - } else if (typeof element === 'string' || typeof element === 'number') { - return '#text'; - } else if (typeof element.type === 'string') { - return element.type; - } else { - return element.type.displayName || element.type.name || 'Unknown'; - } -} - -function describeID(id: DebugID): string { - var name = ReactComponentTreeHook.getDisplayName(id); - var element = ReactComponentTreeHook.getElement(id); - var ownerID = ReactComponentTreeHook.getOwnerID(id); - var ownerName; - if (ownerID) { - ownerName = ReactComponentTreeHook.getDisplayName(ownerID); - } - warning( - element, - 'ReactComponentTreeHook: Missing React element for debugID %s when ' + - 'building stack', - id - ); - return describeComponentFrame(name, element && element._source, ownerName); -} - function describeFiber(fiber : Fiber) : string { switch (fiber.tag) { case IndeterminateComponent: @@ -219,160 +56,6 @@ function describeFiber(fiber : Fiber) : string { } var ReactComponentTreeHook = { - onSetChildren(id: DebugID, nextChildIDs: Array): void { - var item = getItem(id); - invariant(item, 'Item must have been set'); - item.childIDs = nextChildIDs; - - for (var i = 0; i < nextChildIDs.length; i++) { - var nextChildID = nextChildIDs[i]; - var nextChild = getItem(nextChildID); - invariant( - nextChild, - 'Expected hook events to fire for the child ' + - 'before its parent includes it in onSetChildren().' - ); - invariant( - nextChild.childIDs != null || - typeof nextChild.element !== 'object' || - nextChild.element == null, - 'Expected onSetChildren() to fire for a container child ' + - 'before its parent includes it in onSetChildren().' - ); - invariant( - nextChild.isMounted, - 'Expected onMountComponent() to fire for the child ' + - 'before its parent includes it in onSetChildren().' - ); - if (nextChild.parentID == null) { - nextChild.parentID = id; - // TODO: This shouldn't be necessary but mounting a new root during in - // componentWillMount currently causes not-yet-mounted components to - // be purged from our tree data so their parent id is missing. - } - invariant( - nextChild.parentID === id, - 'Expected onBeforeMountComponent() parent and onSetChildren() to ' + - 'be consistent (%s has parents %s and %s).', - nextChildID, - nextChild.parentID, - id - ); - } - }, - - onBeforeMountComponent(id: DebugID, element: ReactElement, parentID: DebugID): void { - var item = { - element, - parentID, - text: null, - childIDs: [], - isMounted: false, - updateCount: 0, - }; - setItem(id, item); - }, - - onBeforeUpdateComponent(id: DebugID, element: ReactElement): void { - var item = getItem(id); - if (!item || !item.isMounted) { - // We may end up here as a result of setState() in componentWillUnmount(). - // In this case, ignore the element. - return; - } - item.element = element; - }, - - onMountComponent(id: DebugID): void { - var item = getItem(id); - invariant(item, 'Item must have been set'); - item.isMounted = true; - var isRoot = item.parentID === 0; - if (isRoot) { - addRoot(id); - } - }, - - onUpdateComponent(id: DebugID): void { - var item = getItem(id); - if (!item || !item.isMounted) { - // We may end up here as a result of setState() in componentWillUnmount(). - // In this case, ignore the element. - return; - } - item.updateCount++; - }, - - onUnmountComponent(id: DebugID): void { - var item = getItem(id); - if (item) { - // We need to check if it exists. - // `item` might not exist if it is inside an error boundary, and a sibling - // error boundary child threw while mounting. Then this instance never - // got a chance to mount, but it still gets an unmounting event during - // the error boundary cleanup. - item.isMounted = false; - var isRoot = item.parentID === 0; - if (isRoot) { - removeRoot(id); - } - } - unmountedIDs.push(id); - }, - - purgeUnmountedComponents(): void { - if (ReactComponentTreeHook._preventPurging) { - // Should only be used for testing. - return; - } - - for (var i = 0; i < unmountedIDs.length; i++) { - var id = unmountedIDs[i]; - purgeDeep(id); - } - unmountedIDs.length = 0; - }, - - isMounted(id: DebugID): boolean { - var item = getItem(id); - return item ? item.isMounted : false; - }, - - getCurrentStackAddendum(topElement: ?ReactElement): string { - var info = ''; - if (topElement) { - var name = getDisplayName(topElement); - var owner = topElement._owner; - info += describeComponentFrame( - name, - topElement._source, - owner && getComponentName(owner) - ); - } - - var currentOwner = ReactCurrentOwner.current; - if (currentOwner) { - if (typeof currentOwner.tag === 'number') { - const workInProgress = ((currentOwner : any) : Fiber); - // Safe because if current owner exists, we are reconciling, - // and it is guaranteed to be the work-in-progress version. - info += ReactComponentTreeHook.getStackAddendumByWorkInProgressFiber(workInProgress); - } else if (typeof currentOwner._debugID === 'number') { - info += ReactComponentTreeHook.getStackAddendumByID(currentOwner._debugID); - } - } - return info; - }, - - getStackAddendumByID(id: ?DebugID): string { - var info = ''; - while (id) { - info += describeID(id); - id = ReactComponentTreeHook.getParentID(id); - } - return info; - }, - // This function can only be called with a work-in-progress fiber and // only during begin or complete phase. Do not call it under any other // circumstances. @@ -386,63 +69,392 @@ var ReactComponentTreeHook = { } while (node); return info; }, +}; - getChildIDs(id: DebugID): Array { - var item = getItem(id); - return item ? item.childIDs : []; - }, +if (__DEV__) { + var ReactCurrentOwner = require('ReactCurrentOwner'); + var invariant = require('fbjs/lib/invariant'); + var warning = require('fbjs/lib/warning'); - getDisplayName(id: DebugID): ?string { - var element = ReactComponentTreeHook.getElement(id); - if (!element) { - return null; + var isNative = function(fn) { + // Based on isNative() from Lodash + var funcToString = Function.prototype.toString; + var hasOwnProperty = Object.prototype.hasOwnProperty; + var reIsNative = RegExp('^' + funcToString + // Take an example native function source for comparison + .call(hasOwnProperty) + // Strip regex characters so we can use it for regex + .replace(/[\\^$.*+?()[\]{}|]/g, '\\$&') + // Remove hasOwnProperty from the template to make it generic + .replace( + /hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, + '$1.*?' + ) + '$' + ); + try { + var source = funcToString.call(fn); + return reIsNative.test(source); + } catch (err) { + return false; } - return getDisplayName(element); - }, + }; - getElement(id: DebugID): ?ReactElement { + var canUseCollections = ( + // Array.from + typeof Array.from === 'function' && + // Map + typeof Map === 'function' && + isNative(Map) && + // Map.prototype.keys + Map.prototype != null && + typeof Map.prototype.keys === 'function' && + isNative(Map.prototype.keys) && + // Set + typeof Set === 'function' && + isNative(Set) && + // Set.prototype.keys + Set.prototype != null && + typeof Set.prototype.keys === 'function' && + isNative(Set.prototype.keys) + ); + + var setItem; + var getItem; + var removeItem; + var getItemIDs; + var addRoot; + var removeRoot; + var getRootIDs; + + if (canUseCollections) { + var itemMap = new Map(); + var rootIDSet = new Set(); + + setItem = function(id, item) { + itemMap.set(id, item); + }; + getItem = function(id) { + return itemMap.get(id); + }; + removeItem = function(id) { + itemMap.delete(id); + }; + getItemIDs = function() { + return Array.from(itemMap.keys()); + }; + + addRoot = function(id) { + rootIDSet.add(id); + }; + removeRoot = function(id) { + rootIDSet.delete(id); + }; + getRootIDs = function() { + return Array.from(rootIDSet.keys()); + }; + + } else { + var itemByKey = {}; + var rootByKey = {}; + + // Use non-numeric keys to prevent V8 performance issues: + // https://github.com/facebook/react/pull/7232 + var getKeyFromID = function(id: DebugID): string { + return '.' + id; + }; + var getIDFromKey = function(key: string): DebugID { + return parseInt(key.substr(1), 10); + }; + + setItem = function(id, item) { + var key = getKeyFromID(id); + itemByKey[key] = item; + }; + getItem = function(id) { + var key = getKeyFromID(id); + return itemByKey[key]; + }; + removeItem = function(id) { + var key = getKeyFromID(id); + delete itemByKey[key]; + }; + getItemIDs = function() { + return Object.keys(itemByKey).map(getIDFromKey); + }; + + addRoot = function(id) { + var key = getKeyFromID(id); + rootByKey[key] = true; + }; + removeRoot = function(id) { + var key = getKeyFromID(id); + delete rootByKey[key]; + }; + getRootIDs = function() { + return Object.keys(rootByKey).map(getIDFromKey); + }; + } + + var unmountedIDs: Array = []; + + var purgeDeep = function(id) { var item = getItem(id); - return item ? item.element : null; - }, + if (item) { + var {childIDs} = item; + removeItem(id); + childIDs.forEach(purgeDeep); + } + }; - getOwnerID(id: DebugID): ?DebugID { - var element = ReactComponentTreeHook.getElement(id); + var getDisplayName = function(element: ?ReactElement): string { + if (element == null) { + return '#empty'; + } else if (typeof element === 'string' || typeof element === 'number') { + return '#text'; + } else if (typeof element.type === 'string') { + return element.type; + } else { + return element.type.displayName || element.type.name || 'Unknown'; + } + }; + + var describeID = function(id: DebugID): string { + var name = getDisplayName((id: any)); + var element = getElement(id); + var ownerID: any = getOwnerID(id); + var ownerName; + if (ownerID) { + ownerName = getDisplayName(ownerID); + } + warning( + element, + 'ReactComponentTreeHook: Missing React element for debugID %s when ' + + 'building stack', + id + ); + return describeComponentFrame(name, element && element._source, ownerName); + }; + + var getOwnerID = function(id: DebugID): ?DebugID { + var element = getElement(id); if (!element || !element._owner) { return null; } return element._owner._debugID; - }, + }; - getParentID(id: DebugID): ?DebugID { + var getElement = function(id: DebugID): ?ReactElement { + var item = getItem(id); + return item ? item.element : null; + }; + + var getParentID = function(id: DebugID): ?DebugID { var item = getItem(id); return item ? item.parentID : null; - }, - - getSource(id: DebugID): ?Source { - var item = getItem(id); - var element = item ? item.element : null; - var source = element != null ? element._source : null; - return source; - }, - - getText(id: DebugID): ?string { - var element = ReactComponentTreeHook.getElement(id); - if (typeof element === 'string') { - return element; - } else if (typeof element === 'number') { - return '' + element; - } else { - return null; + }; + + var getStackAddendumByID = function(id: ?DebugID): string { + var info = ''; + while (id) { + info += describeID(id); + id = getParentID(id); } - }, + return info; + }; - getUpdateCount(id: DebugID): number { - var item = getItem(id); - return item ? item.updateCount : 0; - }, + ReactComponentTreeHook = Object.assign({}, ReactComponentTreeHook, { + onSetChildren(id: DebugID, nextChildIDs: Array): void { + var item = getItem(id); + invariant(item, 'Item must have been set'); + item.childIDs = nextChildIDs; - getRootIDs, - getRegisteredIDs: getItemIDs, -}; + for (var i = 0; i < nextChildIDs.length; i++) { + var nextChildID = nextChildIDs[i]; + var nextChild = getItem(nextChildID); + invariant( + nextChild, + 'Expected hook events to fire for the child ' + + 'before its parent includes it in onSetChildren().' + ); + invariant( + nextChild.childIDs != null || + typeof nextChild.element !== 'object' || + nextChild.element == null, + 'Expected onSetChildren() to fire for a container child ' + + 'before its parent includes it in onSetChildren().' + ); + invariant( + nextChild.isMounted, + 'Expected onMountComponent() to fire for the child ' + + 'before its parent includes it in onSetChildren().' + ); + if (nextChild.parentID == null) { + nextChild.parentID = id; + // TODO: This shouldn't be necessary but mounting a new root during in + // componentWillMount currently causes not-yet-mounted components to + // be purged from our tree data so their parent id is missing. + } + invariant( + nextChild.parentID === id, + 'Expected onBeforeMountComponent() parent and onSetChildren() to ' + + 'be consistent (%s has parents %s and %s).', + nextChildID, + nextChild.parentID, + id + ); + } + }, + + onBeforeMountComponent(id: DebugID, element: ReactElement, parentID: DebugID): void { + var item = { + element, + parentID, + text: null, + childIDs: [], + isMounted: false, + updateCount: 0, + }; + setItem(id, item); + }, + + onBeforeUpdateComponent(id: DebugID, element: ReactElement): void { + var item = getItem(id); + if (!item || !item.isMounted) { + // We may end up here as a result of setState() in componentWillUnmount(). + // In this case, ignore the element. + return; + } + item.element = element; + }, + + onMountComponent(id: DebugID): void { + var item = getItem(id); + invariant(item, 'Item must have been set'); + item.isMounted = true; + var isRoot = item.parentID === 0; + if (isRoot) { + addRoot(id); + } + }, + + onUpdateComponent(id: DebugID): void { + var item = getItem(id); + if (!item || !item.isMounted) { + // We may end up here as a result of setState() in componentWillUnmount(). + // In this case, ignore the element. + return; + } + item.updateCount++; + }, + + onUnmountComponent(id: DebugID): void { + var item = getItem(id); + if (item) { + // We need to check if it exists. + // `item` might not exist if it is inside an error boundary, and a sibling + // error boundary child threw while mounting. Then this instance never + // got a chance to mount, but it still gets an unmounting event during + // the error boundary cleanup. + item.isMounted = false; + var isRoot = item.parentID === 0; + if (isRoot) { + removeRoot(id); + } + } + unmountedIDs.push(id); + }, + + purgeUnmountedComponents(): void { + if (ReactComponentTreeHook._preventPurging) { + // Should only be used for testing. + return; + } + + for (var i = 0; i < unmountedIDs.length; i++) { + var id = unmountedIDs[i]; + purgeDeep(id); + } + unmountedIDs.length = 0; + }, + + isMounted(id: DebugID): boolean { + var item = getItem(id); + return item ? item.isMounted : false; + }, + + getCurrentStackAddendum(topElement: ?ReactElement): string { + var info = ''; + if (topElement) { + var name = getDisplayName(topElement); + var owner = topElement._owner; + info += describeComponentFrame( + name, + topElement._source, + owner && getComponentName(owner) + ); + } + + var currentOwner = ReactCurrentOwner.current; + if (currentOwner) { + if (typeof currentOwner.tag === 'number') { + const workInProgress = ((currentOwner : any) : Fiber); + // Safe because if current owner exists, we are reconciling, + // and it is guaranteed to be the work-in-progress version. + info += ReactComponentTreeHook.getStackAddendumByWorkInProgressFiber(workInProgress); + } else if (typeof currentOwner._debugID === 'number') { + info += getStackAddendumByID(currentOwner._debugID); + } + } + return info; + }, + + getStackAddendumByID, + + getChildIDs(id: DebugID): Array { + var item = getItem(id); + return item ? item.childIDs : []; + }, + + getDisplayName(id: DebugID): ?string { + var element = getElement(id); + if (!element) { + return null; + } + return getDisplayName(element); + }, + + getElement, + + getOwnerID, + + getParentID, + + getSource(id: DebugID): ?Source { + var item = getItem(id); + var element = item ? item.element : null; + var source = element != null ? element._source : null; + return source; + }, + + getText(id: DebugID): ?string { + var element = getElement(id); + if (typeof element === 'string') { + return element; + } else if (typeof element === 'number') { + return '' + element; + } else { + return null; + } + }, + + getUpdateCount(id: DebugID): number { + var item = getItem(id); + return item ? item.updateCount : 0; + }, + + getRootIDs, + getRegisteredIDs: getItemIDs, + }); +} module.exports = ReactComponentTreeHook; diff --git a/src/renderers/shared/ReactDebugTool.js b/src/renderers/shared/ReactDebugTool.js index 6a3781f947..bae00db165 100644 --- a/src/renderers/shared/ReactDebugTool.js +++ b/src/renderers/shared/ReactDebugTool.js @@ -108,22 +108,22 @@ if (__DEV__) { var lifeCycleTimerHasWarned = false; const clearHistory = function() { - ReactComponentTreeHook.purgeUnmountedComponents(); + (ReactComponentTreeHook: any).purgeUnmountedComponents(); ReactHostOperationHistoryHook.clearHistory(); }; const getTreeSnapshot = function(registeredIDs) { return registeredIDs.reduce((tree, id) => { - var ownerID = ReactComponentTreeHook.getOwnerID(id); - var parentID = ReactComponentTreeHook.getParentID(id); + var ownerID = (ReactComponentTreeHook: any).getOwnerID(id); + var parentID = (ReactComponentTreeHook: any).getParentID(id); tree[id] = { - displayName: ReactComponentTreeHook.getDisplayName(id), - text: ReactComponentTreeHook.getText(id), - updateCount: ReactComponentTreeHook.getUpdateCount(id), - childIDs: ReactComponentTreeHook.getChildIDs(id), + displayName: (ReactComponentTreeHook: any).getDisplayName(id), + text: (ReactComponentTreeHook: any).getText(id), + updateCount: (ReactComponentTreeHook: any).getUpdateCount(id), + childIDs: (ReactComponentTreeHook: any).getChildIDs(id), // Text nodes don't have owners but this is close enough. ownerID: ownerID || - parentID && ReactComponentTreeHook.getOwnerID(parentID) || + parentID && (ReactComponentTreeHook: any).getOwnerID(parentID) || 0, parentID, }; @@ -144,7 +144,7 @@ if (__DEV__) { } if (previousMeasurements.length || previousOperations.length) { - var registeredIDs = ReactComponentTreeHook.getRegisteredIDs(); + var registeredIDs = (ReactComponentTreeHook: any).getRegisteredIDs(); flushHistory.push({ duration: performanceNow() - previousStartTime, measurements: previousMeasurements || [], @@ -253,7 +253,7 @@ if (__DEV__) { if (!isProfiling || !canUsePerformanceMeasure) { return false; } - var element = ReactComponentTreeHook.getElement(debugID); + var element = (ReactComponentTreeHook: any).getElement(debugID); if (element == null || typeof element !== 'object') { return false; } @@ -280,7 +280,7 @@ if (__DEV__) { } var markName = `${debugID}::${markType}`; - var displayName = ReactComponentTreeHook.getDisplayName(debugID) || 'Unknown'; + var displayName = (ReactComponentTreeHook: any).getDisplayName(debugID) || 'Unknown'; // Chrome has an issue of dropping markers recorded too fast: // https://bugs.chromium.org/p/chromium/issues/detail?id=640652 diff --git a/src/renderers/shared/stack/reconciler/ReactRef.js b/src/renderers/shared/stack/reconciler/ReactRef.js index 120c6dddbd..1a088e32da 100644 --- a/src/renderers/shared/stack/reconciler/ReactRef.js +++ b/src/renderers/shared/stack/reconciler/ReactRef.js @@ -53,7 +53,7 @@ function attachRef(ref, component, owner) { 'Stateless function components cannot be given refs. ' + 'Attempts to access this ref will fail.%s%s', info, - ReactComponentTreeHook.getStackAddendumByID(component._debugID) + (ReactComponentTreeHook: any).getStackAddendumByID(component._debugID) ); } } diff --git a/src/shared/utils/flattenChildren.js b/src/shared/utils/flattenChildren.js index d07630580f..7bc7d7c94a 100644 --- a/src/shared/utils/flattenChildren.js +++ b/src/shared/utils/flattenChildren.js @@ -58,7 +58,7 @@ function flattenSingleChildIntoContext( '`%s`. Child keys must be unique; when two children share a key, only ' + 'the first child will be used.%s', KeyEscapeUtils.unescape(name), - ReactComponentTreeHook.getStackAddendumByID(selfDebugID) + (ReactComponentTreeHook: any).getStackAddendumByID(selfDebugID) ); } } From e8f3f92a88ac90ba4c95c8fae1b563acf29d959e Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 2 Mar 2017 17:53:09 +0000 Subject: [PATCH 04/32] fixed flow issues and bug resulting in failing tests --- .../classic/element/ReactDebugCurrentFrame.js | 17 +++-- .../hooks/ReactComponentTreeHook.js | 64 +++++++++++++------ src/renderers/shared/ReactDebugTool.js | 55 ++++++++++------ .../shared/stack/reconciler/ReactRef.js | 6 +- src/shared/utils/flattenChildren.js | 4 +- 5 files changed, 98 insertions(+), 48 deletions(-) diff --git a/src/isomorphic/classic/element/ReactDebugCurrentFrame.js b/src/isomorphic/classic/element/ReactDebugCurrentFrame.js index 9cfbab11c4..7a36e4f257 100644 --- a/src/isomorphic/classic/element/ReactDebugCurrentFrame.js +++ b/src/isomorphic/classic/element/ReactDebugCurrentFrame.js @@ -14,11 +14,16 @@ import type { Fiber } from 'ReactFiber'; import type { DebugID } from 'ReactInstanceType'; +import type { ComponentTreeHookType } from '../../hooks/ReactComponentTreeHook'; const ReactDebugCurrentFrame = {}; if (__DEV__) { - var ReactComponentTreeHook = require('ReactComponentTreeHook'); + const { + getStackAddendumByID, + getStackAddendumByWorkInProgressFiber, + getCurrentStackAddendum, + }: ComponentTreeHookType = require('ReactComponentTreeHook'); // Component that is being worked on ReactDebugCurrentFrame.current = (null : Fiber | DebugID | null); @@ -33,16 +38,20 @@ if (__DEV__) { if (typeof current === 'number') { // DebugID from Stack. const debugID = current; - stack = (ReactComponentTreeHook: any).getStackAddendumByID(debugID); + if (getStackAddendumByID) { + stack = getStackAddendumByID(debugID); + } } else if (typeof current.tag === 'number') { // This is a Fiber. // The stack will only be correct if this is a work in progress // version and we're calling it during reconciliation. const workInProgress = current; - stack = ReactComponentTreeHook.getStackAddendumByWorkInProgressFiber(workInProgress); + stack = getStackAddendumByWorkInProgressFiber(workInProgress); } } else if (element !== null) { - stack = (ReactComponentTreeHook: any).getCurrentStackAddendum(element); + if (getCurrentStackAddendum) { + stack = getCurrentStackAddendum(element); + } } return stack; }; diff --git a/src/isomorphic/hooks/ReactComponentTreeHook.js b/src/isomorphic/hooks/ReactComponentTreeHook.js index 40c9a50279..a268651098 100644 --- a/src/isomorphic/hooks/ReactComponentTreeHook.js +++ b/src/isomorphic/hooks/ReactComponentTreeHook.js @@ -55,7 +55,21 @@ function describeFiber(fiber : Fiber) : string { } } -var ReactComponentTreeHook = { +export type ComponentTreeHookType = { + getStackAddendumByWorkInProgressFiber: (Fiber) => any, + getStackAddendumByID?: () => any, + getCurrentStackAddendum?: () => any, + purgeUnmountedComponents?: () => any, + getOwnerID?: (DebugID) => any, + getParentID?: (DebugID) => any, + getDisplayName?: (DebugID) => any, + getText?: (DebugID) => any, + getUpdateCount?: (DebugID) => any, + getChildIDs?: (DebugID) => any, + getRegisteredIDs?: () => any, +}; + +var ReactComponentTreeHook: ComponentTreeHookType = { // This function can only be called with a work-in-progress fiber and // only during begin or complete phase. Do not call it under any other // circumstances. @@ -195,9 +209,9 @@ if (__DEV__) { }; } - var unmountedIDs: Array = []; + const unmountedIDs: Array = []; - var purgeDeep = function(id) { + const purgeDeep = function(id) { var item = getItem(id); if (item) { var {childIDs} = item; @@ -206,7 +220,7 @@ if (__DEV__) { } }; - var getDisplayName = function(element: ?ReactElement): string { + const getDisplayNameFromElement = function(element: ?ReactElement): string { if (element == null) { return '#empty'; } else if (typeof element === 'string' || typeof element === 'number') { @@ -218,10 +232,26 @@ if (__DEV__) { } }; - var describeID = function(id: DebugID): string { - var name = getDisplayName((id: any)); + const getDisplayName = function(id: DebugID): ?string { var element = getElement(id); - var ownerID: any = getOwnerID(id); + if (!element) { + return null; + } + return getDisplayNameFromElement(element); + }; + + const getOwnerID = function(id: DebugID): ?DebugID { + var element = getElement(id); + if (!element || !element._owner) { + return null; + } + return element._owner._debugID; + }; + + const describeID = function(id: DebugID): string { + var name = getDisplayName(id); + var element = getElement(id); + var ownerID = getOwnerID(id); var ownerName; if (ownerID) { ownerName = getDisplayName(ownerID); @@ -235,25 +265,17 @@ if (__DEV__) { return describeComponentFrame(name, element && element._source, ownerName); }; - var getOwnerID = function(id: DebugID): ?DebugID { - var element = getElement(id); - if (!element || !element._owner) { - return null; - } - return element._owner._debugID; - }; - - var getElement = function(id: DebugID): ?ReactElement { + const getElement = function(id: DebugID): ?ReactElement { var item = getItem(id); return item ? item.element : null; }; - var getParentID = function(id: DebugID): ?DebugID { + const getParentID = function(id: DebugID): ?DebugID { var item = getItem(id); return item ? item.parentID : null; }; - - var getStackAddendumByID = function(id: ?DebugID): string { + + const getStackAddendumByID = function(id: ?DebugID): string { var info = ''; while (id) { info += describeID(id); @@ -385,7 +407,7 @@ if (__DEV__) { getCurrentStackAddendum(topElement: ?ReactElement): string { var info = ''; if (topElement) { - var name = getDisplayName(topElement); + var name = getDisplayNameFromElement(topElement); var owner = topElement._owner; info += describeComponentFrame( name, @@ -420,7 +442,7 @@ if (__DEV__) { if (!element) { return null; } - return getDisplayName(element); + return getDisplayNameFromElement(element); }, getElement, diff --git a/src/renderers/shared/ReactDebugTool.js b/src/renderers/shared/ReactDebugTool.js index bae00db165..1d07610ade 100644 --- a/src/renderers/shared/ReactDebugTool.js +++ b/src/renderers/shared/ReactDebugTool.js @@ -14,7 +14,6 @@ var ReactInvalidSetStateWarningHook = require('ReactInvalidSetStateWarningHook'); var ReactHostOperationHistoryHook = require('ReactHostOperationHistoryHook'); -var ReactComponentTreeHook = require('react/lib/ReactComponentTreeHook'); var ExecutionEnvironment = require('fbjs/lib/ExecutionEnvironment'); var performanceNow = require('fbjs/lib/performanceNow'); @@ -67,8 +66,19 @@ export type FlushHistory = Array; var ReactDebugTool = ((null: any): typeof ReactDebugTool); if (__DEV__) { - var hooks = []; - var didHookThrowForEvent = {}; + const hooks = []; + const didHookThrowForEvent = {}; + const ReactComponentTreeHook = require('react/lib/ReactComponentTreeHook'); + const { + purgeUnmountedComponents, + getOwnerID, + getParentID, + getDisplayName, + getText, + getUpdateCount, + getChildIDs, + getRegisteredIDs, + } = ReactComponentTreeHook; const callHook = function(event, fn, context, arg1, arg2, arg3, arg4, arg5) { try { @@ -108,22 +118,24 @@ if (__DEV__) { var lifeCycleTimerHasWarned = false; const clearHistory = function() { - (ReactComponentTreeHook: any).purgeUnmountedComponents(); + if (purgeUnmountedComponents) { + purgeUnmountedComponents(); + } ReactHostOperationHistoryHook.clearHistory(); }; const getTreeSnapshot = function(registeredIDs) { - return registeredIDs.reduce((tree, id) => { - var ownerID = (ReactComponentTreeHook: any).getOwnerID(id); - var parentID = (ReactComponentTreeHook: any).getParentID(id); + return registeredIDs && registeredIDs.reduce((tree, id) => { + var ownerID = getOwnerID && getOwnerID(id); + var parentID = getParentID && getParentID(id); tree[id] = { - displayName: (ReactComponentTreeHook: any).getDisplayName(id), - text: (ReactComponentTreeHook: any).getText(id), - updateCount: (ReactComponentTreeHook: any).getUpdateCount(id), - childIDs: (ReactComponentTreeHook: any).getChildIDs(id), + displayName: getDisplayName && getDisplayName(id), + text: getText && getText(id), + updateCount: getUpdateCount && getUpdateCount(id), + childIDs: getChildIDs && getChildIDs(id), // Text nodes don't have owners but this is close enough. ownerID: ownerID || - parentID && (ReactComponentTreeHook: any).getOwnerID(parentID) || + parentID && getOwnerID && getOwnerID(parentID) || 0, parentID, }; @@ -144,13 +156,16 @@ if (__DEV__) { } if (previousMeasurements.length || previousOperations.length) { - var registeredIDs = (ReactComponentTreeHook: any).getRegisteredIDs(); - flushHistory.push({ - duration: performanceNow() - previousStartTime, - measurements: previousMeasurements || [], - operations: previousOperations || [], - treeSnapshot: getTreeSnapshot(registeredIDs), - }); + var registeredIDs = getRegisteredIDs && getRegisteredIDs(); + + if (registeredIDs) { + flushHistory.push({ + duration: performanceNow() - previousStartTime, + measurements: previousMeasurements || [], + operations: previousOperations || [], + treeSnapshot: getTreeSnapshot(registeredIDs), + }); + } } clearHistory(); @@ -280,7 +295,7 @@ if (__DEV__) { } var markName = `${debugID}::${markType}`; - var displayName = (ReactComponentTreeHook: any).getDisplayName(debugID) || 'Unknown'; + var displayName = getDisplayName && getDisplayName(debugID) || 'Unknown'; // Chrome has an issue of dropping markers recorded too fast: // https://bugs.chromium.org/p/chromium/issues/detail?id=640652 diff --git a/src/renderers/shared/stack/reconciler/ReactRef.js b/src/renderers/shared/stack/reconciler/ReactRef.js index 1a088e32da..155dab6c80 100644 --- a/src/renderers/shared/stack/reconciler/ReactRef.js +++ b/src/renderers/shared/stack/reconciler/ReactRef.js @@ -21,7 +21,9 @@ var ReactRef = {}; if (__DEV__) { var ReactCompositeComponentTypes = require('ReactCompositeComponentTypes'); - var ReactComponentTreeHook = require('react/lib/ReactComponentTreeHook'); + var { + getStackAddendumByID, + } = require('react/lib/ReactComponentTreeHook'); var warning = require('fbjs/lib/warning'); var warnedAboutStatelessRefs = {}; @@ -53,7 +55,7 @@ function attachRef(ref, component, owner) { 'Stateless function components cannot be given refs. ' + 'Attempts to access this ref will fail.%s%s', info, - (ReactComponentTreeHook: any).getStackAddendumByID(component._debugID) + getStackAddendumByID && getStackAddendumByID(component._debugID) ); } } diff --git a/src/shared/utils/flattenChildren.js b/src/shared/utils/flattenChildren.js index 7bc7d7c94a..f2992becdf 100644 --- a/src/shared/utils/flattenChildren.js +++ b/src/shared/utils/flattenChildren.js @@ -51,6 +51,8 @@ function flattenSingleChildIntoContext( if (!ReactComponentTreeHook) { ReactComponentTreeHook = require('react/lib/ReactComponentTreeHook'); } + const { getStackAddendumByID } = ReactComponentTreeHook; + if (!keyUnique) { warning( false, @@ -58,7 +60,7 @@ function flattenSingleChildIntoContext( '`%s`. Child keys must be unique; when two children share a key, only ' + 'the first child will be used.%s', KeyEscapeUtils.unescape(name), - (ReactComponentTreeHook: any).getStackAddendumByID(selfDebugID) + getStackAddendumByID && getStackAddendumByID(selfDebugID) ); } } From da9d91829e094fef634ce928b949512db3d07cdf Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 2 Mar 2017 21:50:02 +0000 Subject: [PATCH 05/32] WIP - not sure how to get the Flow type working properly --- flow/environment.js | 20 ------ flow/react-native-host-hooks.js | 64 ------------------- .../classic/element/ReactDebugCurrentFrame.js | 14 ++-- .../hooks/ReactComponentTreeHook.js | 24 ++++--- 4 files changed, 20 insertions(+), 102 deletions(-) delete mode 100644 flow/environment.js delete mode 100644 flow/react-native-host-hooks.js diff --git a/flow/environment.js b/flow/environment.js deleted file mode 100644 index fa020bf8aa..0000000000 --- a/flow/environment.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright (c) 2015-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. - * - * @flow - */ - -/* eslint-disable */ - -declare var __REACT_DEVTOOLS_GLOBAL_HOOK__: any; /*?{ - inject: ?((stuff: Object) => void) -};*/ - -// temporary patches for React.Component and React.Element -declare var ReactComponent: typeof React$Component; -declare var ReactElement: typeof React$Element; diff --git a/flow/react-native-host-hooks.js b/flow/react-native-host-hooks.js deleted file mode 100644 index 0f96747133..0000000000 --- a/flow/react-native-host-hooks.js +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Copyright (c) 2015-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. - * - * @flow - */ - -/* eslint-disable */ - -declare module 'deepDiffer' { - declare function exports(one: any, two: any): bool; -} -declare module 'deepFreezeAndThrowOnMutationInDev' { - declare function exports(obj : T) : T; -} -declare module 'flattenStyle' { } -declare module 'InitializeCore' { } -declare module 'RCTEventEmitter' { - declare function register() : void; -} -declare module 'TextInputState' { - declare function blurTextInput(object : any) : void; - declare function focusTextInput(object : any) : void; -} -declare module 'UIManager' { - declare var customBubblingEventTypes : Object; - declare var customDirectEventTypes : Object; - declare function createView( - reactTag : number, - viewName : string, - rootTag : number, - props : ?Object, - ) : void; - declare function manageChildren( - containerTag : number, - moveFromIndices : Array, - moveToIndices : Array, - addChildReactTags : Array, - addAtIndices : Array, - removeAtIndices : Array - ) : void; - declare function measure() : void; - declare function measureInWindow() : void; - declare function measureLayout() : void; - declare function removeRootView() : void; - declare function removeSubviewsFromContainerWithID() : void; - declare function replaceExistingNonRootView() : void; - declare function setChildren( - containerTag : number, - reactTags : Array, - ) : void; - declare function updateView( - reactTag : number, - viewName : string, - props : ?Object, - ) : void; -} -declare module 'View' { - declare var exports : typeof ReactComponent; -} diff --git a/src/isomorphic/classic/element/ReactDebugCurrentFrame.js b/src/isomorphic/classic/element/ReactDebugCurrentFrame.js index 7a36e4f257..eb1d1ecaa4 100644 --- a/src/isomorphic/classic/element/ReactDebugCurrentFrame.js +++ b/src/isomorphic/classic/element/ReactDebugCurrentFrame.js @@ -14,16 +14,18 @@ import type { Fiber } from 'ReactFiber'; import type { DebugID } from 'ReactInstanceType'; -import type { ComponentTreeHookType } from '../../hooks/ReactComponentTreeHook'; +import type { ComponentTreeHookDevType } from '../../hooks/ReactComponentTreeHook'; const ReactDebugCurrentFrame = {}; if (__DEV__) { + // how do a state that ReactComponentTreeHook is using the ComponentTreeHookDevType type? + const ReactComponentTreeHook: ComponentTreeHookDevType = require('ReactComponentTreeHook'); const { getStackAddendumByID, getStackAddendumByWorkInProgressFiber, getCurrentStackAddendum, - }: ComponentTreeHookType = require('ReactComponentTreeHook'); + } = ReactComponentTreeHook; // Component that is being worked on ReactDebugCurrentFrame.current = (null : Fiber | DebugID | null); @@ -38,9 +40,7 @@ if (__DEV__) { if (typeof current === 'number') { // DebugID from Stack. const debugID = current; - if (getStackAddendumByID) { - stack = getStackAddendumByID(debugID); - } + stack = getStackAddendumByID(debugID); } else if (typeof current.tag === 'number') { // This is a Fiber. // The stack will only be correct if this is a work in progress @@ -49,9 +49,7 @@ if (__DEV__) { stack = getStackAddendumByWorkInProgressFiber(workInProgress); } } else if (element !== null) { - if (getCurrentStackAddendum) { - stack = getCurrentStackAddendum(element); - } + stack = getCurrentStackAddendum(element); } return stack; }; diff --git a/src/isomorphic/hooks/ReactComponentTreeHook.js b/src/isomorphic/hooks/ReactComponentTreeHook.js index a268651098..c7e2141b34 100644 --- a/src/isomorphic/hooks/ReactComponentTreeHook.js +++ b/src/isomorphic/hooks/ReactComponentTreeHook.js @@ -57,16 +57,20 @@ function describeFiber(fiber : Fiber) : string { export type ComponentTreeHookType = { getStackAddendumByWorkInProgressFiber: (Fiber) => any, - getStackAddendumByID?: () => any, - getCurrentStackAddendum?: () => any, - purgeUnmountedComponents?: () => any, - getOwnerID?: (DebugID) => any, - getParentID?: (DebugID) => any, - getDisplayName?: (DebugID) => any, - getText?: (DebugID) => any, - getUpdateCount?: (DebugID) => any, - getChildIDs?: (DebugID) => any, - getRegisteredIDs?: () => any, +}; + +export type ComponentTreeHookDevType = { + getStackAddendumByWorkInProgressFiber: (Fiber) => any, + getStackAddendumByID: () => any, + getCurrentStackAddendum: () => any, + purgeUnmountedComponents: () => any, + getOwnerID: (DebugID) => any, + getParentID: (DebugID) => any, + getDisplayName: (DebugID) => any, + getText: (DebugID) => any, + getUpdateCount: (DebugID) => any, + getChildIDs: (DebugID) => any, + getRegisteredIDs: () => any, }; var ReactComponentTreeHook: ComponentTreeHookType = { From 54fef501863eb2e53180eaaebdd10169dc4c3611 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 2 Mar 2017 21:52:54 +0000 Subject: [PATCH 06/32] re-added missing files --- flow/environment.js | 20 +++++++++++ flow/react-native-host-hooks.js | 64 +++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 flow/environment.js create mode 100644 flow/react-native-host-hooks.js diff --git a/flow/environment.js b/flow/environment.js new file mode 100644 index 0000000000..fa020bf8aa --- /dev/null +++ b/flow/environment.js @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2015-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. + * + * @flow + */ + +/* eslint-disable */ + +declare var __REACT_DEVTOOLS_GLOBAL_HOOK__: any; /*?{ + inject: ?((stuff: Object) => void) +};*/ + +// temporary patches for React.Component and React.Element +declare var ReactComponent: typeof React$Component; +declare var ReactElement: typeof React$Element; diff --git a/flow/react-native-host-hooks.js b/flow/react-native-host-hooks.js new file mode 100644 index 0000000000..0f96747133 --- /dev/null +++ b/flow/react-native-host-hooks.js @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2015-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. + * + * @flow + */ + +/* eslint-disable */ + +declare module 'deepDiffer' { + declare function exports(one: any, two: any): bool; +} +declare module 'deepFreezeAndThrowOnMutationInDev' { + declare function exports(obj : T) : T; +} +declare module 'flattenStyle' { } +declare module 'InitializeCore' { } +declare module 'RCTEventEmitter' { + declare function register() : void; +} +declare module 'TextInputState' { + declare function blurTextInput(object : any) : void; + declare function focusTextInput(object : any) : void; +} +declare module 'UIManager' { + declare var customBubblingEventTypes : Object; + declare var customDirectEventTypes : Object; + declare function createView( + reactTag : number, + viewName : string, + rootTag : number, + props : ?Object, + ) : void; + declare function manageChildren( + containerTag : number, + moveFromIndices : Array, + moveToIndices : Array, + addChildReactTags : Array, + addAtIndices : Array, + removeAtIndices : Array + ) : void; + declare function measure() : void; + declare function measureInWindow() : void; + declare function measureLayout() : void; + declare function removeRootView() : void; + declare function removeSubviewsFromContainerWithID() : void; + declare function replaceExistingNonRootView() : void; + declare function setChildren( + containerTag : number, + reactTags : Array, + ) : void; + declare function updateView( + reactTag : number, + viewName : string, + props : ?Object, + ) : void; +} +declare module 'View' { + declare var exports : typeof ReactComponent; +} From 1f7cbb63e81fcdf9c81aee86478703f603575a8f Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 2 Mar 2017 22:06:46 +0000 Subject: [PATCH 07/32] updated flow annotations --- .../classic/element/ReactDebugCurrentFrame.js | 4 +- .../hooks/ReactComponentTreeHook.js | 1 + src/renderers/shared/ReactDebugTool.js | 44 +++++++++---------- .../shared/stack/reconciler/ReactRef.js | 5 ++- src/shared/utils/flattenChildren.js | 6 ++- 5 files changed, 31 insertions(+), 29 deletions(-) diff --git a/src/isomorphic/classic/element/ReactDebugCurrentFrame.js b/src/isomorphic/classic/element/ReactDebugCurrentFrame.js index eb1d1ecaa4..e5e966b706 100644 --- a/src/isomorphic/classic/element/ReactDebugCurrentFrame.js +++ b/src/isomorphic/classic/element/ReactDebugCurrentFrame.js @@ -14,13 +14,13 @@ import type { Fiber } from 'ReactFiber'; import type { DebugID } from 'ReactInstanceType'; -import type { ComponentTreeHookDevType } from '../../hooks/ReactComponentTreeHook'; +import type { ComponentTreeHookDevType } from 'ReactComponentTreeHook'; const ReactDebugCurrentFrame = {}; if (__DEV__) { // how do a state that ReactComponentTreeHook is using the ComponentTreeHookDevType type? - const ReactComponentTreeHook: ComponentTreeHookDevType = require('ReactComponentTreeHook'); + const ReactComponentTreeHook: ComponentTreeHookDevType = (require('ReactComponentTreeHook'): any); const { getStackAddendumByID, getStackAddendumByWorkInProgressFiber, diff --git a/src/isomorphic/hooks/ReactComponentTreeHook.js b/src/isomorphic/hooks/ReactComponentTreeHook.js index c7e2141b34..5d8cdf616d 100644 --- a/src/isomorphic/hooks/ReactComponentTreeHook.js +++ b/src/isomorphic/hooks/ReactComponentTreeHook.js @@ -71,6 +71,7 @@ export type ComponentTreeHookDevType = { getUpdateCount: (DebugID) => any, getChildIDs: (DebugID) => any, getRegisteredIDs: () => any, + getElement: () => any, }; var ReactComponentTreeHook: ComponentTreeHookType = { diff --git a/src/renderers/shared/ReactDebugTool.js b/src/renderers/shared/ReactDebugTool.js index 1d07610ade..b0e91811e0 100644 --- a/src/renderers/shared/ReactDebugTool.js +++ b/src/renderers/shared/ReactDebugTool.js @@ -22,6 +22,7 @@ var warning = require('fbjs/lib/warning'); import type { ReactElement } from 'ReactElementType'; import type { DebugID } from 'ReactInstanceType'; import type { Operation } from 'ReactHostOperationHistoryHook'; +import type { ComponentTreeHookDevType } from 'ReactComponentTreeHook'; type Hook = any; @@ -68,7 +69,7 @@ var ReactDebugTool = ((null: any): typeof ReactDebugTool); if (__DEV__) { const hooks = []; const didHookThrowForEvent = {}; - const ReactComponentTreeHook = require('react/lib/ReactComponentTreeHook'); + const ReactComponentTreeHook: ComponentTreeHookDevType = (require('react/lib/ReactComponentTreeHook'): any); const { purgeUnmountedComponents, getOwnerID, @@ -78,6 +79,7 @@ if (__DEV__) { getUpdateCount, getChildIDs, getRegisteredIDs, + getElement, } = ReactComponentTreeHook; const callHook = function(event, fn, context, arg1, arg2, arg3, arg4, arg5) { @@ -118,24 +120,22 @@ if (__DEV__) { var lifeCycleTimerHasWarned = false; const clearHistory = function() { - if (purgeUnmountedComponents) { - purgeUnmountedComponents(); - } + purgeUnmountedComponents(); ReactHostOperationHistoryHook.clearHistory(); }; const getTreeSnapshot = function(registeredIDs) { - return registeredIDs && registeredIDs.reduce((tree, id) => { - var ownerID = getOwnerID && getOwnerID(id); - var parentID = getParentID && getParentID(id); + return registeredIDs.reduce((tree, id) => { + var ownerID = getOwnerID(id); + var parentID = getParentID(id); tree[id] = { - displayName: getDisplayName && getDisplayName(id), - text: getText && getText(id), - updateCount: getUpdateCount && getUpdateCount(id), - childIDs: getChildIDs && getChildIDs(id), + displayName: getDisplayName(id), + text: getText(id), + updateCount: getUpdateCount(id), + childIDs: getChildIDs(id), // Text nodes don't have owners but this is close enough. ownerID: ownerID || - parentID && getOwnerID && getOwnerID(parentID) || + parentID && getOwnerID(parentID) || 0, parentID, }; @@ -156,16 +156,14 @@ if (__DEV__) { } if (previousMeasurements.length || previousOperations.length) { - var registeredIDs = getRegisteredIDs && getRegisteredIDs(); + var registeredIDs = getRegisteredIDs(); - if (registeredIDs) { - flushHistory.push({ - duration: performanceNow() - previousStartTime, - measurements: previousMeasurements || [], - operations: previousOperations || [], - treeSnapshot: getTreeSnapshot(registeredIDs), - }); - } + flushHistory.push({ + duration: performanceNow() - previousStartTime, + measurements: previousMeasurements || [], + operations: previousOperations || [], + treeSnapshot: getTreeSnapshot(registeredIDs), + }); } clearHistory(); @@ -268,7 +266,7 @@ if (__DEV__) { if (!isProfiling || !canUsePerformanceMeasure) { return false; } - var element = (ReactComponentTreeHook: any).getElement(debugID); + var element = getElement(debugID); if (element == null || typeof element !== 'object') { return false; } @@ -295,7 +293,7 @@ if (__DEV__) { } var markName = `${debugID}::${markType}`; - var displayName = getDisplayName && getDisplayName(debugID) || 'Unknown'; + var displayName = getDisplayName(debugID) || 'Unknown'; // Chrome has an issue of dropping markers recorded too fast: // https://bugs.chromium.org/p/chromium/issues/detail?id=640652 diff --git a/src/renderers/shared/stack/reconciler/ReactRef.js b/src/renderers/shared/stack/reconciler/ReactRef.js index 155dab6c80..7f2e5c4567 100644 --- a/src/renderers/shared/stack/reconciler/ReactRef.js +++ b/src/renderers/shared/stack/reconciler/ReactRef.js @@ -16,6 +16,7 @@ var ReactOwner = require('ReactOwner'); import type { ReactInstance } from 'ReactInstanceType'; import type { ReactElement } from 'ReactElementType'; +import type { ComponentTreeHookDevType } from 'ReactComponentTreeHook'; var ReactRef = {}; @@ -23,7 +24,7 @@ if (__DEV__) { var ReactCompositeComponentTypes = require('ReactCompositeComponentTypes'); var { getStackAddendumByID, - } = require('react/lib/ReactComponentTreeHook'); + }: ComponentTreeHookDevType = (require('react/lib/ReactComponentTreeHook'): any); var warning = require('fbjs/lib/warning'); var warnedAboutStatelessRefs = {}; @@ -55,7 +56,7 @@ function attachRef(ref, component, owner) { 'Stateless function components cannot be given refs. ' + 'Attempts to access this ref will fail.%s%s', info, - getStackAddendumByID && getStackAddendumByID(component._debugID) + getStackAddendumByID(component._debugID) ); } } diff --git a/src/shared/utils/flattenChildren.js b/src/shared/utils/flattenChildren.js index f2992becdf..2a8165c829 100644 --- a/src/shared/utils/flattenChildren.js +++ b/src/shared/utils/flattenChildren.js @@ -12,6 +12,8 @@ 'use strict'; +import type { ComponentTreeHookDevType } from 'ReactComponentTreeHook'; + var KeyEscapeUtils = require('KeyEscapeUtils'); var traverseAllChildren = require('traverseAllChildren'); var warning = require('fbjs/lib/warning'); @@ -51,7 +53,7 @@ function flattenSingleChildIntoContext( if (!ReactComponentTreeHook) { ReactComponentTreeHook = require('react/lib/ReactComponentTreeHook'); } - const { getStackAddendumByID } = ReactComponentTreeHook; + const { getStackAddendumByID }: ComponentTreeHookDevType = (ReactComponentTreeHook: any); if (!keyUnique) { warning( @@ -60,7 +62,7 @@ function flattenSingleChildIntoContext( '`%s`. Child keys must be unique; when two children share a key, only ' + 'the first child will be used.%s', KeyEscapeUtils.unescape(name), - getStackAddendumByID && getStackAddendumByID(selfDebugID) + getStackAddendumByID(selfDebugID) ); } } From 9a1db4925968af23570e8e8ae84234b7f0eb452c Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 2 Mar 2017 22:08:15 +0000 Subject: [PATCH 08/32] removed comment --- src/isomorphic/classic/element/ReactDebugCurrentFrame.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/isomorphic/classic/element/ReactDebugCurrentFrame.js b/src/isomorphic/classic/element/ReactDebugCurrentFrame.js index e5e966b706..6bb331994d 100644 --- a/src/isomorphic/classic/element/ReactDebugCurrentFrame.js +++ b/src/isomorphic/classic/element/ReactDebugCurrentFrame.js @@ -19,13 +19,11 @@ import type { ComponentTreeHookDevType } from 'ReactComponentTreeHook'; const ReactDebugCurrentFrame = {}; if (__DEV__) { - // how do a state that ReactComponentTreeHook is using the ComponentTreeHookDevType type? - const ReactComponentTreeHook: ComponentTreeHookDevType = (require('ReactComponentTreeHook'): any); const { getStackAddendumByID, getStackAddendumByWorkInProgressFiber, getCurrentStackAddendum, - } = ReactComponentTreeHook; + }: ComponentTreeHookDevType = (require('ReactComponentTreeHook'): any);; // Component that is being worked on ReactDebugCurrentFrame.current = (null : Fiber | DebugID | null); From 397fd2b92ec8dc5b1eb8dca097592d376e6b40e1 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 2 Mar 2017 22:09:12 +0000 Subject: [PATCH 09/32] removed double semicolon --- src/isomorphic/classic/element/ReactDebugCurrentFrame.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/isomorphic/classic/element/ReactDebugCurrentFrame.js b/src/isomorphic/classic/element/ReactDebugCurrentFrame.js index 6bb331994d..9e90f13f9f 100644 --- a/src/isomorphic/classic/element/ReactDebugCurrentFrame.js +++ b/src/isomorphic/classic/element/ReactDebugCurrentFrame.js @@ -23,7 +23,7 @@ if (__DEV__) { getStackAddendumByID, getStackAddendumByWorkInProgressFiber, getCurrentStackAddendum, - }: ComponentTreeHookDevType = (require('ReactComponentTreeHook'): any);; + }: ComponentTreeHookDevType = (require('ReactComponentTreeHook'): any); // Component that is being worked on ReactDebugCurrentFrame.current = (null : Fiber | DebugID | null); From 91de17ec5212c4fc16c01c66db3645f52901c1f9 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 2 Mar 2017 23:06:32 +0000 Subject: [PATCH 10/32] added guards in place to prevent mixed prod+dev errors from occuring --- .../classic/element/ReactDebugCurrentFrame.js | 4 ++-- src/renderers/shared/ReactDebugTool.js | 24 +++++++++++-------- .../shared/stack/reconciler/ReactRef.js | 2 +- src/shared/utils/flattenChildren.js | 2 +- src/umd/ReactUMDEntry.js | 11 +-------- 5 files changed, 19 insertions(+), 24 deletions(-) diff --git a/src/isomorphic/classic/element/ReactDebugCurrentFrame.js b/src/isomorphic/classic/element/ReactDebugCurrentFrame.js index 9e90f13f9f..056a38bbcb 100644 --- a/src/isomorphic/classic/element/ReactDebugCurrentFrame.js +++ b/src/isomorphic/classic/element/ReactDebugCurrentFrame.js @@ -38,7 +38,7 @@ if (__DEV__) { if (typeof current === 'number') { // DebugID from Stack. const debugID = current; - stack = getStackAddendumByID(debugID); + stack = getStackAddendumByID && getStackAddendumByID(debugID); } else if (typeof current.tag === 'number') { // This is a Fiber. // The stack will only be correct if this is a work in progress @@ -47,7 +47,7 @@ if (__DEV__) { stack = getStackAddendumByWorkInProgressFiber(workInProgress); } } else if (element !== null) { - stack = getCurrentStackAddendum(element); + stack = getCurrentStackAddendum && getCurrentStackAddendum(element); } return stack; }; diff --git a/src/renderers/shared/ReactDebugTool.js b/src/renderers/shared/ReactDebugTool.js index b0e91811e0..b8a3ae7e6e 100644 --- a/src/renderers/shared/ReactDebugTool.js +++ b/src/renderers/shared/ReactDebugTool.js @@ -120,7 +120,9 @@ if (__DEV__) { var lifeCycleTimerHasWarned = false; const clearHistory = function() { - purgeUnmountedComponents(); + if (purgeUnmountedComponents) { + purgeUnmountedComponents(); + } ReactHostOperationHistoryHook.clearHistory(); }; @@ -156,14 +158,16 @@ if (__DEV__) { } if (previousMeasurements.length || previousOperations.length) { - var registeredIDs = getRegisteredIDs(); + if (getRegisteredIDs) { + var registeredIDs = getRegisteredIDs(); - flushHistory.push({ - duration: performanceNow() - previousStartTime, - measurements: previousMeasurements || [], - operations: previousOperations || [], - treeSnapshot: getTreeSnapshot(registeredIDs), - }); + flushHistory.push({ + duration: performanceNow() - previousStartTime, + measurements: previousMeasurements || [], + operations: previousOperations || [], + treeSnapshot: getTreeSnapshot(registeredIDs), + }); + } } clearHistory(); @@ -266,7 +270,7 @@ if (__DEV__) { if (!isProfiling || !canUsePerformanceMeasure) { return false; } - var element = getElement(debugID); + var element = getElement && getElement(debugID); if (element == null || typeof element !== 'object') { return false; } @@ -293,7 +297,7 @@ if (__DEV__) { } var markName = `${debugID}::${markType}`; - var displayName = getDisplayName(debugID) || 'Unknown'; + var displayName = getDisplayName && getDisplayName(debugID) || 'Unknown'; // Chrome has an issue of dropping markers recorded too fast: // https://bugs.chromium.org/p/chromium/issues/detail?id=640652 diff --git a/src/renderers/shared/stack/reconciler/ReactRef.js b/src/renderers/shared/stack/reconciler/ReactRef.js index 7f2e5c4567..6705ec9aa3 100644 --- a/src/renderers/shared/stack/reconciler/ReactRef.js +++ b/src/renderers/shared/stack/reconciler/ReactRef.js @@ -49,7 +49,7 @@ function attachRef(ref, component, owner) { if (element && element._source) { warningKey = element._source.fileName + ':' + element._source.lineNumber; } - if (!warnedAboutStatelessRefs[warningKey]) { + if (!warnedAboutStatelessRefs[warningKey] && getStackAddendumByID) { warnedAboutStatelessRefs[warningKey] = true; warning( false, diff --git a/src/shared/utils/flattenChildren.js b/src/shared/utils/flattenChildren.js index 2a8165c829..a5a3d85bb5 100644 --- a/src/shared/utils/flattenChildren.js +++ b/src/shared/utils/flattenChildren.js @@ -55,7 +55,7 @@ function flattenSingleChildIntoContext( } const { getStackAddendumByID }: ComponentTreeHookDevType = (ReactComponentTreeHook: any); - if (!keyUnique) { + if (!keyUnique && getStackAddendumByID) { warning( false, 'flattenChildren(...): Encountered two children with the same key, ' + diff --git a/src/umd/ReactUMDEntry.js b/src/umd/ReactUMDEntry.js index ab926354a6..8cacdceb35 100644 --- a/src/umd/ReactUMDEntry.js +++ b/src/umd/ReactUMDEntry.js @@ -17,17 +17,8 @@ var React = require('React'); var ReactUMDEntry = Object.assign({ __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: { ReactCurrentOwner: require('react/lib/ReactCurrentOwner'), + ReactComponentTreeHook: require('react/lib/ReactComponentTreeHook'), }, }, React); -if (__DEV__) { - Object.assign( - ReactUMDEntry.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED, - { - // ReactComponentTreeHook should not be included in production. - ReactComponentTreeHook: require('react/lib/ReactComponentTreeHook'), - } - ); -} - module.exports = ReactUMDEntry; From 673f514f810e91faf5bc3a35b07f1d397899dfc4 Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Thu, 2 Mar 2017 16:12:50 -0800 Subject: [PATCH 11/32] Fix PropTypes production test so it works with error code system --- scripts/error-codes/codes.json | 3 ++- .../classic/types/__tests__/ReactPropTypesProduction-test.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/error-codes/codes.json b/scripts/error-codes/codes.json index e4662c0912..fb8006fdf2 100644 --- a/scripts/error-codes/codes.json +++ b/scripts/error-codes/codes.json @@ -142,5 +142,6 @@ "140": "Expected hook events to fire for the child before its parent includes it in onSetChildren().", "141": "Expected onSetChildren() to fire for a container child before its parent includes it in onSetChildren().", "142": "Expected onBeforeMountComponent() parent and onSetChildren() to be consistent (%s has parents %s and %s).", - "143": "React.Children.only expected to receive a single React element child." + "143": "React.Children.only expected to receive a single React element child.", + "144": "React.PropTypes type checking code is stripped in production." } diff --git a/src/isomorphic/classic/types/__tests__/ReactPropTypesProduction-test.js b/src/isomorphic/classic/types/__tests__/ReactPropTypesProduction-test.js index 07a46a8774..67842710e4 100644 --- a/src/isomorphic/classic/types/__tests__/ReactPropTypesProduction-test.js +++ b/src/isomorphic/classic/types/__tests__/ReactPropTypesProduction-test.js @@ -49,7 +49,7 @@ describe('ReactPropTypesProduction', function() { 'prop' ); }).toThrowError( - 'React.PropTypes type checking code is stripped in production.' + 'Minified React error #144' ); } From fcf41e7ca2ab902d3058016aac6fb9b1e0ab46f8 Mon Sep 17 00:00:00 2001 From: Sasha Aickin Date: Fri, 3 Mar 2017 10:34:14 -0800 Subject: [PATCH 12/32] Add async/await to unit tests; modify server rendering tests to use async/await. (#9089) --- package.json | 1 + scripts/jest/preprocessor.js | 9 +- .../ReactDOMServerIntegration-test.js | 99 ++++++++++--------- 3 files changed, 60 insertions(+), 49 deletions(-) diff --git a/package.json b/package.json index 2ca46b1b15..c91e902a6c 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "babel-jest": "^19.0.0", "babel-plugin-check-es2015-constants": "^6.5.0", "babel-plugin-syntax-trailing-function-commas": "^6.5.0", + "babel-plugin-transform-async-to-generator": "^6.22.0", "babel-plugin-transform-class-properties": "^6.11.5", "babel-plugin-transform-es2015-arrow-functions": "^6.5.2", "babel-plugin-transform-es2015-block-scoped-functions": "^6.5.0", diff --git a/scripts/jest/preprocessor.js b/scripts/jest/preprocessor.js index 20f372dc57..4a48cfe9e5 100644 --- a/scripts/jest/preprocessor.js +++ b/scripts/jest/preprocessor.js @@ -22,6 +22,7 @@ var pathToBabel = path.join(require.resolve('babel-core'), '..', 'package.json') var pathToModuleMap = require.resolve('fbjs/module-map'); var pathToBabelPluginDevWithCode = require.resolve('../error-codes/dev-expression-with-codes'); var pathToBabelPluginModules = require.resolve('fbjs-scripts/babel-6/rewrite-modules'); +var pathToBabelPluginAsyncToGenerator = require.resolve('babel-plugin-transform-async-to-generator'); var pathToBabelrc = path.join(__dirname, '..', '..', '.babelrc'); var pathToErrorCodes = require.resolve('../error-codes/codes.json'); @@ -54,11 +55,17 @@ module.exports = { !filePath.match(/\/node_modules\//) && !filePath.match(/\/third_party\//) ) { + // for test files, we also apply the async-await transform, but we want to + // make sure we don't accidentally apply that transform to product code. + var isTestFile = !!filePath.match(/\/__tests__\//); return babel.transform( src, Object.assign( {filename: path.relative(process.cwd(), filePath)}, - babelOptions + babelOptions, + isTestFile ? { + plugins: [pathToBabelPluginAsyncToGenerator].concat(babelOptions.plugins), + } : {} ) ).code; } diff --git a/src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js b/src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js index daf84a2f21..afe70f87cc 100644 --- a/src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js +++ b/src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js @@ -19,41 +19,44 @@ let ReactDOMServer; // Helper functions for rendering tests // ==================================== +// promisified version of ReactDOM.render() +function asyncReactDOMRender(reactElement, domElement) { + return new Promise(resolve => + ReactDOM.render(reactElement, domElement, resolve)); +} // performs fn asynchronously and expects count errors logged to console.error. // will fail the test if the count of errors logged is not equal to count. -function expectErrors(fn, count) { +async function expectErrors(fn, count) { if (console.error.calls && console.error.calls.reset) { console.error.calls.reset(); } else { spyOn(console, 'error'); } - return fn().then((result) => { - if (console.error.calls.count() !== count) { - console.log(`We expected ${count} warning(s), but saw ${console.error.calls.count()} warning(s).`); - if (console.error.calls.count() > 0) { - console.log(`We saw these warnings:`); - for (var i = 0; i < console.error.calls.count(); i++) { - console.log(console.error.calls.argsFor(i)[0]); - } + const result = await fn(); + if (console.error.calls.count() !== count && console.error.calls.count() !== 0) { + console.log(`We expected ${count} warning(s), but saw ${console.error.calls.count()} warning(s).`); + if (console.error.calls.count() > 0) { + console.log(`We saw these warnings:`); + for (var i = 0; i < console.error.calls.count(); i++) { + console.log(console.error.calls.argsFor(i)[0]); } } - expectDev(console.error.calls.count()).toBe(count); - return result; - }); + } + expectDev(console.error.calls.count()).toBe(count); + return result; } // renders the reactElement into domElement, and expects a certain number of errors. // returns a Promise that resolves when the render is complete. function renderIntoDom(reactElement, domElement, errorCount = 0) { return expectErrors( - () => new Promise((resolve) => { + async () => { ExecutionEnvironment.canUseDOM = true; - ReactDOM.render(reactElement, domElement, () => { - ExecutionEnvironment.canUseDOM = false; - resolve(domElement.firstChild); - }); - }), + await asyncReactDOMRender(reactElement, domElement); + ExecutionEnvironment.canUseDOM = false; + return domElement.firstChild; + }, errorCount ); } @@ -61,15 +64,14 @@ function renderIntoDom(reactElement, domElement, errorCount = 0) { // Renders text using SSR and then stuffs it into a DOM node; returns the DOM // element that corresponds with the reactElement. // Does not render on client or perform client-side revival. -function serverRender(reactElement, errorCount = 0) { - return expectErrors( +async function serverRender(reactElement, errorCount = 0) { + const markup = await expectErrors( () => Promise.resolve(ReactDOMServer.renderToString(reactElement)), - errorCount) - .then((markup) => { - var domElement = document.createElement('div'); - domElement.innerHTML = markup; - return domElement.firstChild; - }); + errorCount + ); + var domElement = document.createElement('div'); + domElement.innerHTML = markup; + return domElement.firstChild; } const clientCleanRender = (element, errorCount = 0) => { @@ -77,12 +79,11 @@ const clientCleanRender = (element, errorCount = 0) => { return renderIntoDom(element, div, errorCount); }; -const clientRenderOnServerString = (element, errorCount = 0) => { - return serverRender(element, errorCount).then((markup) => { - var domElement = document.createElement('div'); - domElement.innerHTML = markup; - return renderIntoDom(element, domElement, errorCount); - }); +const clientRenderOnServerString = async (element, errorCount = 0) => { + const markup = await serverRender(element, errorCount); + var domElement = document.createElement('div'); + domElement.innerHTML = markup; + return renderIntoDom(element, domElement, errorCount); }; const clientRenderOnBadMarkup = (element, errorCount = 0) => { @@ -143,24 +144,26 @@ describe('ReactDOMServerIntegration', () => { }); describe('basic rendering', function() { - itRenders('a blank div', render => - render(
).then(e => expect(e.tagName).toBe('DIV'))); + itRenders('a blank div', async render => { + const e = await render(
); + expect(e.tagName).toBe('DIV'); + }); - itRenders('a div with inline styles', render => - render(
).then(e => { - expect(e.style.color).toBe('red'); - expect(e.style.width).toBe('30px'); - }) - ); + itRenders('a div with inline styles', async render => { + const e = await render(
); + expect(e.style.color).toBe('red'); + expect(e.style.width).toBe('30px'); + }); - itRenders('a self-closing tag', render => - render(
).then(e => expect(e.tagName).toBe('BR'))); + itRenders('a self-closing tag', async render => { + const e = await render(
); + expect(e.tagName).toBe('BR'); + }); - itRenders('a self-closing tag as a child', render => - render(

).then(e => { - expect(e.childNodes.length).toBe(1); - expect(e.firstChild.tagName).toBe('BR'); - }) - ); + itRenders('a self-closing tag as a child', async render => { + const e = await render(

); + expect(e.childNodes.length).toBe(1); + expect(e.firstChild.tagName).toBe('BR'); + }); }); }); From 0bedd0697aaf748902ac8c9d57d9ecb8b93c91f9 Mon Sep 17 00:00:00 2001 From: Dima Beznos Date: Sat, 4 Mar 2017 00:15:25 +0200 Subject: [PATCH 13/32] Added Brunch build tool to the docs (#9074) * Added Brunch build tool to the docs * Improved grammar * product build -> production build --- docs/docs/installation.md | 4 ++++ docs/docs/optimizing-performance.md | 1 + 2 files changed, 5 insertions(+) diff --git a/docs/docs/installation.md b/docs/docs/installation.md index 0039fc7750..15da2f4d17 100644 --- a/docs/docs/installation.md +++ b/docs/docs/installation.md @@ -99,6 +99,10 @@ By default, React includes many helpful warnings. These warnings are very useful If you use [Create React App](https://github.com/facebookincubator/create-react-app), `npm run build` will create an optimized build of your app in the `build` folder. +#### Brunch + +To create an optimized production build with Brunch, just add the `-p` flag to the build command. See the [Brunch docs](http://brunch.io/docs/commands) for more details. + #### Webpack Include both `DefinePlugin` and `UglifyJsPlugin` into your production Webpack configuration as described in [this guide](https://webpack.js.org/guides/production-build/). diff --git a/docs/docs/optimizing-performance.md b/docs/docs/optimizing-performance.md index fe1d93077a..86acb8cdb7 100644 --- a/docs/docs/optimizing-performance.md +++ b/docs/docs/optimizing-performance.md @@ -13,6 +13,7 @@ If you're benchmarking or experiencing performance problems in your React apps, * For Create React App, you need to run `npm run build` and follow the instructions. * For single-file builds, we offer production-ready `.min.js` versions. +* For Brunch, you need to add the `-p` flag to the `build` command. * For Browserify, you need to run it with `NODE_ENV=production`. * For Webpack, you need to add this to plugins in your production config: From d87c4d5e5ca3e82ea143ce1db5b2ee448482a12d Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Sat, 4 Mar 2017 04:23:09 +0000 Subject: [PATCH 14/32] [Fiber] Don't schedule class fibers without relevant lifecycles for commit (#9105) * Don't schedule class fibers without relevant lifecycles for commit * Separate Update and Ref effects * Simplify the exit condition * Add missing annotation * Write conditions differently to avoid an extra check * Inline markUpdateIfNecessary() * Inline markUpdateIfAlreadyInProgress() --- .../shared/fiber/ReactFiberClassComponent.js | 46 ++++++++++--------- .../shared/fiber/ReactFiberCommitWork.js | 39 ++++++---------- .../shared/fiber/ReactFiberCompleteWork.js | 11 ++++- .../shared/fiber/ReactFiberScheduler.js | 28 +++++++---- 4 files changed, 67 insertions(+), 57 deletions(-) diff --git a/src/renderers/shared/fiber/ReactFiberClassComponent.js b/src/renderers/shared/fiber/ReactFiberClassComponent.js index 5af2249d3b..ae35f8db6d 100644 --- a/src/renderers/shared/fiber/ReactFiberClassComponent.js +++ b/src/renderers/shared/fiber/ReactFiberClassComponent.js @@ -227,22 +227,6 @@ module.exports = function( } } - - function markUpdate(workInProgress) { - workInProgress.effectTag |= Update; - } - - function markUpdateIfAlreadyInProgress(current: Fiber | null, workInProgress : Fiber) { - // If an update was already in progress, we should schedule an Update - // effect even though we're bailing out, so that cWU/cDU are called. - if (current !== null) { - if (workInProgress.memoizedProps !== current.memoizedProps || - workInProgress.memoizedState !== current.memoizedState) { - markUpdate(workInProgress); - } - } - } - function resetInputPointers(workInProgress : Fiber, instance : any) { instance.props = workInProgress.memoizedProps; instance.state = workInProgress.memoizedState; @@ -276,7 +260,6 @@ module.exports = function( // Invokes the mount life-cycles on a previously never rendered instance. function mountClassInstance(workInProgress : Fiber, priorityLevel : PriorityLevel) : void { - markUpdate(workInProgress); const instance = workInProgress.stateNode; const state = instance.state || null; @@ -310,12 +293,14 @@ module.exports = function( ); } } + if (typeof instance.componentDidMount === 'function') { + workInProgress.effectTag |= Update; + } } // Called on a preexisting class instance. Returns false if a resumed render // could be reused. function resumeMountClassInstance(workInProgress : Fiber, priorityLevel : PriorityLevel) : boolean { - markUpdate(workInProgress); const instance = workInProgress.stateNode; resetInputPointers(workInProgress, instance); @@ -378,6 +363,9 @@ module.exports = function( priorityLevel ); } + if (typeof instance.componentDidMount === 'function') { + workInProgress.effectTag |= Update; + } return true; } @@ -447,7 +435,14 @@ module.exports = function( oldState === newState && !hasContextChanged() && !(updateQueue !== null && updateQueue.hasForceUpdate)) { - markUpdateIfAlreadyInProgress(current, workInProgress); + // If an update was already in progress, we should schedule an Update + // effect even though we're bailing out, so that cWU/cDU are called. + if (typeof instance.componentDidUpdate === 'function') { + if (oldProps !== current.memoizedProps || + oldState !== current.memoizedState) { + workInProgress.effectTag |= Update; + } + } return false; } @@ -461,12 +456,21 @@ module.exports = function( ); if (shouldUpdate) { - markUpdate(workInProgress); if (typeof instance.componentWillUpdate === 'function') { instance.componentWillUpdate(newProps, newState, newContext); } + if (typeof instance.componentDidUpdate === 'function') { + workInProgress.effectTag |= Update; + } } else { - markUpdateIfAlreadyInProgress(current, workInProgress); + // If an update was already in progress, we should schedule an Update + // effect even though we're bailing out, so that cWU/cDU are called. + if (typeof instance.componentDidUpdate === 'function') { + if (oldProps !== current.memoizedProps || + oldState !== current.memoizedState) { + workInProgress.effectTag |= Update; + } + } // If shouldComponentUpdate returned false, we should still update the // memoized props/state to indicate that this work can be reused. diff --git a/src/renderers/shared/fiber/ReactFiberCommitWork.js b/src/renderers/shared/fiber/ReactFiberCommitWork.js index e9676b106a..32cd4fb52f 100644 --- a/src/renderers/shared/fiber/ReactFiberCommitWork.js +++ b/src/renderers/shared/fiber/ReactFiberCommitWork.js @@ -87,16 +87,6 @@ module.exports = function( } } - // Only called during update. It's ok to throw. - function detachRefIfNeeded(current : Fiber | null, finishedWork : Fiber) { - if (current) { - const currentRef = current.ref; - if (currentRef !== null && currentRef !== finishedWork.ref) { - currentRef(null); - } - } - } - function getHostParent(fiber : Fiber) : I | C { let parent = fiber.return; while (parent !== null) { @@ -381,7 +371,6 @@ module.exports = function( function commitWork(current : Fiber | null, finishedWork : Fiber) : void { switch (finishedWork.tag) { case ClassComponent: { - detachRefIfNeeded(current, finishedWork); return; } case HostComponent: { @@ -398,7 +387,6 @@ module.exports = function( commitUpdate(instance, updatePayload, type, oldProps, newProps, finishedWork); } } - detachRefIfNeeded(current, finishedWork); return; } case HostText: { @@ -435,15 +423,11 @@ module.exports = function( const instance = finishedWork.stateNode; if (finishedWork.effectTag & Update) { if (current === null) { - if (typeof instance.componentDidMount === 'function') { - instance.componentDidMount(); - } + instance.componentDidMount(); } else { - if (typeof instance.componentDidUpdate === 'function') { - const prevProps = current.memoizedProps; - const prevState = current.memoizedState; - instance.componentDidUpdate(prevProps, prevState); - } + const prevProps = current.memoizedProps; + const prevState = current.memoizedState; + instance.componentDidUpdate(prevProps, prevState); } } if ((finishedWork.effectTag & Callback) && finishedWork.updateQueue !== null) { @@ -495,10 +479,7 @@ module.exports = function( } } - function commitRef(finishedWork : Fiber) { - if (finishedWork.tag !== ClassComponent && finishedWork.tag !== HostComponent) { - return; - } + function commitAttachRef(finishedWork : Fiber) { const ref = finishedWork.ref; if (ref !== null) { const instance = getPublicInstance(finishedWork.stateNode); @@ -506,12 +487,20 @@ module.exports = function( } } + function commitDetachRef(current : Fiber) { + const currentRef = current.ref; + if (currentRef !== null) { + currentRef(null); + } + } + return { commitPlacement, commitDeletion, commitWork, commitLifeCycles, - commitRef, + commitAttachRef, + commitDetachRef, }; }; diff --git a/src/renderers/shared/fiber/ReactFiberCompleteWork.js b/src/renderers/shared/fiber/ReactFiberCompleteWork.js index 8a04936183..aef565e1d2 100644 --- a/src/renderers/shared/fiber/ReactFiberCompleteWork.js +++ b/src/renderers/shared/fiber/ReactFiberCompleteWork.js @@ -85,6 +85,10 @@ module.exports = function( workInProgress.effectTag |= Update; } + function markRef(workInProgress : Fiber) { + workInProgress.effectTag |= Ref; + } + function appendAllYields(yields : Array, workInProgress : Fiber) { let node = workInProgress.stateNode; if (node) { @@ -231,9 +235,12 @@ module.exports = function( workInProgress.updateQueue = (updatePayload : any); // If the update payload indicates that there is a change or if there // is a new ref we mark this as an update. - if (updatePayload || current.ref !== workInProgress.ref) { + if (updatePayload) { markUpdate(workInProgress); } + if (current.ref !== workInProgress.ref) { + markRef(workInProgress); + } } else { if (!newProps) { invariant( @@ -270,7 +277,7 @@ module.exports = function( workInProgress.stateNode = instance; if (workInProgress.ref !== null) { // If there is a ref on a host node we need to schedule a callback - workInProgress.effectTag |= Ref; + markRef(workInProgress); } } return null; diff --git a/src/renderers/shared/fiber/ReactFiberScheduler.js b/src/renderers/shared/fiber/ReactFiberScheduler.js index cacaacdf83..861dd4a621 100644 --- a/src/renderers/shared/fiber/ReactFiberScheduler.js +++ b/src/renderers/shared/fiber/ReactFiberScheduler.js @@ -146,7 +146,8 @@ module.exports = function(config : HostConfig(config : HostConfig(config : HostConfig Date: Fri, 3 Mar 2017 22:42:25 -0600 Subject: [PATCH 15/32] Remove ReactStateSetters (#9107) --- scripts/fiber/tests-passing.txt | 8 - src/addons/link/ReactStateSetters.js | 104 ------------ .../__tests__/ReactStateSetters-test.js | 152 ------------------ 3 files changed, 264 deletions(-) delete mode 100644 src/addons/link/ReactStateSetters.js delete mode 100644 src/renderers/__tests__/ReactStateSetters-test.js diff --git a/scripts/fiber/tests-passing.txt b/scripts/fiber/tests-passing.txt index e8f18715b8..ac6bdcf360 100644 --- a/scripts/fiber/tests-passing.txt +++ b/scripts/fiber/tests-passing.txt @@ -784,14 +784,6 @@ src/renderers/__tests__/ReactPerf-test.js * should not print errant warnings if portal throws in componentWillMount() * should not print errant warnings if portal throws in componentDidMount() -src/renderers/__tests__/ReactStateSetters-test.js -* createStateSetter should update state -* createStateKeySetter should update state -* createStateKeySetter is memoized -* createStateSetter should update state from mixin -* createStateKeySetter should update state with mixin -* createStateKeySetter is memoized with mixin - src/renderers/__tests__/ReactStatelessComponent-test.js * should render stateless component * should update stateless component diff --git a/src/addons/link/ReactStateSetters.js b/src/addons/link/ReactStateSetters.js deleted file mode 100644 index 977826d301..0000000000 --- a/src/addons/link/ReactStateSetters.js +++ /dev/null @@ -1,104 +0,0 @@ -/** - * 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 ReactStateSetters - */ - -'use strict'; - -var ReactStateSetters = { - /** - * Returns a function that calls the provided function, and uses the result - * of that to set the component's state. - * - * @param {ReactCompositeComponent} component - * @param {function} funcReturningState Returned callback uses this to - * determine how to update state. - * @return {function} callback that when invoked uses funcReturningState to - * determined the object literal to setState. - */ - createStateSetter: function(component, funcReturningState) { - return function(a, b, c, d, e, f) { - var partialState = funcReturningState.call(component, a, b, c, d, e, f); - if (partialState) { - component.setState(partialState); - } - }; - }, - - /** - * Returns a single-argument callback that can be used to update a single - * key in the component's state. - * - * Note: this is memoized function, which makes it inexpensive to call. - * - * @param {ReactCompositeComponent} component - * @param {string} key The key in the state that you should update. - * @return {function} callback of 1 argument which calls setState() with - * the provided keyName and callback argument. - */ - createStateKeySetter: function(component, key) { - // Memoize the setters. - var cache = component.__keySetters || (component.__keySetters = {}); - return cache[key] || (cache[key] = createStateKeySetter(component, key)); - }, -}; - -function createStateKeySetter(component, key) { - // Partial state is allocated outside of the function closure so it can be - // reused with every call, avoiding memory allocation when this function - // is called. - var partialState = {}; - return function stateKeySetter(value) { - partialState[key] = value; - component.setState(partialState); - }; -} - -ReactStateSetters.Mixin = { - /** - * Returns a function that calls the provided function, and uses the result - * of that to set the component's state. - * - * For example, these statements are equivalent: - * - * this.setState({x: 1}); - * this.createStateSetter(function(xValue) { - * return {x: xValue}; - * })(1); - * - * @param {function} funcReturningState Returned callback uses this to - * determine how to update state. - * @return {function} callback that when invoked uses funcReturningState to - * determined the object literal to setState. - */ - createStateSetter: function(funcReturningState) { - return ReactStateSetters.createStateSetter(this, funcReturningState); - }, - - /** - * Returns a single-argument callback that can be used to update a single - * key in the component's state. - * - * For example, these statements are equivalent: - * - * this.setState({x: 1}); - * this.createStateKeySetter('x')(1); - * - * Note: this is memoized function, which makes it inexpensive to call. - * - * @param {string} key The key in the state that you should update. - * @return {function} callback of 1 argument which calls setState() with - * the provided keyName and callback argument. - */ - createStateKeySetter: function(key) { - return ReactStateSetters.createStateKeySetter(this, key); - }, -}; - -module.exports = ReactStateSetters; diff --git a/src/renderers/__tests__/ReactStateSetters-test.js b/src/renderers/__tests__/ReactStateSetters-test.js deleted file mode 100644 index dcc22a743b..0000000000 --- a/src/renderers/__tests__/ReactStateSetters-test.js +++ /dev/null @@ -1,152 +0,0 @@ -/** - * 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. - * - * @emails react-core - */ - -'use strict'; - -var React = require('react'); -var ReactStateSetters = require('ReactStateSetters'); -var ReactTestUtils = require('ReactTestUtils'); - -var TestComponent; -var TestComponentWithMixin; - -describe('ReactStateSetters', () => { - beforeEach(() => { - jest.resetModules(); - - TestComponent = class extends React.Component { - state = {foo: 'foo'}; - - render() { - return
; - } - }; - - TestComponentWithMixin = React.createClass({ - mixins: [ReactStateSetters.Mixin], - - getInitialState: function() { - return {foo: 'foo'}; - }, - - render: function() { - return
; - }, - }); - }); - - it('createStateSetter should update state', () => { - var instance = ; - instance = ReactTestUtils.renderIntoDocument(instance); - expect(instance.state).toEqual({foo: 'foo'}); - - var setter = ReactStateSetters.createStateSetter( - instance, - function(a, b, c) { - return { - foo: a + b + c, - bar: a * b * c, - }; - } - ); - expect(instance.state).toEqual({foo: 'foo'}); - - setter(1, 2, 3); - expect(instance.state).toEqual({foo: 6, bar: 6}); - - setter(10, 11, 12); - expect(instance.state).toEqual({foo: 33, bar: 1320}); - }); - - it('createStateKeySetter should update state', () => { - var instance = ; - instance = ReactTestUtils.renderIntoDocument(instance); - expect(instance.state).toEqual({foo: 'foo'}); - - var setter = ReactStateSetters.createStateKeySetter(instance, 'foo'); - - expect(instance.state).toEqual({foo: 'foo'}); - - setter('bar'); - expect(instance.state).toEqual({foo: 'bar'}); - - setter('baz'); - expect(instance.state).toEqual({foo: 'baz'}); - }); - - it('createStateKeySetter is memoized', () => { - var instance = ; - instance = ReactTestUtils.renderIntoDocument(instance); - expect(instance.state).toEqual({foo: 'foo'}); - - var foo1 = ReactStateSetters.createStateKeySetter(instance, 'foo'); - var bar1 = ReactStateSetters.createStateKeySetter(instance, 'bar'); - - var foo2 = ReactStateSetters.createStateKeySetter(instance, 'foo'); - var bar2 = ReactStateSetters.createStateKeySetter(instance, 'bar'); - - expect(foo2).toBe(foo1); - expect(bar2).toBe(bar1); - }); - - it('createStateSetter should update state from mixin', () => { - var instance = ; - instance = ReactTestUtils.renderIntoDocument(instance); - expect(instance.state).toEqual({foo: 'foo'}); - - var setter = instance.createStateSetter( - function(a, b, c) { - return { - foo: a + b + c, - bar: a * b * c, - }; - } - ); - expect(instance.state).toEqual({foo: 'foo'}); - - setter(1, 2, 3); - expect(instance.state).toEqual({foo: 6, bar: 6}); - - setter(10, 11, 12); - expect(instance.state).toEqual({foo: 33, bar: 1320}); - }); - - it('createStateKeySetter should update state with mixin', () => { - var instance = ; - instance = ReactTestUtils.renderIntoDocument(instance); - expect(instance.state).toEqual({foo: 'foo'}); - - var setter = instance.createStateKeySetter('foo'); - - expect(instance.state).toEqual({foo: 'foo'}); - - setter('bar'); - expect(instance.state).toEqual({foo: 'bar'}); - - setter('baz'); - expect(instance.state).toEqual({foo: 'baz'}); - }); - - it('createStateKeySetter is memoized with mixin', () => { - var instance = ; - instance = ReactTestUtils.renderIntoDocument(instance); - expect(instance.state).toEqual({foo: 'foo'}); - - var foo1 = instance.createStateKeySetter('foo'); - var bar1 = instance.createStateKeySetter('bar'); - - var foo2 = instance.createStateKeySetter('foo'); - var bar2 = instance.createStateKeySetter('bar'); - - expect(foo2).toBe(foo1); - expect(bar2).toBe(bar1); - }); -}); From 0c1f515fafb61dc6586f8a47fbb4c9db9bc8b0e5 Mon Sep 17 00:00:00 2001 From: Brandon Dail Date: Mon, 6 Mar 2017 00:52:39 -0600 Subject: [PATCH 16/32] Remove sliceChildren (#9109) --- scripts/fiber/tests-passing.txt | 7 -- .../children/__tests__/sliceChildren-test.js | 93 ------------------- src/isomorphic/children/sliceChildren.js | 34 ------- 3 files changed, 134 deletions(-) delete mode 100644 src/isomorphic/children/__tests__/sliceChildren-test.js delete mode 100644 src/isomorphic/children/sliceChildren.js diff --git a/scripts/fiber/tests-passing.txt b/scripts/fiber/tests-passing.txt index ac6bdcf360..bd4121d8ed 100644 --- a/scripts/fiber/tests-passing.txt +++ b/scripts/fiber/tests-passing.txt @@ -157,13 +157,6 @@ src/isomorphic/children/__tests__/onlyChild-test.js * should not fail when passed interpolated single child * should return the only child -src/isomorphic/children/__tests__/sliceChildren-test.js -* should render the whole set if start zero is supplied -* should render the remaining set if no end index is supplied -* should exclude everything at or after the end index -* should allow static children to be sliced -* should slice nested children - src/isomorphic/classic/__tests__/ReactContextValidator-test.js * should filter out context not in contextTypes * should pass next context to lifecycles diff --git a/src/isomorphic/children/__tests__/sliceChildren-test.js b/src/isomorphic/children/__tests__/sliceChildren-test.js deleted file mode 100644 index d09ab6757f..0000000000 --- a/src/isomorphic/children/__tests__/sliceChildren-test.js +++ /dev/null @@ -1,93 +0,0 @@ -/** - * 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. - * - * @emails react-core - */ - -'use strict'; - -describe('sliceChildren', () => { - - var React; - - var sliceChildren; - - beforeEach(() => { - React = require('react'); - - sliceChildren = require('sliceChildren'); - }); - - it('should render the whole set if start zero is supplied', () => { - var fullSet = [ -
, -
, -
, - ]; - var children = sliceChildren(fullSet, 0); - expect(children).toEqual([ -
, -
, -
, - ]); - }); - - it('should render the remaining set if no end index is supplied', () => { - var fullSet = [ -
, -
, -
, - ]; - var children = sliceChildren(fullSet, 1); - expect(children).toEqual([ -
, -
, - ]); - }); - - it('should exclude everything at or after the end index', () => { - var fullSet = [ -
, -
, -
, -
, - ]; - var children = sliceChildren(fullSet, 1, 2); - expect(children).toEqual([ -
, - ]); - }); - - it('should allow static children to be sliced', () => { - var a = ; - var b = ; - var c = ; - - var el =
{a}{b}{c}
; - var children = sliceChildren(el.props.children, 1, 2); - expect(children).toEqual([ - , - ]); - }); - - it('should slice nested children', () => { - var fullSet = [ -
, - [ -
, -
, - ], -
, - ]; - var children = sliceChildren(fullSet, 1, 2); - expect(children).toEqual([ -
, - ]); - }); - -}); diff --git a/src/isomorphic/children/sliceChildren.js b/src/isomorphic/children/sliceChildren.js deleted file mode 100644 index 691af1da34..0000000000 --- a/src/isomorphic/children/sliceChildren.js +++ /dev/null @@ -1,34 +0,0 @@ -/** - * 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 sliceChildren - */ - -'use strict'; - -var ReactChildren = require('ReactChildren'); - -/** - * Slice children that are typically specified as `props.children`. This version - * of slice children ignores empty child components. - * - * @param {*} children The children set to filter. - * @param {number} start The first zero-based index to include in the subset. - * @param {?number} end The non-inclusive last index of the subset. - * @return {object} mirrored array with mapped children - */ -function sliceChildren(children, start, end) { - if (children == null) { - return children; - } - - var array = ReactChildren.toArray(children); - return array.slice(start, end); -} - -module.exports = sliceChildren; From 12ad77d00635b8dfa6d491207688acb9cd4b25bc Mon Sep 17 00:00:00 2001 From: Brandon Dail Date: Mon, 6 Mar 2017 00:52:56 -0600 Subject: [PATCH 17/32] Remove EventPluginRegistry.getPluginModuleForEvent (#9110) --- scripts/fiber/tests-passing.txt | 1 - .../shared/event/EventPluginRegistry.js | 36 ------------------- .../__tests__/EventPluginRegistry-test.js | 36 ------------------- 3 files changed, 73 deletions(-) diff --git a/scripts/fiber/tests-passing.txt b/scripts/fiber/tests-passing.txt index bd4121d8ed..c2cfe1b935 100644 --- a/scripts/fiber/tests-passing.txt +++ b/scripts/fiber/tests-passing.txt @@ -1641,7 +1641,6 @@ src/renderers/shared/shared/event/__tests__/EventPluginRegistry-test.js * should publish registration names of injected plugins * should throw if multiple registration names collide * should throw if an invalid event is published -* should be able to get the plugin from synthetic events src/renderers/shared/shared/event/eventPlugins/__tests__/ResponderEventPlugin-test.js * should do nothing when no one wants to respond diff --git a/src/renderers/shared/shared/event/EventPluginRegistry.js b/src/renderers/shared/shared/event/EventPluginRegistry.js index fa868dca10..effcd59939 100644 --- a/src/renderers/shared/shared/event/EventPluginRegistry.js +++ b/src/renderers/shared/shared/event/EventPluginRegistry.js @@ -14,7 +14,6 @@ import type { DispatchConfig, - ReactSyntheticEvent, } from 'ReactSyntheticEventType'; import type { @@ -257,41 +256,6 @@ var EventPluginRegistry = { } }, - /** - * Looks up the plugin for the supplied event. - * - * @param {object} event A synthetic event. - * @return {?object} The plugin that created the supplied event. - * @internal - */ - getPluginModuleForEvent: function( - event: ReactSyntheticEvent, - ): null | PluginModule { - var dispatchConfig = event.dispatchConfig; - if (dispatchConfig.registrationName) { - return EventPluginRegistry.registrationNameModules[ - dispatchConfig.registrationName - ] || null; - } - if (dispatchConfig.phasedRegistrationNames !== undefined) { - // pulling phasedRegistrationNames out of dispatchConfig helps Flow see - // that it is not undefined. - var {phasedRegistrationNames} = dispatchConfig; - for (var phase in phasedRegistrationNames) { - if (!phasedRegistrationNames.hasOwnProperty(phase)) { - continue; - } - var pluginModule = EventPluginRegistry.registrationNameModules[ - phasedRegistrationNames[phase] - ]; - if (pluginModule) { - return pluginModule; - } - } - } - return null; - }, - /** * Exposed for unit testing. * @private diff --git a/src/renderers/shared/shared/event/__tests__/EventPluginRegistry-test.js b/src/renderers/shared/shared/event/__tests__/EventPluginRegistry-test.js index 245e7bcc49..ed22b5c7de 100644 --- a/src/renderers/shared/shared/event/__tests__/EventPluginRegistry-test.js +++ b/src/renderers/shared/shared/event/__tests__/EventPluginRegistry-test.js @@ -225,40 +225,4 @@ describe('EventPluginRegistry', () => { '`one`.' ); }); - - it('should be able to get the plugin from synthetic events', () => { - var clickDispatchConfig = { - registrationName: 'onClick', - }; - var magicDispatchConfig = { - phasedRegistrationNames: { - bubbled: 'onMagicBubble', - captured: 'onMagicCapture', - }, - }; - - var OnePlugin = createPlugin({ - eventTypes: { - click: clickDispatchConfig, - magic: magicDispatchConfig, - }, - }); - - var clickEvent = {dispatchConfig: clickDispatchConfig}; - var magicEvent = {dispatchConfig: magicDispatchConfig}; - - expect(EventPluginRegistry.getPluginModuleForEvent(clickEvent)).toBe(null); - expect(EventPluginRegistry.getPluginModuleForEvent(magicEvent)).toBe(null); - - EventPluginRegistry.injectEventPluginsByName({one: OnePlugin}); - EventPluginRegistry.injectEventPluginOrder(['one']); - - expect( - EventPluginRegistry.getPluginModuleForEvent(clickEvent) - ).toBe(OnePlugin); - expect( - EventPluginRegistry.getPluginModuleForEvent(magicEvent) - ).toBe(OnePlugin); - }); - }); From f939c1bca4cf907616c89c023f0cfddf33d0089a Mon Sep 17 00:00:00 2001 From: jddxf <740531372@qq.com> Date: Mon, 6 Mar 2017 17:08:56 +0800 Subject: [PATCH 18/32] Don't reset the return fiber after cloning the child fibers, as we already do that while cloning. --- src/renderers/shared/fiber/ReactChildFiber.js | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/renderers/shared/fiber/ReactChildFiber.js b/src/renderers/shared/fiber/ReactChildFiber.js index 9dd69f1505..a79a124fcd 100644 --- a/src/renderers/shared/fiber/ReactChildFiber.js +++ b/src/renderers/shared/fiber/ReactChildFiber.js @@ -1353,15 +1353,15 @@ exports.cloneChildFibers = function(current : Fiber | null, workInProgress : Fib newChild.return = workInProgress; } newChild.sibling = null; - } - - // If there is no alternate, then we don't need to clone the children. - // If the children of the alternate fiber is a different set, then we don't - // need to clone. We need to reset the return fiber though since we'll - // traverse down into them. - let child = workInProgress.child; - while (child !== null) { - child.return = workInProgress; - child = child.sibling; + } else { + // If there is no alternate, then we don't need to clone the children. + // If the children of the alternate fiber is a different set, then we don't + // need to clone. We need to reset the return fiber though since we'll + // traverse down into them. + let child = workInProgress.child; + while (child !== null) { + child.return = workInProgress; + child = child.sibling; + } } }; From b572e3c907e31700b140d750fd77977f0fd15885 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Mon, 6 Mar 2017 14:19:23 +0000 Subject: [PATCH 19/32] moved non DEV methods to a new file called ReactFiberComponentTreeHook.js This moves the non DEV methods to a new file called ReactFiberComponentTreeHook, updates existing references to DEV functions to remain inside DEV flags so they do not pull in ReactComponentTreeHook --- .../classic/element/ReactDebugCurrentFrame.js | 14 +- .../classic/element/ReactElementValidator.js | 8 +- .../hooks/ReactComponentTreeHook.js | 773 ++++++++---------- .../shared/hooks/ReactDOMInvalidARIAHook.js | 9 +- .../hooks/ReactDOMNullInputValuePropHook.js | 10 +- src/renderers/shared/ReactDebugTool.js | 57 +- .../shared/fiber/ReactDebugCurrentFiber.js | 2 +- .../shared/fiber/ReactFiberScheduler.js | 2 +- .../shared/stack/reconciler/ReactRef.js | 9 +- src/shared/ReactFiberComponentTreeHook.js | 77 ++ src/shared/utils/flattenChildren.js | 8 +- src/umd/ReactUMDEntry.js | 11 +- 12 files changed, 486 insertions(+), 494 deletions(-) create mode 100644 src/shared/ReactFiberComponentTreeHook.js diff --git a/src/isomorphic/classic/element/ReactDebugCurrentFrame.js b/src/isomorphic/classic/element/ReactDebugCurrentFrame.js index 056a38bbcb..ee8ab2248e 100644 --- a/src/isomorphic/classic/element/ReactDebugCurrentFrame.js +++ b/src/isomorphic/classic/element/ReactDebugCurrentFrame.js @@ -14,16 +14,18 @@ import type { Fiber } from 'ReactFiber'; import type { DebugID } from 'ReactInstanceType'; -import type { ComponentTreeHookDevType } from 'ReactComponentTreeHook'; const ReactDebugCurrentFrame = {}; if (__DEV__) { - const { + var { getStackAddendumByID, - getStackAddendumByWorkInProgressFiber, getCurrentStackAddendum, - }: ComponentTreeHookDevType = (require('ReactComponentTreeHook'): any); + } = require('ReactComponentTreeHook'); + var { + getStackAddendumByWorkInProgressFiber, + } = require('ReactFiberComponentTreeHook'); + // Component that is being worked on ReactDebugCurrentFrame.current = (null : Fiber | DebugID | null); @@ -38,7 +40,7 @@ if (__DEV__) { if (typeof current === 'number') { // DebugID from Stack. const debugID = current; - stack = getStackAddendumByID && getStackAddendumByID(debugID); + stack = getStackAddendumByID(debugID); } else if (typeof current.tag === 'number') { // This is a Fiber. // The stack will only be correct if this is a work in progress @@ -47,7 +49,7 @@ if (__DEV__) { stack = getStackAddendumByWorkInProgressFiber(workInProgress); } } else if (element !== null) { - stack = getCurrentStackAddendum && getCurrentStackAddendum(element); + stack = getCurrentStackAddendum(element); } return stack; }; diff --git a/src/isomorphic/classic/element/ReactElementValidator.js b/src/isomorphic/classic/element/ReactElementValidator.js index b70fedc37e..7f30253349 100644 --- a/src/isomorphic/classic/element/ReactElementValidator.js +++ b/src/isomorphic/classic/element/ReactElementValidator.js @@ -19,7 +19,6 @@ 'use strict'; var ReactCurrentOwner = require('ReactCurrentOwner'); -var ReactComponentTreeHook = require('ReactComponentTreeHook'); var ReactElement = require('ReactElement'); var checkReactTypeSpec = require('checkReactTypeSpec'); @@ -31,6 +30,9 @@ var getIteratorFn = require('getIteratorFn'); if (__DEV__) { var warning = require('fbjs/lib/warning'); var ReactDebugCurrentFrame = require('ReactDebugCurrentFrame'); + var { + getCurrentStackAddendum, + } = require('ReactComponentTreeHook'); } function getDeclarationErrorAddendum() { @@ -122,7 +124,7 @@ function validateExplicitKey(element, parentType) { '%s%s See https://fb.me/react-warning-keys for more information.%s', currentComponentErrorInfo, childOwner, - ReactComponentTreeHook.getCurrentStackAddendum(element) + getCurrentStackAddendum && getCurrentStackAddendum(element) ); } @@ -225,7 +227,7 @@ var ReactElementValidator = { info += getDeclarationErrorAddendum(); } - info += ReactComponentTreeHook.getCurrentStackAddendum(); + info += getCurrentStackAddendum && getCurrentStackAddendum(); warning( false, diff --git a/src/isomorphic/hooks/ReactComponentTreeHook.js b/src/isomorphic/hooks/ReactComponentTreeHook.js index 5d8cdf616d..9fede1ac22 100644 --- a/src/isomorphic/hooks/ReactComponentTreeHook.js +++ b/src/isomorphic/hooks/ReactComponentTreeHook.js @@ -12,476 +12,393 @@ 'use strict'; +var ReactCurrentOwner = require('ReactCurrentOwner'); +var { + getStackAddendumByWorkInProgressFiber, + describeComponentFrame, +} = require('ReactFiberComponentTreeHook'); +var invariant = require('fbjs/lib/invariant'); +var warning = require('fbjs/lib/warning'); +var getComponentName = require('getComponentName'); + import type { ReactElement, Source } from 'ReactElementType'; import type { DebugID } from 'ReactInstanceType'; import type { Fiber } from 'ReactFiber'; -var getComponentName = require('getComponentName'); -var ReactTypeOfWork = require('ReactTypeOfWork'); -var { - IndeterminateComponent, - FunctionalComponent, - ClassComponent, - HostComponent, -} = ReactTypeOfWork; - -function describeComponentFrame(name, source, ownerName) { - return '\n in ' + (name || 'Unknown') + ( - source ? - ' (at ' + source.fileName.replace(/^.*[\\\/]/, '') + ':' + - source.lineNumber + ')' : - ownerName ? - ' (created by ' + ownerName + ')' : - '' +function isNative(fn) { + // Based on isNative() from Lodash + var funcToString = Function.prototype.toString; + var hasOwnProperty = Object.prototype.hasOwnProperty; + var reIsNative = RegExp('^' + funcToString + // Take an example native function source for comparison + .call(hasOwnProperty) + // Strip regex characters so we can use it for regex + .replace(/[\\^$.*+?()[\]{}|]/g, '\\$&') + // Remove hasOwnProperty from the template to make it generic + .replace( + /hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, + '$1.*?' + ) + '$' ); -} - -function describeFiber(fiber : Fiber) : string { - switch (fiber.tag) { - case IndeterminateComponent: - case FunctionalComponent: - case ClassComponent: - case HostComponent: - var owner = fiber._debugOwner; - var source = fiber._debugSource; - var name = getComponentName(fiber); - var ownerName = null; - if (owner) { - ownerName = getComponentName(owner); - } - return describeComponentFrame(name, source, ownerName); - default: - return ''; + try { + var source = funcToString.call(fn); + return reIsNative.test(source); + } catch (err) { + return false; } } -export type ComponentTreeHookType = { - getStackAddendumByWorkInProgressFiber: (Fiber) => any, -}; +var canUseCollections = ( + // Array.from + typeof Array.from === 'function' && + // Map + typeof Map === 'function' && + isNative(Map) && + // Map.prototype.keys + Map.prototype != null && + typeof Map.prototype.keys === 'function' && + isNative(Map.prototype.keys) && + // Set + typeof Set === 'function' && + isNative(Set) && + // Set.prototype.keys + Set.prototype != null && + typeof Set.prototype.keys === 'function' && + isNative(Set.prototype.keys) +); -export type ComponentTreeHookDevType = { - getStackAddendumByWorkInProgressFiber: (Fiber) => any, - getStackAddendumByID: () => any, - getCurrentStackAddendum: () => any, - purgeUnmountedComponents: () => any, - getOwnerID: (DebugID) => any, - getParentID: (DebugID) => any, - getDisplayName: (DebugID) => any, - getText: (DebugID) => any, - getUpdateCount: (DebugID) => any, - getChildIDs: (DebugID) => any, - getRegisteredIDs: () => any, - getElement: () => any, -}; +var setItem; +var getItem; +var removeItem; +var getItemIDs; +var addRoot; +var removeRoot; +var getRootIDs; -var ReactComponentTreeHook: ComponentTreeHookType = { - // This function can only be called with a work-in-progress fiber and - // only during begin or complete phase. Do not call it under any other - // circumstances. - getStackAddendumByWorkInProgressFiber(workInProgress : Fiber) : string { - var info = ''; - var node = workInProgress; - do { - info += describeFiber(node); - // Otherwise this return pointer might point to the wrong tree: - node = node.return; - } while (node); - return info; - }, -}; +if (canUseCollections) { + var itemMap = new Map(); + var rootIDSet = new Set(); -if (__DEV__) { - var ReactCurrentOwner = require('ReactCurrentOwner'); - var invariant = require('fbjs/lib/invariant'); - var warning = require('fbjs/lib/warning'); - - var isNative = function(fn) { - // Based on isNative() from Lodash - var funcToString = Function.prototype.toString; - var hasOwnProperty = Object.prototype.hasOwnProperty; - var reIsNative = RegExp('^' + funcToString - // Take an example native function source for comparison - .call(hasOwnProperty) - // Strip regex characters so we can use it for regex - .replace(/[\\^$.*+?()[\]{}|]/g, '\\$&') - // Remove hasOwnProperty from the template to make it generic - .replace( - /hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, - '$1.*?' - ) + '$' - ); - try { - var source = funcToString.call(fn); - return reIsNative.test(source); - } catch (err) { - return false; - } + setItem = function(id, item) { + itemMap.set(id, item); + }; + getItem = function(id) { + return itemMap.get(id); + }; + removeItem = function(id) { + itemMap.delete(id); + }; + getItemIDs = function() { + return Array.from(itemMap.keys()); }; - var canUseCollections = ( - // Array.from - typeof Array.from === 'function' && - // Map - typeof Map === 'function' && - isNative(Map) && - // Map.prototype.keys - Map.prototype != null && - typeof Map.prototype.keys === 'function' && - isNative(Map.prototype.keys) && - // Set - typeof Set === 'function' && - isNative(Set) && - // Set.prototype.keys - Set.prototype != null && - typeof Set.prototype.keys === 'function' && - isNative(Set.prototype.keys) - ); + addRoot = function(id) { + rootIDSet.add(id); + }; + removeRoot = function(id) { + rootIDSet.delete(id); + }; + getRootIDs = function() { + return Array.from(rootIDSet.keys()); + }; - var setItem; - var getItem; - var removeItem; - var getItemIDs; - var addRoot; - var removeRoot; - var getRootIDs; +} else { + var itemByKey = {}; + var rootByKey = {}; - if (canUseCollections) { - var itemMap = new Map(); - var rootIDSet = new Set(); + // Use non-numeric keys to prevent V8 performance issues: + // https://github.com/facebook/react/pull/7232 + var getKeyFromID = function(id: DebugID): string { + return '.' + id; + }; + var getIDFromKey = function(key: string): DebugID { + return parseInt(key.substr(1), 10); + }; - setItem = function(id, item) { - itemMap.set(id, item); - }; - getItem = function(id) { - return itemMap.get(id); - }; - removeItem = function(id) { - itemMap.delete(id); - }; - getItemIDs = function() { - return Array.from(itemMap.keys()); - }; + setItem = function(id, item) { + var key = getKeyFromID(id); + itemByKey[key] = item; + }; + getItem = function(id) { + var key = getKeyFromID(id); + return itemByKey[key]; + }; + removeItem = function(id) { + var key = getKeyFromID(id); + delete itemByKey[key]; + }; + getItemIDs = function() { + return Object.keys(itemByKey).map(getIDFromKey); + }; - addRoot = function(id) { - rootIDSet.add(id); - }; - removeRoot = function(id) { - rootIDSet.delete(id); - }; - getRootIDs = function() { - return Array.from(rootIDSet.keys()); - }; + addRoot = function(id) { + var key = getKeyFromID(id); + rootByKey[key] = true; + }; + removeRoot = function(id) { + var key = getKeyFromID(id); + delete rootByKey[key]; + }; + getRootIDs = function() { + return Object.keys(rootByKey).map(getIDFromKey); + }; +} - } else { - var itemByKey = {}; - var rootByKey = {}; +var unmountedIDs: Array = []; - // Use non-numeric keys to prevent V8 performance issues: - // https://github.com/facebook/react/pull/7232 - var getKeyFromID = function(id: DebugID): string { - return '.' + id; - }; - var getIDFromKey = function(key: string): DebugID { - return parseInt(key.substr(1), 10); - }; - - setItem = function(id, item) { - var key = getKeyFromID(id); - itemByKey[key] = item; - }; - getItem = function(id) { - var key = getKeyFromID(id); - return itemByKey[key]; - }; - removeItem = function(id) { - var key = getKeyFromID(id); - delete itemByKey[key]; - }; - getItemIDs = function() { - return Object.keys(itemByKey).map(getIDFromKey); - }; - - addRoot = function(id) { - var key = getKeyFromID(id); - rootByKey[key] = true; - }; - removeRoot = function(id) { - var key = getKeyFromID(id); - delete rootByKey[key]; - }; - getRootIDs = function() { - return Object.keys(rootByKey).map(getIDFromKey); - }; +function purgeDeep(id) { + var item = getItem(id); + if (item) { + var {childIDs} = item; + removeItem(id); + childIDs.forEach(purgeDeep); } +} - const unmountedIDs: Array = []; +function getDisplayName(element: ?ReactElement): string { + if (element == null) { + return '#empty'; + } else if (typeof element === 'string' || typeof element === 'number') { + return '#text'; + } else if (typeof element.type === 'string') { + return element.type; + } else { + return element.type.displayName || element.type.name || 'Unknown'; + } +} - const purgeDeep = function(id) { +function describeID(id: DebugID): string { + const name = ReactComponentTreeHook.getDisplayName(id); + const element = ReactComponentTreeHook.getElement(id); + const ownerID = ReactComponentTreeHook.getOwnerID(id); + let ownerName; + + if (ownerID) { + ownerName = ReactComponentTreeHook.getDisplayName(ownerID); + } + warning( + element, + 'ReactComponentTreeHook: Missing React element for debugID %s when ' + + 'building stack', + id + ); + if (element && name) { + return describeComponentFrame(name || '', element._source, ownerName || ''); + } + return ''; +} + +var ReactComponentTreeHook = { + onSetChildren(id: DebugID, nextChildIDs: Array): void { + var item = getItem(id); + invariant(item, 'Item must have been set'); + item.childIDs = nextChildIDs; + + for (var i = 0; i < nextChildIDs.length; i++) { + var nextChildID = nextChildIDs[i]; + var nextChild = getItem(nextChildID); + invariant( + nextChild, + 'Expected hook events to fire for the child ' + + 'before its parent includes it in onSetChildren().' + ); + invariant( + nextChild.childIDs != null || + typeof nextChild.element !== 'object' || + nextChild.element == null, + 'Expected onSetChildren() to fire for a container child ' + + 'before its parent includes it in onSetChildren().' + ); + invariant( + nextChild.isMounted, + 'Expected onMountComponent() to fire for the child ' + + 'before its parent includes it in onSetChildren().' + ); + if (nextChild.parentID == null) { + nextChild.parentID = id; + // TODO: This shouldn't be necessary but mounting a new root during in + // componentWillMount currently causes not-yet-mounted components to + // be purged from our tree data so their parent id is missing. + } + invariant( + nextChild.parentID === id, + 'Expected onBeforeMountComponent() parent and onSetChildren() to ' + + 'be consistent (%s has parents %s and %s).', + nextChildID, + nextChild.parentID, + id + ); + } + }, + + onBeforeMountComponent(id: DebugID, element: ReactElement, parentID: DebugID): void { + var item = { + element, + parentID, + text: null, + childIDs: [], + isMounted: false, + updateCount: 0, + }; + setItem(id, item); + }, + + onBeforeUpdateComponent(id: DebugID, element: ReactElement): void { + var item = getItem(id); + if (!item || !item.isMounted) { + // We may end up here as a result of setState() in componentWillUnmount(). + // In this case, ignore the element. + return; + } + item.element = element; + }, + + onMountComponent(id: DebugID): void { + var item = getItem(id); + invariant(item, 'Item must have been set'); + item.isMounted = true; + var isRoot = item.parentID === 0; + if (isRoot) { + addRoot(id); + } + }, + + onUpdateComponent(id: DebugID): void { + var item = getItem(id); + if (!item || !item.isMounted) { + // We may end up here as a result of setState() in componentWillUnmount(). + // In this case, ignore the element. + return; + } + item.updateCount++; + }, + + onUnmountComponent(id: DebugID): void { var item = getItem(id); if (item) { - var {childIDs} = item; - removeItem(id); - childIDs.forEach(purgeDeep); + // We need to check if it exists. + // `item` might not exist if it is inside an error boundary, and a sibling + // error boundary child threw while mounting. Then this instance never + // got a chance to mount, but it still gets an unmounting event during + // the error boundary cleanup. + item.isMounted = false; + var isRoot = item.parentID === 0; + if (isRoot) { + removeRoot(id); + } } - }; + unmountedIDs.push(id); + }, - const getDisplayNameFromElement = function(element: ?ReactElement): string { - if (element == null) { - return '#empty'; - } else if (typeof element === 'string' || typeof element === 'number') { - return '#text'; - } else if (typeof element.type === 'string') { - return element.type; - } else { - return element.type.displayName || element.type.name || 'Unknown'; + purgeUnmountedComponents(): void { + if (ReactComponentTreeHook._preventPurging) { + // Should only be used for testing. + return; } - }; - const getDisplayName = function(id: DebugID): ?string { - var element = getElement(id); + for (var i = 0; i < unmountedIDs.length; i++) { + var id = unmountedIDs[i]; + purgeDeep(id); + } + unmountedIDs.length = 0; + }, + + isMounted(id: DebugID): boolean { + var item = getItem(id); + return item ? item.isMounted : false; + }, + + getCurrentStackAddendum(topElement: ?ReactElement): string { + var info = ''; + if (topElement) { + var name = getDisplayName(topElement); + var owner = topElement._owner; + info += describeComponentFrame( + name, + topElement._source, + owner && getComponentName(owner) + ); + } + + var currentOwner = ReactCurrentOwner.current; + if (currentOwner) { + if (typeof currentOwner.tag === 'number') { + const workInProgress = ((currentOwner : any) : Fiber); + // Safe because if current owner exists, we are reconciling, + // and it is guaranteed to be the work-in-progress version. + info += getStackAddendumByWorkInProgressFiber(workInProgress); + } else if (typeof currentOwner._debugID === 'number') { + info += ReactComponentTreeHook.getStackAddendumByID(currentOwner._debugID); + } + } + return info; + }, + + getStackAddendumByID(id: ?DebugID): string { + var info = ''; + while (id) { + info += describeID(id); + id = ReactComponentTreeHook.getParentID(id); + } + return info; + }, + + getChildIDs(id: DebugID): Array { + var item = getItem(id); + return item ? item.childIDs : []; + }, + + getDisplayName(id: DebugID): ?string { + var element = ReactComponentTreeHook.getElement(id); if (!element) { return null; } - return getDisplayNameFromElement(element); - }; + return getDisplayName(element); + }, - const getOwnerID = function(id: DebugID): ?DebugID { - var element = getElement(id); + getElement(id: DebugID): ?ReactElement { + var item = getItem(id); + return item ? item.element : null; + }, + + getOwnerID(id: DebugID): ?DebugID { + var element = ReactComponentTreeHook.getElement(id); if (!element || !element._owner) { return null; } return element._owner._debugID; - }; + }, - const describeID = function(id: DebugID): string { - var name = getDisplayName(id); - var element = getElement(id); - var ownerID = getOwnerID(id); - var ownerName; - if (ownerID) { - ownerName = getDisplayName(ownerID); - } - warning( - element, - 'ReactComponentTreeHook: Missing React element for debugID %s when ' + - 'building stack', - id - ); - return describeComponentFrame(name, element && element._source, ownerName); - }; - - const getElement = function(id: DebugID): ?ReactElement { - var item = getItem(id); - return item ? item.element : null; - }; - - const getParentID = function(id: DebugID): ?DebugID { + getParentID(id: DebugID): ?DebugID { var item = getItem(id); return item ? item.parentID : null; - }; + }, - const getStackAddendumByID = function(id: ?DebugID): string { - var info = ''; - while (id) { - info += describeID(id); - id = getParentID(id); + getSource(id: DebugID): ?Source { + var item = getItem(id); + var element = item ? item.element : null; + var source = element != null ? element._source : null; + return source; + }, + + getText(id: DebugID): ?string { + var element = ReactComponentTreeHook.getElement(id); + if (typeof element === 'string') { + return element; + } else if (typeof element === 'number') { + return '' + element; + } else { + return null; } - return info; - }; + }, - ReactComponentTreeHook = Object.assign({}, ReactComponentTreeHook, { - onSetChildren(id: DebugID, nextChildIDs: Array): void { - var item = getItem(id); - invariant(item, 'Item must have been set'); - item.childIDs = nextChildIDs; + getUpdateCount(id: DebugID): number { + var item = getItem(id); + return item ? item.updateCount : 0; + }, - for (var i = 0; i < nextChildIDs.length; i++) { - var nextChildID = nextChildIDs[i]; - var nextChild = getItem(nextChildID); - invariant( - nextChild, - 'Expected hook events to fire for the child ' + - 'before its parent includes it in onSetChildren().' - ); - invariant( - nextChild.childIDs != null || - typeof nextChild.element !== 'object' || - nextChild.element == null, - 'Expected onSetChildren() to fire for a container child ' + - 'before its parent includes it in onSetChildren().' - ); - invariant( - nextChild.isMounted, - 'Expected onMountComponent() to fire for the child ' + - 'before its parent includes it in onSetChildren().' - ); - if (nextChild.parentID == null) { - nextChild.parentID = id; - // TODO: This shouldn't be necessary but mounting a new root during in - // componentWillMount currently causes not-yet-mounted components to - // be purged from our tree data so their parent id is missing. - } - invariant( - nextChild.parentID === id, - 'Expected onBeforeMountComponent() parent and onSetChildren() to ' + - 'be consistent (%s has parents %s and %s).', - nextChildID, - nextChild.parentID, - id - ); - } - }, - - onBeforeMountComponent(id: DebugID, element: ReactElement, parentID: DebugID): void { - var item = { - element, - parentID, - text: null, - childIDs: [], - isMounted: false, - updateCount: 0, - }; - setItem(id, item); - }, - - onBeforeUpdateComponent(id: DebugID, element: ReactElement): void { - var item = getItem(id); - if (!item || !item.isMounted) { - // We may end up here as a result of setState() in componentWillUnmount(). - // In this case, ignore the element. - return; - } - item.element = element; - }, - - onMountComponent(id: DebugID): void { - var item = getItem(id); - invariant(item, 'Item must have been set'); - item.isMounted = true; - var isRoot = item.parentID === 0; - if (isRoot) { - addRoot(id); - } - }, - - onUpdateComponent(id: DebugID): void { - var item = getItem(id); - if (!item || !item.isMounted) { - // We may end up here as a result of setState() in componentWillUnmount(). - // In this case, ignore the element. - return; - } - item.updateCount++; - }, - - onUnmountComponent(id: DebugID): void { - var item = getItem(id); - if (item) { - // We need to check if it exists. - // `item` might not exist if it is inside an error boundary, and a sibling - // error boundary child threw while mounting. Then this instance never - // got a chance to mount, but it still gets an unmounting event during - // the error boundary cleanup. - item.isMounted = false; - var isRoot = item.parentID === 0; - if (isRoot) { - removeRoot(id); - } - } - unmountedIDs.push(id); - }, - - purgeUnmountedComponents(): void { - if (ReactComponentTreeHook._preventPurging) { - // Should only be used for testing. - return; - } - - for (var i = 0; i < unmountedIDs.length; i++) { - var id = unmountedIDs[i]; - purgeDeep(id); - } - unmountedIDs.length = 0; - }, - - isMounted(id: DebugID): boolean { - var item = getItem(id); - return item ? item.isMounted : false; - }, - - getCurrentStackAddendum(topElement: ?ReactElement): string { - var info = ''; - if (topElement) { - var name = getDisplayNameFromElement(topElement); - var owner = topElement._owner; - info += describeComponentFrame( - name, - topElement._source, - owner && getComponentName(owner) - ); - } - - var currentOwner = ReactCurrentOwner.current; - if (currentOwner) { - if (typeof currentOwner.tag === 'number') { - const workInProgress = ((currentOwner : any) : Fiber); - // Safe because if current owner exists, we are reconciling, - // and it is guaranteed to be the work-in-progress version. - info += ReactComponentTreeHook.getStackAddendumByWorkInProgressFiber(workInProgress); - } else if (typeof currentOwner._debugID === 'number') { - info += getStackAddendumByID(currentOwner._debugID); - } - } - return info; - }, - - getStackAddendumByID, - - getChildIDs(id: DebugID): Array { - var item = getItem(id); - return item ? item.childIDs : []; - }, - - getDisplayName(id: DebugID): ?string { - var element = getElement(id); - if (!element) { - return null; - } - return getDisplayNameFromElement(element); - }, - - getElement, - - getOwnerID, - - getParentID, - - getSource(id: DebugID): ?Source { - var item = getItem(id); - var element = item ? item.element : null; - var source = element != null ? element._source : null; - return source; - }, - - getText(id: DebugID): ?string { - var element = getElement(id); - if (typeof element === 'string') { - return element; - } else if (typeof element === 'number') { - return '' + element; - } else { - return null; - } - }, - - getUpdateCount(id: DebugID): number { - var item = getItem(id); - return item ? item.updateCount : 0; - }, - - getRootIDs, - getRegisteredIDs: getItemIDs, - }); -} + getRootIDs, + getRegisteredIDs: getItemIDs, +}; module.exports = ReactComponentTreeHook; diff --git a/src/renderers/dom/shared/hooks/ReactDOMInvalidARIAHook.js b/src/renderers/dom/shared/hooks/ReactDOMInvalidARIAHook.js index 8dc38050b1..31933d9d86 100644 --- a/src/renderers/dom/shared/hooks/ReactDOMInvalidARIAHook.js +++ b/src/renderers/dom/shared/hooks/ReactDOMInvalidARIAHook.js @@ -12,7 +12,6 @@ 'use strict'; var DOMProperty = require('DOMProperty'); -var ReactComponentTreeHook = require('react/lib/ReactComponentTreeHook'); var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber'); var warning = require('fbjs/lib/warning'); @@ -20,10 +19,16 @@ var warning = require('fbjs/lib/warning'); var warnedProperties = {}; var rARIA = new RegExp('^(aria)-[' + DOMProperty.ATTRIBUTE_NAME_CHAR + ']*$'); +if (__DEV__) { + var { + getStackAddendumByID, + } = require('react/lib/ReactComponentTreeHook'); +} + function getStackAddendum(debugID) { if (debugID != null) { // This can only happen on Stack - return ReactComponentTreeHook.getStackAddendumByID(debugID); + return getStackAddendumByID && getStackAddendumByID(debugID); } else { // This can only happen on Fiber return ReactDebugCurrentFiber.getCurrentFiberStackAddendum(); diff --git a/src/renderers/dom/shared/hooks/ReactDOMNullInputValuePropHook.js b/src/renderers/dom/shared/hooks/ReactDOMNullInputValuePropHook.js index 24b6b64f7f..a8f9a01917 100644 --- a/src/renderers/dom/shared/hooks/ReactDOMNullInputValuePropHook.js +++ b/src/renderers/dom/shared/hooks/ReactDOMNullInputValuePropHook.js @@ -11,17 +11,21 @@ 'use strict'; -var ReactComponentTreeHook = require('react/lib/ReactComponentTreeHook'); var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber'); - var warning = require('fbjs/lib/warning'); +if (__DEV__) { + var { + getStackAddendumByID, + } = require('react/lib/ReactComponentTreeHook'); +} + var didWarnValueNull = false; function getStackAddendum(debugID) { if (debugID != null) { // This can only happen on Stack - return ReactComponentTreeHook.getStackAddendumByID(debugID); + return getStackAddendumByID && getStackAddendumByID(debugID); } else { // This can only happen on Fiber return ReactDebugCurrentFiber.getCurrentFiberStackAddendum(); diff --git a/src/renderers/shared/ReactDebugTool.js b/src/renderers/shared/ReactDebugTool.js index b8a3ae7e6e..6a3781f947 100644 --- a/src/renderers/shared/ReactDebugTool.js +++ b/src/renderers/shared/ReactDebugTool.js @@ -14,6 +14,7 @@ var ReactInvalidSetStateWarningHook = require('ReactInvalidSetStateWarningHook'); var ReactHostOperationHistoryHook = require('ReactHostOperationHistoryHook'); +var ReactComponentTreeHook = require('react/lib/ReactComponentTreeHook'); var ExecutionEnvironment = require('fbjs/lib/ExecutionEnvironment'); var performanceNow = require('fbjs/lib/performanceNow'); @@ -22,7 +23,6 @@ var warning = require('fbjs/lib/warning'); import type { ReactElement } from 'ReactElementType'; import type { DebugID } from 'ReactInstanceType'; import type { Operation } from 'ReactHostOperationHistoryHook'; -import type { ComponentTreeHookDevType } from 'ReactComponentTreeHook'; type Hook = any; @@ -67,20 +67,8 @@ export type FlushHistory = Array; var ReactDebugTool = ((null: any): typeof ReactDebugTool); if (__DEV__) { - const hooks = []; - const didHookThrowForEvent = {}; - const ReactComponentTreeHook: ComponentTreeHookDevType = (require('react/lib/ReactComponentTreeHook'): any); - const { - purgeUnmountedComponents, - getOwnerID, - getParentID, - getDisplayName, - getText, - getUpdateCount, - getChildIDs, - getRegisteredIDs, - getElement, - } = ReactComponentTreeHook; + var hooks = []; + var didHookThrowForEvent = {}; const callHook = function(event, fn, context, arg1, arg2, arg3, arg4, arg5) { try { @@ -120,24 +108,22 @@ if (__DEV__) { var lifeCycleTimerHasWarned = false; const clearHistory = function() { - if (purgeUnmountedComponents) { - purgeUnmountedComponents(); - } + ReactComponentTreeHook.purgeUnmountedComponents(); ReactHostOperationHistoryHook.clearHistory(); }; const getTreeSnapshot = function(registeredIDs) { return registeredIDs.reduce((tree, id) => { - var ownerID = getOwnerID(id); - var parentID = getParentID(id); + var ownerID = ReactComponentTreeHook.getOwnerID(id); + var parentID = ReactComponentTreeHook.getParentID(id); tree[id] = { - displayName: getDisplayName(id), - text: getText(id), - updateCount: getUpdateCount(id), - childIDs: getChildIDs(id), + displayName: ReactComponentTreeHook.getDisplayName(id), + text: ReactComponentTreeHook.getText(id), + updateCount: ReactComponentTreeHook.getUpdateCount(id), + childIDs: ReactComponentTreeHook.getChildIDs(id), // Text nodes don't have owners but this is close enough. ownerID: ownerID || - parentID && getOwnerID(parentID) || + parentID && ReactComponentTreeHook.getOwnerID(parentID) || 0, parentID, }; @@ -158,16 +144,13 @@ if (__DEV__) { } if (previousMeasurements.length || previousOperations.length) { - if (getRegisteredIDs) { - var registeredIDs = getRegisteredIDs(); - - flushHistory.push({ - duration: performanceNow() - previousStartTime, - measurements: previousMeasurements || [], - operations: previousOperations || [], - treeSnapshot: getTreeSnapshot(registeredIDs), - }); - } + var registeredIDs = ReactComponentTreeHook.getRegisteredIDs(); + flushHistory.push({ + duration: performanceNow() - previousStartTime, + measurements: previousMeasurements || [], + operations: previousOperations || [], + treeSnapshot: getTreeSnapshot(registeredIDs), + }); } clearHistory(); @@ -270,7 +253,7 @@ if (__DEV__) { if (!isProfiling || !canUsePerformanceMeasure) { return false; } - var element = getElement && getElement(debugID); + var element = ReactComponentTreeHook.getElement(debugID); if (element == null || typeof element !== 'object') { return false; } @@ -297,7 +280,7 @@ if (__DEV__) { } var markName = `${debugID}::${markType}`; - var displayName = getDisplayName && getDisplayName(debugID) || 'Unknown'; + var displayName = ReactComponentTreeHook.getDisplayName(debugID) || 'Unknown'; // Chrome has an issue of dropping markers recorded too fast: // https://bugs.chromium.org/p/chromium/issues/detail?id=640652 diff --git a/src/renderers/shared/fiber/ReactDebugCurrentFiber.js b/src/renderers/shared/fiber/ReactDebugCurrentFiber.js index 33ad128f72..539c19e085 100644 --- a/src/renderers/shared/fiber/ReactDebugCurrentFiber.js +++ b/src/renderers/shared/fiber/ReactDebugCurrentFiber.js @@ -18,7 +18,7 @@ type LifeCyclePhase = 'render' | 'getChildContext'; if (__DEV__) { var getComponentName = require('getComponentName'); - var { getStackAddendumByWorkInProgressFiber } = require('react/lib/ReactComponentTreeHook'); + var { getStackAddendumByWorkInProgressFiber } = require('ReactFiberComponentTreeHook'); } function getCurrentFiberOwnerName() : string | null { diff --git a/src/renderers/shared/fiber/ReactFiberScheduler.js b/src/renderers/shared/fiber/ReactFiberScheduler.js index cacaacdf83..256f882045 100644 --- a/src/renderers/shared/fiber/ReactFiberScheduler.js +++ b/src/renderers/shared/fiber/ReactFiberScheduler.js @@ -37,7 +37,7 @@ var { const { reset } = require('ReactFiberStack'); var { getStackAddendumByWorkInProgressFiber, -} = require('react/lib/ReactComponentTreeHook'); +} = require('ReactFiberComponentTreeHook'); var { logCapturedError } = require('ReactFiberErrorLogger'); var { invokeGuardedCallback } = require('ReactErrorUtils'); diff --git a/src/renderers/shared/stack/reconciler/ReactRef.js b/src/renderers/shared/stack/reconciler/ReactRef.js index 6705ec9aa3..120c6dddbd 100644 --- a/src/renderers/shared/stack/reconciler/ReactRef.js +++ b/src/renderers/shared/stack/reconciler/ReactRef.js @@ -16,15 +16,12 @@ var ReactOwner = require('ReactOwner'); import type { ReactInstance } from 'ReactInstanceType'; import type { ReactElement } from 'ReactElementType'; -import type { ComponentTreeHookDevType } from 'ReactComponentTreeHook'; var ReactRef = {}; if (__DEV__) { var ReactCompositeComponentTypes = require('ReactCompositeComponentTypes'); - var { - getStackAddendumByID, - }: ComponentTreeHookDevType = (require('react/lib/ReactComponentTreeHook'): any); + var ReactComponentTreeHook = require('react/lib/ReactComponentTreeHook'); var warning = require('fbjs/lib/warning'); var warnedAboutStatelessRefs = {}; @@ -49,14 +46,14 @@ function attachRef(ref, component, owner) { if (element && element._source) { warningKey = element._source.fileName + ':' + element._source.lineNumber; } - if (!warnedAboutStatelessRefs[warningKey] && getStackAddendumByID) { + if (!warnedAboutStatelessRefs[warningKey]) { warnedAboutStatelessRefs[warningKey] = true; warning( false, 'Stateless function components cannot be given refs. ' + 'Attempts to access this ref will fail.%s%s', info, - getStackAddendumByID(component._debugID) + ReactComponentTreeHook.getStackAddendumByID(component._debugID) ); } } diff --git a/src/shared/ReactFiberComponentTreeHook.js b/src/shared/ReactFiberComponentTreeHook.js new file mode 100644 index 0000000000..51cffdb37f --- /dev/null +++ b/src/shared/ReactFiberComponentTreeHook.js @@ -0,0 +1,77 @@ +/** + * Copyright 2016-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. + * + * @flow + * @providesModule ReactFiberComponentTreeHook + */ + +'use strict'; + +var ReactTypeOfWork = require('ReactTypeOfWork'); +var { + IndeterminateComponent, + FunctionalComponent, + ClassComponent, + HostComponent, +} = ReactTypeOfWork; +var getComponentName = require('getComponentName'); + +import type { Fiber } from 'ReactFiber'; +import type { Source } from 'ReactElementType'; + +function describeComponentFrame(name, source: Source, ownerName) { + return '\n in ' + (name || 'Unknown') + ( + source ? + ' (at ' + source.fileName.replace(/^.*[\\\/]/, '') + ':' + + source.lineNumber + ')' : + ownerName ? + ' (created by ' + ownerName + ')' : + '' + ); +} + +function describeFiber(fiber : Fiber) : string { + switch (fiber.tag) { + case IndeterminateComponent: + case FunctionalComponent: + case ClassComponent: + case HostComponent: + var owner = fiber._debugOwner; + var source = fiber._debugSource; + var name = getComponentName(fiber); + var ownerName = null; + if (owner) { + ownerName = getComponentName(owner); + } + if (source) { + return describeComponentFrame(name, source, ownerName); + } + return ''; + default: + return ''; + } +} + +// This function can only be called with a work-in-progress fiber and +// only during begin or complete phase. Do not call it under any other +// circumstances. +function getStackAddendumByWorkInProgressFiber(workInProgress : Fiber) : string { + var info = ''; + var node = workInProgress; + do { + info += describeFiber(node); + // Otherwise this return pointer might point to the wrong tree: + node = node.return; + } while (node); + return info; +} + +module.exports = { + getStackAddendumByWorkInProgressFiber, + describeComponentFrame, +}; diff --git a/src/shared/utils/flattenChildren.js b/src/shared/utils/flattenChildren.js index a5a3d85bb5..d07630580f 100644 --- a/src/shared/utils/flattenChildren.js +++ b/src/shared/utils/flattenChildren.js @@ -12,8 +12,6 @@ 'use strict'; -import type { ComponentTreeHookDevType } from 'ReactComponentTreeHook'; - var KeyEscapeUtils = require('KeyEscapeUtils'); var traverseAllChildren = require('traverseAllChildren'); var warning = require('fbjs/lib/warning'); @@ -53,16 +51,14 @@ function flattenSingleChildIntoContext( if (!ReactComponentTreeHook) { ReactComponentTreeHook = require('react/lib/ReactComponentTreeHook'); } - const { getStackAddendumByID }: ComponentTreeHookDevType = (ReactComponentTreeHook: any); - - if (!keyUnique && getStackAddendumByID) { + if (!keyUnique) { warning( false, 'flattenChildren(...): Encountered two children with the same key, ' + '`%s`. Child keys must be unique; when two children share a key, only ' + 'the first child will be used.%s', KeyEscapeUtils.unescape(name), - getStackAddendumByID(selfDebugID) + ReactComponentTreeHook.getStackAddendumByID(selfDebugID) ); } } diff --git a/src/umd/ReactUMDEntry.js b/src/umd/ReactUMDEntry.js index 8cacdceb35..ab926354a6 100644 --- a/src/umd/ReactUMDEntry.js +++ b/src/umd/ReactUMDEntry.js @@ -17,8 +17,17 @@ var React = require('React'); var ReactUMDEntry = Object.assign({ __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: { ReactCurrentOwner: require('react/lib/ReactCurrentOwner'), - ReactComponentTreeHook: require('react/lib/ReactComponentTreeHook'), }, }, React); +if (__DEV__) { + Object.assign( + ReactUMDEntry.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED, + { + // ReactComponentTreeHook should not be included in production. + ReactComponentTreeHook: require('react/lib/ReactComponentTreeHook'), + } + ); +} + module.exports = ReactUMDEntry; From af4d249aa907139e41801b081e14da351ef25485 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Mon, 6 Mar 2017 15:15:18 +0000 Subject: [PATCH 20/32] fixes tests so they correctly pass in fiber --- scripts/fiber/tests-passing-except-dev.txt | 58 ------------------- .../hooks/ReactComponentTreeHook.js | 5 +- .../__tests__/ReactComponentTreeHook-test.js | 2 +- src/shared/ReactFiberComponentTreeHook.js | 8 +-- 4 files changed, 4 insertions(+), 69 deletions(-) diff --git a/scripts/fiber/tests-passing-except-dev.txt b/scripts/fiber/tests-passing-except-dev.txt index bd22db6cba..95cad08849 100644 --- a/scripts/fiber/tests-passing-except-dev.txt +++ b/scripts/fiber/tests-passing-except-dev.txt @@ -1,61 +1,3 @@ -src/renderers/__tests__/ReactComponentTreeHook-test.js -* uses displayName or Unknown for classic components -* uses displayName, name, or ReactComponent for modern components -* uses displayName, name, or Object for factory components -* uses displayName, name, or StatelessComponent for functional components -* reports a host tree correctly -* reports a simple tree with composites correctly -* reports a tree with composites correctly -* ignores null children -* ignores false children -* reports text nodes as children -* reports a single text node as a child -* reports a single number node as a child -* reports a zero as a child -* skips empty nodes for multiple children -* reports html content as no children -* updates text of a single text child -* updates from no children to a single text child -* updates from a single text child to no children -* updates from html content to a single text child -* updates from a single text child to html content -* updates from no children to multiple text children -* updates from multiple text children to no children -* updates from html content to multiple text children -* updates from multiple text children to html content -* updates from html content to no children -* updates from no children to html content -* updates from one text child to multiple text children -* updates from multiple text children to one text child -* updates text nodes when reordering -* updates host nodes when reordering with keys -* updates host nodes when reordering without keys -* updates a single composite child of a different type -* updates a single composite child of the same type -* updates from no children to a single composite child -* updates from a single composite child to no children -* updates mixed children -* updates with a host child -* updates from null to a host child -* updates from a host child to null -* updates from a host child to a composite child -* updates from a composite child to a host child -* updates from null to a composite child -* updates from a composite child to null -* updates with a host child -* updates from null to a host child -* updates from a host child to null -* updates from a host child to a composite child -* updates from a composite child to a host child -* updates from null to a composite child -* updates from a composite child to null -* tracks owner correctly -* purges unmounted components automatically -* reports update counts -* does not report top-level wrapper as a root -* registers inlined text nodes -* works - src/renderers/__tests__/ReactComponentTreeHook-test.native.js * uses displayName or Unknown for classic components * uses displayName, name, or ReactComponent for modern components diff --git a/src/isomorphic/hooks/ReactComponentTreeHook.js b/src/isomorphic/hooks/ReactComponentTreeHook.js index 9fede1ac22..332c5cac8c 100644 --- a/src/isomorphic/hooks/ReactComponentTreeHook.js +++ b/src/isomorphic/hooks/ReactComponentTreeHook.js @@ -182,10 +182,7 @@ function describeID(id: DebugID): string { 'building stack', id ); - if (element && name) { - return describeComponentFrame(name || '', element._source, ownerName || ''); - } - return ''; + return describeComponentFrame(name || '', element && element._source, ownerName || ''); } var ReactComponentTreeHook = { diff --git a/src/renderers/__tests__/ReactComponentTreeHook-test.js b/src/renderers/__tests__/ReactComponentTreeHook-test.js index a9c19c13eb..0e92d9bfdf 100644 --- a/src/renderers/__tests__/ReactComponentTreeHook-test.js +++ b/src/renderers/__tests__/ReactComponentTreeHook-test.js @@ -1736,7 +1736,7 @@ describe('ReactComponentTreeHook', () => { expectDev(ReactComponentTreeTestUtils.getRegisteredDisplayNames()).toEqual([]); }); - describe('stack addenda', () => { + describe.only('stack addenda', () => { it('gets created', () => { function getAddendum(element) { var addendum = ReactComponentTreeHook.getCurrentStackAddendum(element); diff --git a/src/shared/ReactFiberComponentTreeHook.js b/src/shared/ReactFiberComponentTreeHook.js index 51cffdb37f..df24a78b0d 100644 --- a/src/shared/ReactFiberComponentTreeHook.js +++ b/src/shared/ReactFiberComponentTreeHook.js @@ -22,9 +22,8 @@ var { var getComponentName = require('getComponentName'); import type { Fiber } from 'ReactFiber'; -import type { Source } from 'ReactElementType'; -function describeComponentFrame(name, source: Source, ownerName) { +function describeComponentFrame(name, source: any, ownerName) { return '\n in ' + (name || 'Unknown') + ( source ? ' (at ' + source.fileName.replace(/^.*[\\\/]/, '') + ':' + @@ -48,10 +47,7 @@ function describeFiber(fiber : Fiber) : string { if (owner) { ownerName = getComponentName(owner); } - if (source) { - return describeComponentFrame(name, source, ownerName); - } - return ''; + return describeComponentFrame(name, source, ownerName); default: return ''; } From f992d5415752da7413b2821783407762fd9e29f5 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Mon, 6 Mar 2017 15:18:21 +0000 Subject: [PATCH 21/32] removed describe.only --- scripts/fiber/tests-passing-except-dev.txt | 58 +++++++++++++++++++ .../__tests__/ReactComponentTreeHook-test.js | 2 +- 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/scripts/fiber/tests-passing-except-dev.txt b/scripts/fiber/tests-passing-except-dev.txt index 95cad08849..bd22db6cba 100644 --- a/scripts/fiber/tests-passing-except-dev.txt +++ b/scripts/fiber/tests-passing-except-dev.txt @@ -1,3 +1,61 @@ +src/renderers/__tests__/ReactComponentTreeHook-test.js +* uses displayName or Unknown for classic components +* uses displayName, name, or ReactComponent for modern components +* uses displayName, name, or Object for factory components +* uses displayName, name, or StatelessComponent for functional components +* reports a host tree correctly +* reports a simple tree with composites correctly +* reports a tree with composites correctly +* ignores null children +* ignores false children +* reports text nodes as children +* reports a single text node as a child +* reports a single number node as a child +* reports a zero as a child +* skips empty nodes for multiple children +* reports html content as no children +* updates text of a single text child +* updates from no children to a single text child +* updates from a single text child to no children +* updates from html content to a single text child +* updates from a single text child to html content +* updates from no children to multiple text children +* updates from multiple text children to no children +* updates from html content to multiple text children +* updates from multiple text children to html content +* updates from html content to no children +* updates from no children to html content +* updates from one text child to multiple text children +* updates from multiple text children to one text child +* updates text nodes when reordering +* updates host nodes when reordering with keys +* updates host nodes when reordering without keys +* updates a single composite child of a different type +* updates a single composite child of the same type +* updates from no children to a single composite child +* updates from a single composite child to no children +* updates mixed children +* updates with a host child +* updates from null to a host child +* updates from a host child to null +* updates from a host child to a composite child +* updates from a composite child to a host child +* updates from null to a composite child +* updates from a composite child to null +* updates with a host child +* updates from null to a host child +* updates from a host child to null +* updates from a host child to a composite child +* updates from a composite child to a host child +* updates from null to a composite child +* updates from a composite child to null +* tracks owner correctly +* purges unmounted components automatically +* reports update counts +* does not report top-level wrapper as a root +* registers inlined text nodes +* works + src/renderers/__tests__/ReactComponentTreeHook-test.native.js * uses displayName or Unknown for classic components * uses displayName, name, or ReactComponent for modern components diff --git a/src/renderers/__tests__/ReactComponentTreeHook-test.js b/src/renderers/__tests__/ReactComponentTreeHook-test.js index 0e92d9bfdf..a9c19c13eb 100644 --- a/src/renderers/__tests__/ReactComponentTreeHook-test.js +++ b/src/renderers/__tests__/ReactComponentTreeHook-test.js @@ -1736,7 +1736,7 @@ describe('ReactComponentTreeHook', () => { expectDev(ReactComponentTreeTestUtils.getRegisteredDisplayNames()).toEqual([]); }); - describe.only('stack addenda', () => { + describe('stack addenda', () => { it('gets created', () => { function getAddendum(element) { var addendum = ReactComponentTreeHook.getCurrentStackAddendum(element); From 678cb70da2750988dd048cb63aa8d94269b387d0 Mon Sep 17 00:00:00 2001 From: Ben Alpert Date: Mon, 6 Mar 2017 14:58:29 -0800 Subject: [PATCH 22/32] Fix radio buttons using stale props in Fiber (#9126) D4662269 --- src/renderers/dom/fiber/wrappers/ReactDOMFiberInput.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/renderers/dom/fiber/wrappers/ReactDOMFiberInput.js b/src/renderers/dom/fiber/wrappers/ReactDOMFiberInput.js index 36be73bac9..d9e2467a9c 100644 --- a/src/renderers/dom/fiber/wrappers/ReactDOMFiberInput.js +++ b/src/renderers/dom/fiber/wrappers/ReactDOMFiberInput.js @@ -292,16 +292,16 @@ function updateNamedCousins(rootNode, props) { // and the same name are rendered into the same form (same as #1939). // That's probably okay; we don't support it just as we don't support // mixing React radio buttons with non-React ones. - var otherInstance = ReactDOMComponentTree.getInstanceFromNode(otherNode); + var otherProps = ReactDOMComponentTree.getFiberCurrentPropsFromNode(otherNode); invariant( - otherInstance, + otherProps, 'ReactDOMInput: Mixing React and non-React radio inputs with the ' + 'same `name` is not supported.' ); // If this is a controlled radio button group, forcing the input that // was previously checked to update will cause it to be come re-checked // as appropriate. - ReactDOMInput.updateWrapper(otherNode, otherInstance.memoizedProps); + ReactDOMInput.updateWrapper(otherNode, otherProps); } } } From bb149c817083d3b2d6473c2e8f4f89b14886b790 Mon Sep 17 00:00:00 2001 From: Vincent Taing Date: Mon, 6 Mar 2017 15:20:12 -0800 Subject: [PATCH 23/32] Fix redundant null type coercion (#9114) --- .../shared/stack/reconciler/ReactUpdateQueue.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/renderers/shared/stack/reconciler/ReactUpdateQueue.js b/src/renderers/shared/stack/reconciler/ReactUpdateQueue.js index 19aee0d985..8e77b3346e 100644 --- a/src/renderers/shared/stack/reconciler/ReactUpdateQueue.js +++ b/src/renderers/shared/stack/reconciler/ReactUpdateQueue.js @@ -134,8 +134,8 @@ var ReactUpdateQueue = { return; } - if (callback) { - callback = callback === undefined ? null : callback; + callback = callback === undefined ? null : callback; + if (callback !== null) { if (__DEV__) { warnOnInvalidCallback(callback, callerName); } @@ -174,8 +174,8 @@ var ReactUpdateQueue = { internalInstance._pendingStateQueue = [completeState]; internalInstance._pendingReplaceState = true; - if (callback) { - callback = callback === undefined ? null : callback; + callback = callback === undefined ? null : callback; + if (callback !== null) { if (__DEV__) { warnOnInvalidCallback(callback, callerName); } @@ -222,8 +222,8 @@ var ReactUpdateQueue = { (internalInstance._pendingStateQueue = []); queue.push(partialState); - if (callback) { - callback = callback === undefined ? null : callback; + callback = callback === undefined ? null : callback; + if (callback !== null) { if (__DEV__) { warnOnInvalidCallback(callback, callerName); } From d79c6ab995cc28868fd44ae46a792d624cb73edc Mon Sep 17 00:00:00 2001 From: Ben Alpert Date: Mon, 6 Mar 2017 16:21:43 -0800 Subject: [PATCH 24/32] Fiber DOM async feature flag (#9127) D4662171 --- src/renderers/dom/fiber/ReactDOMFiber.js | 2 +- src/renderers/dom/shared/ReactDOMFeatureFlags.js | 1 + src/renderers/shared/shared/event/EventPluginHub.js | 9 ++++++--- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/renderers/dom/fiber/ReactDOMFiber.js b/src/renderers/dom/fiber/ReactDOMFiber.js index cd0988f512..f1f61f1eb4 100644 --- a/src/renderers/dom/fiber/ReactDOMFiber.js +++ b/src/renderers/dom/fiber/ReactDOMFiber.js @@ -330,7 +330,7 @@ var DOMRenderer = ReactFiberReconciler({ scheduleDeferredCallback: ReactDOMFrameScheduling.rIC, - useSyncScheduling: true, + useSyncScheduling: !ReactDOMFeatureFlags.fiberAsyncScheduling, }); diff --git a/src/renderers/dom/shared/ReactDOMFeatureFlags.js b/src/renderers/dom/shared/ReactDOMFeatureFlags.js index 5e9d93267c..a53cc0dff5 100644 --- a/src/renderers/dom/shared/ReactDOMFeatureFlags.js +++ b/src/renderers/dom/shared/ReactDOMFeatureFlags.js @@ -12,6 +12,7 @@ 'use strict'; var ReactDOMFeatureFlags = { + fiberAsyncScheduling: false, useCreateElement: true, useFiber: false, }; diff --git a/src/renderers/shared/shared/event/EventPluginHub.js b/src/renderers/shared/shared/event/EventPluginHub.js index 6c4235ea42..179e89d651 100644 --- a/src/renderers/shared/shared/event/EventPluginHub.js +++ b/src/renderers/shared/shared/event/EventPluginHub.js @@ -126,9 +126,12 @@ var EventPluginHub = { // TODO: shouldPreventMouseEvent is DOM-specific and definitely should not // live here; needs to be moved to a better place soon if (typeof inst.tag === 'number') { - const props = EventPluginUtils.getFiberCurrentPropsFromNode( - inst.stateNode - ); + const stateNode = inst.stateNode; + if (!stateNode) { + // Work in progress (ex: onload events in incremental mode). + return null; + } + const props = EventPluginUtils.getFiberCurrentPropsFromNode(stateNode); if (!props) { // Work in progress. return null; From 6ca2140c73a6e811ed6d5cbd9014640d59884ff3 Mon Sep 17 00:00:00 2001 From: Brandon Dail Date: Mon, 6 Mar 2017 20:25:24 -0600 Subject: [PATCH 25/32] Remove _resetEventPlugins, use jest.resetModuleRegistry (#9111) * Move EventPluginRegistry._resetEventPlugins to DEV This is not called during runtime at all and is only using in internal unit tests. Despite that, its currently being included in production builds. This change makes it so its only defined in DEV, which might even be too much (we can maybe inject it in our unit tests to avoid defining it at all in EventPluginRegistry) * Remove _resetEventsPlugin, use resetModuleRegistry --- .../shared/event/EventPluginRegistry.js | 39 ------------------- .../__tests__/EventPluginRegistry-test.js | 2 +- 2 files changed, 1 insertion(+), 40 deletions(-) diff --git a/src/renderers/shared/shared/event/EventPluginRegistry.js b/src/renderers/shared/shared/event/EventPluginRegistry.js index effcd59939..b379ce1e9d 100644 --- a/src/renderers/shared/shared/event/EventPluginRegistry.js +++ b/src/renderers/shared/shared/event/EventPluginRegistry.js @@ -255,45 +255,6 @@ var EventPluginRegistry = { recomputePluginOrdering(); } }, - - /** - * Exposed for unit testing. - * @private - */ - _resetEventPlugins: function(): void { - eventPluginOrder = null; - for (var pluginName in namesToPlugins) { - if (namesToPlugins.hasOwnProperty(pluginName)) { - delete namesToPlugins[pluginName]; - } - } - EventPluginRegistry.plugins.length = 0; - - var eventNameDispatchConfigs = EventPluginRegistry.eventNameDispatchConfigs; - for (var eventName in eventNameDispatchConfigs) { - if (eventNameDispatchConfigs.hasOwnProperty(eventName)) { - delete eventNameDispatchConfigs[eventName]; - } - } - - var registrationNameModules = EventPluginRegistry.registrationNameModules; - for (var registrationName in registrationNameModules) { - if (registrationNameModules.hasOwnProperty(registrationName)) { - delete registrationNameModules[registrationName]; - } - } - - if (__DEV__) { - var possibleRegistrationNames = - EventPluginRegistry.possibleRegistrationNames; - for (var lowerCasedName in possibleRegistrationNames) { - if (possibleRegistrationNames.hasOwnProperty(lowerCasedName)) { - delete possibleRegistrationNames[lowerCasedName]; - } - } - } - }, - }; module.exports = EventPluginRegistry; diff --git a/src/renderers/shared/shared/event/__tests__/EventPluginRegistry-test.js b/src/renderers/shared/shared/event/__tests__/EventPluginRegistry-test.js index ed22b5c7de..01e872abb6 100644 --- a/src/renderers/shared/shared/event/__tests__/EventPluginRegistry-test.js +++ b/src/renderers/shared/shared/event/__tests__/EventPluginRegistry-test.js @@ -17,8 +17,8 @@ describe('EventPluginRegistry', () => { var createPlugin; beforeEach(() => { + jest.resetModuleRegistry(); EventPluginRegistry = require('EventPluginRegistry'); - EventPluginRegistry._resetEventPlugins(); createPlugin = function(properties) { return Object.assign({extractEvents: function() {}}, properties); From a159e85c469cb2ad336d6976eaf11cf12ccbde10 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Tue, 7 Mar 2017 13:35:41 +0000 Subject: [PATCH 26/32] removed inline boolean checks for methods --- src/isomorphic/classic/element/ReactElementValidator.js | 4 ++-- src/renderers/dom/shared/hooks/ReactDOMInvalidARIAHook.js | 2 +- .../dom/shared/hooks/ReactDOMNullInputValuePropHook.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/isomorphic/classic/element/ReactElementValidator.js b/src/isomorphic/classic/element/ReactElementValidator.js index 7f30253349..f2a699dc9e 100644 --- a/src/isomorphic/classic/element/ReactElementValidator.js +++ b/src/isomorphic/classic/element/ReactElementValidator.js @@ -124,7 +124,7 @@ function validateExplicitKey(element, parentType) { '%s%s See https://fb.me/react-warning-keys for more information.%s', currentComponentErrorInfo, childOwner, - getCurrentStackAddendum && getCurrentStackAddendum(element) + getCurrentStackAddendum(element) ); } @@ -227,7 +227,7 @@ var ReactElementValidator = { info += getDeclarationErrorAddendum(); } - info += getCurrentStackAddendum && getCurrentStackAddendum(); + info += getCurrentStackAddendum(); warning( false, diff --git a/src/renderers/dom/shared/hooks/ReactDOMInvalidARIAHook.js b/src/renderers/dom/shared/hooks/ReactDOMInvalidARIAHook.js index 31933d9d86..098023cde7 100644 --- a/src/renderers/dom/shared/hooks/ReactDOMInvalidARIAHook.js +++ b/src/renderers/dom/shared/hooks/ReactDOMInvalidARIAHook.js @@ -28,7 +28,7 @@ if (__DEV__) { function getStackAddendum(debugID) { if (debugID != null) { // This can only happen on Stack - return getStackAddendumByID && getStackAddendumByID(debugID); + return getStackAddendumByID(debugID); } else { // This can only happen on Fiber return ReactDebugCurrentFiber.getCurrentFiberStackAddendum(); diff --git a/src/renderers/dom/shared/hooks/ReactDOMNullInputValuePropHook.js b/src/renderers/dom/shared/hooks/ReactDOMNullInputValuePropHook.js index a8f9a01917..a1e8cfbe16 100644 --- a/src/renderers/dom/shared/hooks/ReactDOMNullInputValuePropHook.js +++ b/src/renderers/dom/shared/hooks/ReactDOMNullInputValuePropHook.js @@ -25,7 +25,7 @@ var didWarnValueNull = false; function getStackAddendum(debugID) { if (debugID != null) { // This can only happen on Stack - return getStackAddendumByID && getStackAddendumByID(debugID); + return getStackAddendumByID(debugID); } else { // This can only happen on Fiber return ReactDebugCurrentFiber.getCurrentFiberStackAddendum(); From 73378c92939d17706d1063d67d28f75459b47129 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 8 Mar 2017 18:28:53 +0000 Subject: [PATCH 27/32] [Fiber] Performance measurements (#9071) * wip * better * better * track commits * better * wip * Fix * Add some lifecycles * wip * Naming * Moar emojis * Remove stacks in favor of a flag * Fix Flow * Gate behind __DEV__ * Revert flag for testing * Measure all lifecycles * Push it to the limits * Polish * Indent * Refactor and track cascading updates * More prominent warnings * Make mark names themselves readable This is useful for RN Systrace which doesn't let us assign labels after the fact. * Keep track of how many effects we call * Fix typo * Do less work to reduce the overhead * Fix lint * Remove closure * Remove unintentional formatting changes * Add tests * Fix test regex and record tests * Disable irrelevant tests needed for ReactPerf * Fix typo * Fix lint and flow * Don't treat cWM or cWRP as cascading * Whitespace * Update tests * Gate callComponentWillUnmountWithTimerInDev definition by DEV --- package.json | 2 +- scripts/fiber/tests-failing.txt | 15 - scripts/fiber/tests-passing-except-dev.txt | 129 ----- scripts/fiber/tests-passing.txt | 55 +- .../__tests__/ReactComponentTreeHook-test.js | 501 +++++++++--------- .../ReactComponentTreeHook-test.native.js | 10 +- .../ReactHostOperationHistoryHook-test.js | 9 +- src/renderers/__tests__/ReactPerf-test.js | 7 +- .../shared/fiber/ReactDebugFiberPerf.js | 408 ++++++++++++++ src/renderers/shared/fiber/ReactFiber.js | 2 + .../shared/fiber/ReactFiberBeginWork.js | 10 + .../shared/fiber/ReactFiberClassComponent.js | 34 ++ .../shared/fiber/ReactFiberCommitWork.js | 35 +- .../shared/fiber/ReactFiberContext.js | 6 + .../shared/fiber/ReactFiberScheduler.js | 66 +++ .../__tests__/ReactIncrementalPerf-test.js | 487 +++++++++++++++++ .../ReactIncrementalPerf-test.js.snap | 260 +++++++++ 17 files changed, 1601 insertions(+), 435 deletions(-) create mode 100644 src/renderers/shared/fiber/ReactDebugFiberPerf.js create mode 100644 src/renderers/shared/fiber/__tests__/ReactIncrementalPerf-test.js create mode 100644 src/renderers/shared/fiber/__tests__/__snapshots__/ReactIncrementalPerf-test.js.snap diff --git a/package.json b/package.json index c91e902a6c..a236574feb 100644 --- a/package.json +++ b/package.json @@ -107,7 +107,7 @@ "./scripts/jest/environment.js" ], "setupTestFrameworkScriptFile": "./scripts/jest/test-framework-setup.js", - "testRegex": "/__tests__/", + "testRegex": "/__tests__/.*(\\.js|coffee|ts)$", "moduleFileExtensions": [ "js", "json", diff --git a/scripts/fiber/tests-failing.txt b/scripts/fiber/tests-failing.txt index 9c2bebbd84..325811e749 100644 --- a/scripts/fiber/tests-failing.txt +++ b/scripts/fiber/tests-failing.txt @@ -1,21 +1,6 @@ src/isomorphic/classic/__tests__/ReactContextValidator-test.js * should pass previous context to lifecycles -src/renderers/__tests__/ReactComponentTreeHook-test.js -* can be retrieved by ID - -src/renderers/__tests__/ReactHostOperationHistoryHook-test.js -* gets recorded during an update - -src/renderers/__tests__/ReactPerf-test.js -* should count no-op update as waste -* should count no-op update in child as waste -* should include stats for components unmounted during measurement -* should include lifecycle methods in measurements -* should include render time of functional components -* should not count time in a portal towards lifecycle method -* should work when measurement starts during reconciliation - src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js * gives source code refs for unknown prop warning (ssr) * gives source code refs for unknown prop warning for exact elements (ssr) diff --git a/scripts/fiber/tests-passing-except-dev.txt b/scripts/fiber/tests-passing-except-dev.txt index bd22db6cba..ea25216a39 100644 --- a/scripts/fiber/tests-passing-except-dev.txt +++ b/scripts/fiber/tests-passing-except-dev.txt @@ -1,132 +1,3 @@ -src/renderers/__tests__/ReactComponentTreeHook-test.js -* uses displayName or Unknown for classic components -* uses displayName, name, or ReactComponent for modern components -* uses displayName, name, or Object for factory components -* uses displayName, name, or StatelessComponent for functional components -* reports a host tree correctly -* reports a simple tree with composites correctly -* reports a tree with composites correctly -* ignores null children -* ignores false children -* reports text nodes as children -* reports a single text node as a child -* reports a single number node as a child -* reports a zero as a child -* skips empty nodes for multiple children -* reports html content as no children -* updates text of a single text child -* updates from no children to a single text child -* updates from a single text child to no children -* updates from html content to a single text child -* updates from a single text child to html content -* updates from no children to multiple text children -* updates from multiple text children to no children -* updates from html content to multiple text children -* updates from multiple text children to html content -* updates from html content to no children -* updates from no children to html content -* updates from one text child to multiple text children -* updates from multiple text children to one text child -* updates text nodes when reordering -* updates host nodes when reordering with keys -* updates host nodes when reordering without keys -* updates a single composite child of a different type -* updates a single composite child of the same type -* updates from no children to a single composite child -* updates from a single composite child to no children -* updates mixed children -* updates with a host child -* updates from null to a host child -* updates from a host child to null -* updates from a host child to a composite child -* updates from a composite child to a host child -* updates from null to a composite child -* updates from a composite child to null -* updates with a host child -* updates from null to a host child -* updates from a host child to null -* updates from a host child to a composite child -* updates from a composite child to a host child -* updates from null to a composite child -* updates from a composite child to null -* tracks owner correctly -* purges unmounted components automatically -* reports update counts -* does not report top-level wrapper as a root -* registers inlined text nodes -* works - -src/renderers/__tests__/ReactComponentTreeHook-test.native.js -* uses displayName or Unknown for classic components -* uses displayName, name, or ReactComponent for modern components -* uses displayName, name, or Object for factory components -* uses displayName, name, or StatelessComponent for functional components -* reports a host tree correctly -* reports a simple tree with composites correctly -* reports a tree with composites correctly -* ignores null children -* ignores false children -* reports text nodes as children -* reports a single text node as a child -* reports a single number node as a child -* reports a zero as a child -* skips empty nodes for multiple children -* updates text of a single text child -* updates from no children to a single text child -* updates from a single text child to no children -* updates from no children to multiple text children -* updates from multiple text children to no children -* updates from one text child to multiple text children -* updates from multiple text children to one text child -* updates text nodes when reordering -* updates host nodes when reordering with keys -* updates host nodes when reordering with keys -* updates a single composite child of a different type -* updates a single composite child of the same type -* updates from no children to a single composite child -* updates from a single composite child to no children -* updates mixed children -* updates with a host child -* updates from null to a host child -* updates from a host child to null -* updates from a host child to a composite child -* updates from a composite child to a host child -* updates from null to a composite child -* updates from a composite child to null -* updates with a host child -* updates from null to a host child -* updates from a host child to null -* updates from a host child to a composite child -* updates from a composite child to a host child -* updates from null to a composite child -* updates from a composite child to null -* tracks owner correctly -* purges unmounted components automatically -* reports update counts -* does not report top-level wrapper as a root - -src/renderers/__tests__/ReactHostOperationHistoryHook-test.js -* gets recorded for host roots -* gets recorded for composite roots -* gets recorded when a native is mounted deeply instead of null -* gets recorded during mount -* gets recorded during an update -* gets ignored if the styles are shallowly equal -* gets recorded during mount -* gets recorded during mount -* gets recorded during mount -* gets recorded during an update from text content -* gets recorded during an update from html -* gets recorded during an update from children -* gets recorded when composite renders to a different type -* gets recorded when composite renders to null after a native -* gets recorded during an update from text content -* gets recorded during an update from html -* gets recorded during an update from children -* gets reported when a child is inserted -* gets reported when a child is inserted -* gets reported when a child is removed - src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js * should not warn when server-side rendering `onScroll` * should warn about incorrect casing on properties (ssr) diff --git a/scripts/fiber/tests-passing.txt b/scripts/fiber/tests-passing.txt index c2cfe1b935..e6ffdc28ab 100644 --- a/scripts/fiber/tests-passing.txt +++ b/scripts/fiber/tests-passing.txt @@ -561,8 +561,6 @@ src/renderers/__tests__/ReactComponentLifeCycle-test.js src/renderers/__tests__/ReactComponentTreeHook-test.js * gets created -* is created during mounting -* is created when calling renderToString during render src/renderers/__tests__/ReactCompositeComponent-test.js * should support module pattern components @@ -673,17 +671,6 @@ src/renderers/__tests__/ReactErrorBoundaries-test.js * renders empty output if error boundary does not handle the error * passes first error when two errors happen in commit -src/renderers/__tests__/ReactHostOperationHistoryHook-test.js -* gets ignored for composite roots that return null -* gets recorded during an update -* gets recorded as a removal during an update -* gets recorded during an update -* gets recorded during an update -* gets ignored if new text is equal -* gets ignored if new text is equal -* gets ignored if the type has not changed -* gets ignored if new html is equal - src/renderers/__tests__/ReactIdentity-test.js * should allow key property to express identity * should use composite identity @@ -750,33 +737,6 @@ src/renderers/__tests__/ReactMultiChildText-test.js * should throw if rendering both HTML and children * should render between nested components and inline children -src/renderers/__tests__/ReactPerf-test.js -* should not count initial render as waste -* should not count unmount as waste -* should not count content update as waste -* should not count child addition as waste -* should not count child removal as waste -* should not count property update as waste -* should not count style update as waste -* should not count property removal as waste -* should not count raw HTML update as waste -* should not count child reordering as waste -* should not count text update as waste -* should not count replacing null with a host as waste -* should not count replacing a host with null as waste -* warns once when using getMeasurementsSummaryMap -* warns once when using printDOM -* returns isRunning state -* start has no effect when already running -* stop has no effect when already stopped -* should print console error only once -* should not print errant warnings if render() throws -* should not print errant warnings if componentWillMount() throws -* should not print errant warnings if componentDidMount() throws -* should not print errant warnings if portal throws in render() -* should not print errant warnings if portal throws in componentWillMount() -* should not print errant warnings if portal throws in componentDidMount() - src/renderers/__tests__/ReactStatelessComponent-test.js * should render stateless component * should update stateless component @@ -1565,6 +1525,21 @@ src/renderers/shared/fiber/__tests__/ReactIncrementalErrorHandling-test.js * should ignore errors thrown in log method to prevent cycle * should relay info about error boundary and retry attempts if applicable +src/renderers/shared/fiber/__tests__/ReactIncrementalPerf-test.js +* measures a simple reconciliation +* skips parents during setState +* warns on cascading renders from setState +* warns on cascading renders from top-level render +* does not treat setState from cWM or cWRP as cascading +* captures all lifecycles +* measures deprioritized work +* measures deferred work in chunks +* recovers from fatal errors +* recovers from caught errors +* deduplicates lifecycle names during commit to reduce overhead +* supports coroutines +* supports portals + src/renderers/shared/fiber/__tests__/ReactIncrementalReflection-test.js * handles isMounted even when the initial render is deferred * handles isMounted when an unmount is deferred diff --git a/src/renderers/__tests__/ReactComponentTreeHook-test.js b/src/renderers/__tests__/ReactComponentTreeHook-test.js index a9c19c13eb..da91ab2627 100644 --- a/src/renderers/__tests__/ReactComponentTreeHook-test.js +++ b/src/renderers/__tests__/ReactComponentTreeHook-test.js @@ -11,6 +11,9 @@ 'use strict'; +var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); +var describeStack = ReactDOMFeatureFlags.useFiber ? describe.skip : describe; + describe('ReactComponentTreeHook', () => { var React; var ReactDOM; @@ -30,6 +33,148 @@ describe('ReactComponentTreeHook', () => { ReactComponentTreeTestUtils = require('ReactComponentTreeTestUtils'); }); + // This is the only part used both by Stack and Fiber. + describe('stack addenda', () => { + it('gets created', () => { + function getAddendum(element) { + var addendum = ReactComponentTreeHook.getCurrentStackAddendum(element); + return addendum.replace(/\(at .+?:\d+\)/g, '(at **)'); + } + + var Anon = React.createClass({displayName: null, render: () => null}); + var Orange = React.createClass({render: () => null}); + + expectDev(getAddendum()).toBe( + '' + ); + expectDev(getAddendum(
)).toBe( + '\n in div (at **)' + ); + expectDev(getAddendum()).toBe( + '\n in Unknown (at **)' + ); + expectDev(getAddendum()).toBe( + '\n in Orange (at **)' + ); + expectDev(getAddendum(React.createElement(Orange))).toBe( + '\n in Orange' + ); + + var renders = 0; + var rOwnedByQ; + + function Q() { + return (rOwnedByQ = React.createElement(R)); + } + function R() { + return
; + } + class S extends React.Component { + componentDidMount() { + // Check that the parent path is still fetched when only S itself is on + // the stack. + this.forceUpdate(); + } + render() { + expectDev(getAddendum()).toBe( + '\n in S (at **)' + + '\n in div (at **)' + + '\n in R (created by Q)' + + '\n in Q (at **)' + ); + expectDev(getAddendum()).toBe( + '\n in span (at **)' + + '\n in S (at **)' + + '\n in div (at **)' + + '\n in R (created by Q)' + + '\n in Q (at **)' + ); + expectDev(getAddendum(React.createElement('span'))).toBe( + '\n in span (created by S)' + + '\n in S (at **)' + + '\n in div (at **)' + + '\n in R (created by Q)' + + '\n in Q (at **)' + ); + renders++; + return null; + } + } + ReactDOM.render(, document.createElement('div')); + expectDev(renders).toBe(2); + + // Make sure owner is fetched for the top element too. + expectDev(getAddendum(rOwnedByQ)).toBe( + '\n in R (created by Q)' + ); + }); + + // These are features and regression tests that only affect + // the Stack implementation of the stack addendum. + if (!ReactDOMFeatureFlags.useFiber) { + it('can be retrieved by ID', () => { + function getAddendum(id) { + var addendum = ReactComponentTreeHook.getStackAddendumByID(id); + return addendum.replace(/\(at .+?:\d+\)/g, '(at **)'); + } + + class Q extends React.Component { + render() { + return null; + } + } + + var q = ReactDOM.render(, document.createElement('div')); + expectDev(getAddendum(ReactInstanceMap.get(q)._debugID)).toBe( + '\n in Q (at **)' + ); + + spyOn(console, 'error'); + getAddendum(-17); + expectDev(console.error.calls.count()).toBe(1); + expectDev(console.error.calls.argsFor(0)[0]).toBe( + 'Warning: ReactComponentTreeHook: Missing React element for ' + + 'debugID -17 when building stack' + ); + }); + + it('is created during mounting', () => { + // https://github.com/facebook/react/issues/7187 + var el = document.createElement('div'); + var portalEl = document.createElement('div'); + class Foo extends React.Component { + componentWillMount() { + ReactDOM.render(
, portalEl); + } + render() { + return
; + } + } + ReactDOM.render(, el); + }); + + it('is created when calling renderToString during render', () => { + // https://github.com/facebook/react/issues/7190 + var el = document.createElement('div'); + class Foo extends React.Component { + render() { + return ( +
+
+ {ReactDOMServer.renderToString(
)} +
+
+ ); + } + } + ReactDOM.render(, el); + }); + } + }); + + // The rest of this file is not relevant for Fiber. + // TODO: remove tests below when we delete Stack. + function assertTreeMatches(pairs) { if (!Array.isArray(pairs[0])) { pairs = [pairs]; @@ -106,7 +251,7 @@ describe('ReactComponentTreeHook', () => { }); } - describe('mount', () => { + describeStack('mount', () => { it('uses displayName or Unknown for classic components', () => { class Foo extends React.Component { render() { @@ -520,7 +665,7 @@ describe('ReactComponentTreeHook', () => { }); }); - describe('update', () => { + describeStack('update', () => { describe('host component', () => { it('updates text of a single text child', () => { var elementBefore =
Hi.
; @@ -1601,276 +1746,144 @@ describe('ReactComponentTreeHook', () => { }); }); - it('tracks owner correctly', () => { - class Foo extends React.Component { - render() { - return

Hi.

; + describeStack('misc', () => { + it('tracks owner correctly', () => { + class Foo extends React.Component { + render() { + return

Hi.

; + } + } + function Bar({children}) { + return
{children} Mom
; } - } - function Bar({children}) { - return
{children} Mom
; - } - // Note that owner is not calculated for text nodes - // because they are not created from real elements. - var element =
; - var tree = { - displayName: 'article', - children: [{ - displayName: 'Foo', + // Note that owner is not calculated for text nodes + // because they are not created from real elements. + var element =
; + var tree = { + displayName: 'article', children: [{ - displayName: 'Bar', - ownerDisplayName: 'Foo', + displayName: 'Foo', children: [{ - displayName: 'div', - ownerDisplayName: 'Bar', + displayName: 'Bar', + ownerDisplayName: 'Foo', children: [{ - displayName: 'h1', - ownerDisplayName: 'Foo', + displayName: 'div', + ownerDisplayName: 'Bar', children: [{ + displayName: 'h1', + ownerDisplayName: 'Foo', + children: [{ + displayName: '#text', + text: 'Hi.', + }], + }, { displayName: '#text', - text: 'Hi.', + text: ' Mom', }], - }, { - displayName: '#text', - text: ' Mom', }], }], }], - }], - }; - assertTreeMatches([element, tree]); - }); - - it('purges unmounted components automatically', () => { - var node = document.createElement('div'); - var renderBar = true; - var fooInstance; - var barInstance; - - class Foo extends React.Component { - render() { - fooInstance = ReactInstanceMap.get(this); - return renderBar ? : null; - } - } - - class Bar extends React.Component { - render() { - barInstance = ReactInstanceMap.get(this); - return null; - } - } - - ReactDOM.render(, node); - ReactComponentTreeTestUtils.expectTree(barInstance._debugID, { - displayName: 'Bar', - parentDisplayName: 'Foo', - parentID: fooInstance._debugID, - children: [], - }, 'Foo'); - - renderBar = false; - ReactDOM.render(, node); - ReactDOM.render(, node); - ReactComponentTreeTestUtils.expectTree(barInstance._debugID, { - displayName: 'Unknown', - children: [], - parentID: null, - }, 'Foo'); - - ReactDOM.unmountComponentAtNode(node); - ReactComponentTreeTestUtils.expectTree(barInstance._debugID, { - displayName: 'Unknown', - children: [], - parentID: null, - }, 'Foo'); - }); - - it('reports update counts', () => { - var node = document.createElement('div'); - - ReactDOM.render(
, node); - var divID = ReactComponentTreeHook.getRootIDs()[0]; - expectDev(ReactComponentTreeHook.getUpdateCount(divID)).toEqual(0); - - ReactDOM.render(, node); - var spanID = ReactComponentTreeHook.getRootIDs()[0]; - expectDev(ReactComponentTreeHook.getUpdateCount(divID)).toEqual(0); - expectDev(ReactComponentTreeHook.getUpdateCount(spanID)).toEqual(0); - - ReactDOM.render(, node); - expectDev(ReactComponentTreeHook.getUpdateCount(divID)).toEqual(0); - expectDev(ReactComponentTreeHook.getUpdateCount(spanID)).toEqual(1); - - ReactDOM.render(, node); - expectDev(ReactComponentTreeHook.getUpdateCount(divID)).toEqual(0); - expectDev(ReactComponentTreeHook.getUpdateCount(spanID)).toEqual(2); - - ReactDOM.unmountComponentAtNode(node); - expectDev(ReactComponentTreeHook.getUpdateCount(divID)).toEqual(0); - expectDev(ReactComponentTreeHook.getUpdateCount(spanID)).toEqual(0); - }); - - it('does not report top-level wrapper as a root', () => { - var node = document.createElement('div'); - - ReactDOM.render(
, node); - expectDev(ReactComponentTreeTestUtils.getRootDisplayNames()).toEqual(['div']); - - ReactDOM.render(
, node); - expectDev(ReactComponentTreeTestUtils.getRootDisplayNames()).toEqual(['div']); - - ReactDOM.unmountComponentAtNode(node); - expectDev(ReactComponentTreeTestUtils.getRootDisplayNames()).toEqual([]); - expectDev(ReactComponentTreeTestUtils.getRegisteredDisplayNames()).toEqual([]); - }); - - it('registers inlined text nodes', () => { - var node = document.createElement('div'); - - ReactDOM.render(
hi
, node); - expectDev(ReactComponentTreeTestUtils.getRegisteredDisplayNames()).toEqual(['div', '#text']); - - ReactDOM.unmountComponentAtNode(node); - expectDev(ReactComponentTreeTestUtils.getRegisteredDisplayNames()).toEqual([]); - }); - - describe('stack addenda', () => { - it('gets created', () => { - function getAddendum(element) { - var addendum = ReactComponentTreeHook.getCurrentStackAddendum(element); - return addendum.replace(/\(at .+?:\d+\)/g, '(at **)'); - } - - var Anon = React.createClass({displayName: null, render: () => null}); - var Orange = React.createClass({render: () => null}); - - expectDev(getAddendum()).toBe( - '' - ); - expectDev(getAddendum(
)).toBe( - '\n in div (at **)' - ); - expectDev(getAddendum()).toBe( - '\n in Unknown (at **)' - ); - expectDev(getAddendum()).toBe( - '\n in Orange (at **)' - ); - expectDev(getAddendum(React.createElement(Orange))).toBe( - '\n in Orange' - ); - - var renders = 0; - var rOwnedByQ; - - function Q() { - return (rOwnedByQ = React.createElement(R)); - } - function R() { - return
; - } - class S extends React.Component { - componentDidMount() { - // Check that the parent path is still fetched when only S itself is on - // the stack. - this.forceUpdate(); - } - render() { - expectDev(getAddendum()).toBe( - '\n in S (at **)' + - '\n in div (at **)' + - '\n in R (created by Q)' + - '\n in Q (at **)' - ); - expectDev(getAddendum()).toBe( - '\n in span (at **)' + - '\n in S (at **)' + - '\n in div (at **)' + - '\n in R (created by Q)' + - '\n in Q (at **)' - ); - expectDev(getAddendum(React.createElement('span'))).toBe( - '\n in span (created by S)' + - '\n in S (at **)' + - '\n in div (at **)' + - '\n in R (created by Q)' + - '\n in Q (at **)' - ); - renders++; - return null; - } - } - ReactDOM.render(, document.createElement('div')); - expectDev(renders).toBe(2); - - // Make sure owner is fetched for the top element too. - expectDev(getAddendum(rOwnedByQ)).toBe( - '\n in R (created by Q)' - ); + }; + assertTreeMatches([element, tree]); }); - it('can be retrieved by ID', () => { - function getAddendum(id) { - var addendum = ReactComponentTreeHook.getStackAddendumByID(id); - return addendum.replace(/\(at .+?:\d+\)/g, '(at **)'); + it('purges unmounted components automatically', () => { + var node = document.createElement('div'); + var renderBar = true; + var fooInstance; + var barInstance; + + class Foo extends React.Component { + render() { + fooInstance = ReactInstanceMap.get(this); + return renderBar ? : null; + } } - class Q extends React.Component { + class Bar extends React.Component { render() { + barInstance = ReactInstanceMap.get(this); return null; } } - var q = ReactDOM.render(, document.createElement('div')); - expectDev(getAddendum(ReactInstanceMap.get(q)._debugID)).toBe( - '\n in Q (at **)' - ); + ReactDOM.render(, node); + ReactComponentTreeTestUtils.expectTree(barInstance._debugID, { + displayName: 'Bar', + parentDisplayName: 'Foo', + parentID: fooInstance._debugID, + children: [], + }, 'Foo'); - spyOn(console, 'error'); - getAddendum(-17); - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toBe( - 'Warning: ReactComponentTreeHook: Missing React element for ' + - 'debugID -17 when building stack' - ); + renderBar = false; + ReactDOM.render(, node); + ReactDOM.render(, node); + ReactComponentTreeTestUtils.expectTree(barInstance._debugID, { + displayName: 'Unknown', + children: [], + parentID: null, + }, 'Foo'); + + ReactDOM.unmountComponentAtNode(node); + ReactComponentTreeTestUtils.expectTree(barInstance._debugID, { + displayName: 'Unknown', + children: [], + parentID: null, + }, 'Foo'); }); - it('is created during mounting', () => { - // https://github.com/facebook/react/issues/7187 - var el = document.createElement('div'); - var portalEl = document.createElement('div'); - class Foo extends React.Component { - componentWillMount() { - ReactDOM.render(
, portalEl); - } - render() { - return
; - } - } - ReactDOM.render(, el); + it('reports update counts', () => { + var node = document.createElement('div'); + + ReactDOM.render(
, node); + var divID = ReactComponentTreeHook.getRootIDs()[0]; + expectDev(ReactComponentTreeHook.getUpdateCount(divID)).toEqual(0); + + ReactDOM.render(, node); + var spanID = ReactComponentTreeHook.getRootIDs()[0]; + expectDev(ReactComponentTreeHook.getUpdateCount(divID)).toEqual(0); + expectDev(ReactComponentTreeHook.getUpdateCount(spanID)).toEqual(0); + + ReactDOM.render(, node); + expectDev(ReactComponentTreeHook.getUpdateCount(divID)).toEqual(0); + expectDev(ReactComponentTreeHook.getUpdateCount(spanID)).toEqual(1); + + ReactDOM.render(, node); + expectDev(ReactComponentTreeHook.getUpdateCount(divID)).toEqual(0); + expectDev(ReactComponentTreeHook.getUpdateCount(spanID)).toEqual(2); + + ReactDOM.unmountComponentAtNode(node); + expectDev(ReactComponentTreeHook.getUpdateCount(divID)).toEqual(0); + expectDev(ReactComponentTreeHook.getUpdateCount(spanID)).toEqual(0); }); - it('is created when calling renderToString during render', () => { - // https://github.com/facebook/react/issues/7190 - var el = document.createElement('div'); - class Foo extends React.Component { - render() { - return ( -
-
- {ReactDOMServer.renderToString(
)} -
-
- ); - } - } - ReactDOM.render(, el); + it('does not report top-level wrapper as a root', () => { + var node = document.createElement('div'); + + ReactDOM.render(
, node); + expectDev(ReactComponentTreeTestUtils.getRootDisplayNames()).toEqual(['div']); + + ReactDOM.render(
, node); + expectDev(ReactComponentTreeTestUtils.getRootDisplayNames()).toEqual(['div']); + + ReactDOM.unmountComponentAtNode(node); + expectDev(ReactComponentTreeTestUtils.getRootDisplayNames()).toEqual([]); + expectDev(ReactComponentTreeTestUtils.getRegisteredDisplayNames()).toEqual([]); + }); + + it('registers inlined text nodes', () => { + var node = document.createElement('div'); + + ReactDOM.render(
hi
, node); + expectDev(ReactComponentTreeTestUtils.getRegisteredDisplayNames()).toEqual(['div', '#text']); + + ReactDOM.unmountComponentAtNode(node); + expectDev(ReactComponentTreeTestUtils.getRegisteredDisplayNames()).toEqual([]); }); }); - describe('in environment without Map, Set and Array.from', () => { + describeStack('in environment without Map, Set and Array.from', () => { var realMap; var realSet; var realArrayFrom; diff --git a/src/renderers/__tests__/ReactComponentTreeHook-test.native.js b/src/renderers/__tests__/ReactComponentTreeHook-test.native.js index 23c934c274..17a8fa4986 100644 --- a/src/renderers/__tests__/ReactComponentTreeHook-test.native.js +++ b/src/renderers/__tests__/ReactComponentTreeHook-test.native.js @@ -11,7 +11,15 @@ 'use strict'; -describe('ReactComponentTreeHook', () => { +var ReactNativeFeatureFlags = require('ReactNativeFeatureFlags'); +var describeStack = ReactNativeFeatureFlags.useFiber ? describe.skip : describe; + +// These tests are only relevant for the Stack version of the tree hook. +// This file is for RN. There is a sibling file that has some tree hook +// tests that are still relevant in Fiber. +// TODO: remove this file when we delete Stack. + +describeStack('ReactComponentTreeHook', () => { var React; var ReactNative; var ReactInstanceMap; diff --git a/src/renderers/__tests__/ReactHostOperationHistoryHook-test.js b/src/renderers/__tests__/ReactHostOperationHistoryHook-test.js index b830342b75..d6c12a6f4c 100644 --- a/src/renderers/__tests__/ReactHostOperationHistoryHook-test.js +++ b/src/renderers/__tests__/ReactHostOperationHistoryHook-test.js @@ -11,12 +11,16 @@ 'use strict'; -describe('ReactHostOperationHistoryHook', () => { +var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); +var describeStack = ReactDOMFeatureFlags.useFiber ? describe.skip : describe; + +// This is only used by ReactPerf which is currently not supported on Fiber. +// Use browser timeline integration instead. +describeStack('ReactHostOperationHistoryHook', () => { var React; var ReactPerf; var ReactDOM; var ReactDOMComponentTree; - var ReactDOMFeatureFlags; var ReactHostOperationHistoryHook; beforeEach(() => { @@ -26,7 +30,6 @@ describe('ReactHostOperationHistoryHook', () => { ReactPerf = require('react-dom/lib/ReactPerf'); ReactDOM = require('react-dom'); ReactDOMComponentTree = require('ReactDOMComponentTree'); - ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); ReactHostOperationHistoryHook = require('ReactHostOperationHistoryHook'); ReactPerf.start(); diff --git a/src/renderers/__tests__/ReactPerf-test.js b/src/renderers/__tests__/ReactPerf-test.js index 7d5cb809c2..2abc0d17df 100644 --- a/src/renderers/__tests__/ReactPerf-test.js +++ b/src/renderers/__tests__/ReactPerf-test.js @@ -11,7 +11,12 @@ 'use strict'; -describe('ReactPerf', () => { +var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); +var describeStack = ReactDOMFeatureFlags.useFiber ? describe.skip : describe; + +// ReactPerf is currently not supported on Fiber. +// Use browser timeline integration instead. +describeStack('ReactPerf', () => { var React; var ReactDOM; var ReactPerf; diff --git a/src/renderers/shared/fiber/ReactDebugFiberPerf.js b/src/renderers/shared/fiber/ReactDebugFiberPerf.js new file mode 100644 index 0000000000..0c16eca8c0 --- /dev/null +++ b/src/renderers/shared/fiber/ReactDebugFiberPerf.js @@ -0,0 +1,408 @@ +/** + * 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 ReactDebugFiberPerf + * @flow + */ + +import type { Fiber } from 'ReactFiber'; + +type MeasurementPhase = + 'componentWillMount' | + 'componentWillUnmount' | + 'componentWillReceiveProps' | + 'shouldComponentUpdate' | + 'componentWillUpdate' | + 'componentDidUpdate' | + 'componentDidMount' | + 'getChildContext'; + +// Trust the developer to only use this with a __DEV__ check +let ReactDebugFiberPerf = ((null: any): typeof ReactDebugFiberPerf); + +if (__DEV__) { + const { + HostRoot, + HostComponent, + HostText, + HostPortal, + YieldComponent, + Fragment, + } = require('ReactTypeOfWork'); + + const getComponentName = require('getComponentName'); + + // Prefix measurements so that it's possible to filter them. + // Longer prefixes are hard to read in DevTools. + const reactEmoji = '\u269B'; + const warningEmoji = '\uD83D\uDED1'; + const supportsUserTiming = + typeof performance !== 'undefined' && + typeof performance.mark === 'function' && + typeof performance.clearMarks === 'function' && + typeof performance.measure === 'function' && + typeof performance.clearMeasures === 'function'; + + // Keep track of current fiber so that we know the path to unwind on pause. + // TODO: this looks the same as nextUnitOfWork in scheduler. Can we unify them? + let currentFiber : Fiber | null = null; + // If we're in the middle of user code, which fiber and method is it? + // Reusing `currentFiber` would be confusing for this because user code fiber + // can change during commit phase too, but we don't need to unwind it (since + // lifecycles in the commit phase don't resemble a tree). + let currentPhase : MeasurementPhase | null = null; + let currentPhaseFiber : Fiber | null = null; + // Did lifecycle hook schedule an update? This is often a performance problem, + // so we will keep track of it, and include it in the report. + // Track commits caused by cascading updates. + let isCommitting : boolean = false; + let hasScheduledUpdateInCurrentCommit : boolean = false; + let hasScheduledUpdateInCurrentPhase : boolean = false; + let commitCountInCurrentWorkLoop : number = 0; + let effectCountInCurrentCommit : number = 0; + // During commits, we only show a measurement once per method name + // to avoid stretch the commit phase with measurement overhead. + const labelsInCurrentCommit : Set = new Set(); + + const formatMarkName = (markName : string) => { + return `${reactEmoji} ${markName}`; + }; + + const formatLabel = (label : string, warning : string | null) => { + const prefix = warning ? `${warningEmoji} ` : `${reactEmoji} `; + const suffix = warning ? ` Warning: ${warning}` : ''; + return `${prefix}${label}${suffix}`; + }; + + const beginMark = (markName : string) => { + performance.mark(formatMarkName(markName)); + }; + + const clearMark = (markName : string) => { + performance.clearMarks(formatMarkName(markName)); + }; + + const endMark = (label : string, markName : string, warning : string | null) => { + const formattedMarkName = formatMarkName(markName); + const formattedLabel = formatLabel(label, warning); + try { + performance.measure(formattedLabel, formattedMarkName); + } catch (err) { + // If previous mark was missing for some reason, this will throw. + // This could only happen if React crashed in an unexpected place earlier. + // Don't pile on with more errors. + } + // Clear marks immediately to avoid growing buffer. + performance.clearMarks(formattedMarkName); + performance.clearMeasures(formattedLabel); + }; + + const getFiberMarkName = (label : string, debugID : number) => { + return `${label} (#${debugID})`; + }; + + const getFiberLabel = ( + componentName : string, + isMounted : boolean, + phase : MeasurementPhase | null, + ) => { + if (phase === null) { + // These are composite component total time measurements. + return `${componentName} [${isMounted ? 'update' : 'mount'}]`; + } else { + // Composite component methods. + return `${componentName}.${phase}`; + } + }; + + const beginFiberMark = ( + fiber : Fiber, + phase : MeasurementPhase | null, + ) : boolean => { + const componentName = getComponentName(fiber) || 'Unknown'; + const debugID = ((fiber._debugID : any) : number); + const isMounted = fiber.alternate !== null; + const label = getFiberLabel(componentName, isMounted, phase); + + if (isCommitting && labelsInCurrentCommit.has(label)) { + // During the commit phase, we don't show duplicate labels because + // there is a fixed overhead for every measurement, and we don't + // want to stretch the commit phase beyond necessary. + return false; + } + labelsInCurrentCommit.add(label); + + const markName = getFiberMarkName(label, debugID); + beginMark(markName); + return true; + }; + + const clearFiberMark = (fiber : Fiber, phase : MeasurementPhase | null) => { + const componentName = getComponentName(fiber) || 'Unknown'; + const debugID = ((fiber._debugID : any) : number); + const isMounted = fiber.alternate !== null; + const label = getFiberLabel(componentName, isMounted, phase); + const markName = getFiberMarkName(label, debugID); + clearMark(markName); + }; + + const endFiberMark = ( + fiber : Fiber, + phase : MeasurementPhase | null, + warning : string | null, + ) => { + const componentName = getComponentName(fiber) || 'Unknown'; + const debugID = ((fiber._debugID : any) : number); + const isMounted = fiber.alternate !== null; + const label = getFiberLabel(componentName, isMounted, phase); + const markName = getFiberMarkName(label, debugID); + endMark(label, markName, warning); + }; + + const shouldIgnoreFiber = (fiber : Fiber) : boolean => { + // Host components should be skipped in the timeline. + // We could check typeof fiber.type, but does this work with RN? + switch (fiber.tag) { + case HostRoot: + case HostComponent: + case HostText: + case HostPortal: + case YieldComponent: + case Fragment: + return true; + default: + return false; + } + }; + + const clearPendingPhaseMeasurement = () => { + if (currentPhase !== null && currentPhaseFiber !== null) { + clearFiberMark(currentPhaseFiber, currentPhase); + } + currentPhaseFiber = null; + currentPhase = null; + hasScheduledUpdateInCurrentPhase = false; + }; + + const pauseTimers = () => { + // Stops all currently active measurements so that they can be resumed + // if we continue in a later deferred loop from the same unit of work. + let fiber = currentFiber; + while (fiber) { + if (fiber._debugIsCurrentlyTiming) { + endFiberMark(fiber, null, null); + } + fiber = fiber.return; + } + }; + + const resumeTimersRecursively = (fiber : Fiber) => { + if (fiber.return !== null) { + resumeTimersRecursively(fiber.return); + } + if (fiber._debugIsCurrentlyTiming) { + beginFiberMark(fiber, null); + } + }; + + const resumeTimers = () => { + // Resumes all measurements that were active during the last deferred loop. + if (currentFiber !== null) { + resumeTimersRecursively(currentFiber); + } + }; + + ReactDebugFiberPerf = { + recordEffect() : void { + effectCountInCurrentCommit++; + }, + + recordScheduleUpdate() : void { + if (isCommitting) { + hasScheduledUpdateInCurrentCommit = true; + } + if ( + currentPhase !== null && + currentPhase !== 'componentWillMount' && + currentPhase !== 'componentWillReceiveProps' + ) { + hasScheduledUpdateInCurrentPhase = true; + } + }, + + startWorkTimer(fiber : Fiber) : void { + if (!supportsUserTiming || shouldIgnoreFiber(fiber)) { + return; + } + // If we pause, this is the fiber to unwind from. + currentFiber = fiber; + if (!beginFiberMark(fiber, null)) { + return; + } + fiber._debugIsCurrentlyTiming = true; + }, + + cancelWorkTimer(fiber : Fiber) : void { + if (!supportsUserTiming || shouldIgnoreFiber(fiber)) { + return; + } + // Remember we shouldn't complete measurement for this fiber. + // Otherwise flamechart will be deep even for small updates. + fiber._debugIsCurrentlyTiming = false; + clearFiberMark(fiber, null); + }, + + stopWorkTimer(fiber : Fiber) : void { + if (!supportsUserTiming || shouldIgnoreFiber(fiber)) { + return; + } + // If we pause, its parent is the fiber to unwind from. + currentFiber = fiber.return; + if (!fiber._debugIsCurrentlyTiming) { + return; + } + fiber._debugIsCurrentlyTiming = false; + endFiberMark(fiber, null, null); + }, + + startPhaseTimer( + fiber : Fiber, + phase : MeasurementPhase, + ) : void { + if (!supportsUserTiming) { + return; + } + clearPendingPhaseMeasurement(); + if (!beginFiberMark(fiber, phase)) { + return; + } + currentPhaseFiber = fiber; + currentPhase = phase; + }, + + stopPhaseTimer() : void { + if (!supportsUserTiming) { + return; + } + if (currentPhase !== null && currentPhaseFiber !== null) { + const warning = hasScheduledUpdateInCurrentPhase ? + 'Scheduled a cascading update' : + null; + endFiberMark(currentPhaseFiber, currentPhase, warning); + } + currentPhase = null; + currentPhaseFiber = null; + }, + + startWorkLoopTimer() : void { + if (!supportsUserTiming) { + return; + } + commitCountInCurrentWorkLoop = 0; + // This is top level call. + // Any other measurements are performed within. + beginMark('(React Tree Reconciliation)'); + // Resume any measurements that were in progress during the last loop. + resumeTimers(); + }, + + stopWorkLoopTimer() : void { + if (!supportsUserTiming) { + return; + } + const warning = commitCountInCurrentWorkLoop > 1 ? + 'There were cascading updates' : + null; + commitCountInCurrentWorkLoop = 0; + // Pause any measurements until the next loop. + pauseTimers(); + endMark( + '(React Tree Reconciliation)', + '(React Tree Reconciliation)', + warning, + ); + }, + + startCommitTimer() : void { + if (!supportsUserTiming) { + return; + } + isCommitting = true; + hasScheduledUpdateInCurrentCommit = false; + labelsInCurrentCommit.clear(); + beginMark('(Committing Changes)'); + }, + + stopCommitTimer() : void { + if (!supportsUserTiming) { + return; + } + + let warning = null; + if (hasScheduledUpdateInCurrentCommit) { + warning = 'Lifecycle hook scheduled a cascading update'; + } else if (commitCountInCurrentWorkLoop > 0) { + warning = 'Caused by a cascading update in earlier commit'; + } + hasScheduledUpdateInCurrentCommit = false; + commitCountInCurrentWorkLoop++; + isCommitting = false; + labelsInCurrentCommit.clear(); + + endMark( + '(Committing Changes)', + '(Committing Changes)', + warning, + ); + }, + + startCommitHostEffectsTimer() : void { + if (!supportsUserTiming) { + return; + } + effectCountInCurrentCommit = 0; + beginMark('(Committing Host Effects)'); + }, + + stopCommitHostEffectsTimer() : void { + if (!supportsUserTiming) { + return; + } + const count = effectCountInCurrentCommit; + effectCountInCurrentCommit = 0; + endMark( + `(Committing Host Effects: ${count} Total)`, + '(Committing Host Effects)', + null, + ); + }, + + startCommitLifeCyclesTimer() : void { + if (!supportsUserTiming) { + return; + } + effectCountInCurrentCommit = 0; + beginMark('(Calling Lifecycle Methods)'); + }, + + stopCommitLifeCyclesTimer() : void { + if (!supportsUserTiming) { + return; + } + const count = effectCountInCurrentCommit; + effectCountInCurrentCommit = 0; + endMark( + `(Calling Lifecycle Methods: ${count} Total)`, + '(Calling Lifecycle Methods)', + null, + ); + }, + }; +} + +module.exports = ReactDebugFiberPerf; diff --git a/src/renderers/shared/fiber/ReactFiber.js b/src/renderers/shared/fiber/ReactFiber.js index bd89a93f82..0b34566378 100644 --- a/src/renderers/shared/fiber/ReactFiber.js +++ b/src/renderers/shared/fiber/ReactFiber.js @@ -60,6 +60,7 @@ export type Fiber = { _debugID ?: DebugID, _debugSource ?: Source | null, _debugOwner ?: Fiber | ReactInstance | null, // Stack compatible + _debugIsCurrentlyTiming ?: boolean, // These first fields are conceptually members of an Instance. This used to // be split into a separate type and intersected with the other Fiber fields, @@ -223,6 +224,7 @@ var createFiber = function(tag : TypeOfWork, key : null | string) : Fiber { fiber._debugID = debugCounter++; fiber._debugSource = null; fiber._debugOwner = null; + fiber._debugIsCurrentlyTiming = false; if (typeof Object.preventExtensions === 'function') { Object.preventExtensions(fiber); } diff --git a/src/renderers/shared/fiber/ReactFiberBeginWork.js b/src/renderers/shared/fiber/ReactFiberBeginWork.js index 1d1d6a75c1..3fa3cfda99 100644 --- a/src/renderers/shared/fiber/ReactFiberBeginWork.js +++ b/src/renderers/shared/fiber/ReactFiberBeginWork.js @@ -66,7 +66,9 @@ var invariant = require('fbjs/lib/invariant'); if (__DEV__) { var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber'); + var {cancelWorkTimer} = require('ReactDebugFiberPerf'); var warning = require('fbjs/lib/warning'); + var warnedAboutStatelessRefs = {}; } @@ -652,6 +654,10 @@ module.exports = function( */ function bailoutOnAlreadyFinishedWork(current, workInProgress : Fiber) : Fiber | null { + if (__DEV__) { + cancelWorkTimer(workInProgress); + } + const priorityLevel = workInProgress.pendingWorkPriority; // TODO: We should ideally be able to bail out early if the children have no // more work to do. However, since we don't have a separation of this @@ -679,6 +685,10 @@ module.exports = function( } function bailoutOnLowPriority(current, workInProgress) { + if (__DEV__) { + cancelWorkTimer(workInProgress); + } + // TODO: Handle HostComponent tags here as well and call pushHostContext()? // See PR 8590 discussion for context switch (workInProgress.tag) { diff --git a/src/renderers/shared/fiber/ReactFiberClassComponent.js b/src/renderers/shared/fiber/ReactFiberClassComponent.js index ae35f8db6d..47017cf292 100644 --- a/src/renderers/shared/fiber/ReactFiberClassComponent.js +++ b/src/renderers/shared/fiber/ReactFiberClassComponent.js @@ -40,6 +40,10 @@ var invariant = require('fbjs/lib/invariant'); const isArray = Array.isArray; if (__DEV__) { + var { + startPhaseTimer, + stopPhaseTimer, + } = require('ReactDebugFiberPerf'); var warning = require('fbjs/lib/warning'); var warnOnInvalidCallback = function(callback : mixed, callerName : string) { warning( @@ -102,7 +106,13 @@ module.exports = function( const instance = workInProgress.stateNode; if (typeof instance.shouldComponentUpdate === 'function') { + if (__DEV__) { + startPhaseTimer(workInProgress, 'shouldComponentUpdate'); + } const shouldUpdate = instance.shouldComponentUpdate(newProps, newState, newContext); + if (__DEV__) { + stopPhaseTimer(); + } if (__DEV__) { warning( @@ -278,7 +288,13 @@ module.exports = function( instance.context = getMaskedContext(workInProgress, unmaskedContext); if (typeof instance.componentWillMount === 'function') { + if (__DEV__) { + startPhaseTimer(workInProgress, 'componentWillMount'); + } instance.componentWillMount(); + if (__DEV__) { + stopPhaseTimer(); + } // If we had additional state updates during this life-cycle, let's // process them now. const updateQueue = workInProgress.updateQueue; @@ -347,7 +363,13 @@ module.exports = function( newInstance.context = newContext; if (typeof newInstance.componentWillMount === 'function') { + if (__DEV__) { + startPhaseTimer(workInProgress, 'componentWillMount'); + } newInstance.componentWillMount(); + if (__DEV__) { + stopPhaseTimer(); + } } // If we had additional state updates, process them now. // They may be from componentWillMount() or from error boundary's setState() @@ -396,7 +418,13 @@ module.exports = function( if (oldProps !== newProps || oldContext !== newContext) { if (typeof instance.componentWillReceiveProps === 'function') { + if (__DEV__) { + startPhaseTimer(workInProgress, 'componentWillReceiveProps'); + } instance.componentWillReceiveProps(newProps, newContext); + if (__DEV__) { + stopPhaseTimer(); + } if (instance.state !== workInProgress.memoizedState) { if (__DEV__) { @@ -457,7 +485,13 @@ module.exports = function( if (shouldUpdate) { if (typeof instance.componentWillUpdate === 'function') { + if (__DEV__) { + startPhaseTimer(workInProgress, 'componentWillUpdate'); + } instance.componentWillUpdate(newProps, newState, newContext); + if (__DEV__) { + stopPhaseTimer(); + } } if (typeof instance.componentDidUpdate === 'function') { workInProgress.effectTag |= Update; diff --git a/src/renderers/shared/fiber/ReactFiberCommitWork.js b/src/renderers/shared/fiber/ReactFiberCommitWork.js index 32cd4fb52f..30a2f4962c 100644 --- a/src/renderers/shared/fiber/ReactFiberCommitWork.js +++ b/src/renderers/shared/fiber/ReactFiberCommitWork.js @@ -37,6 +37,13 @@ var { var invariant = require('fbjs/lib/invariant'); +if (__DEV__) { + var { + startPhaseTimer, + stopPhaseTimer, + } = require('ReactDebugFiberPerf'); +} + module.exports = function( config : HostConfig, captureError : (failedFiber : Fiber, error: Error) => Fiber | null @@ -53,10 +60,24 @@ module.exports = function( getPublicInstance, } = config; + if (__DEV__) { + var callComponentWillUnmountWithTimerInDev = function(current, instance) { + startPhaseTimer(current, 'componentWillUnmount'); + instance.componentWillUnmount(); + stopPhaseTimer(); + }; + } + // Capture errors so they don't interrupt unmounting. function safelyCallComponentWillUnmount(current, instance) { if (__DEV__) { - const unmountError = invokeGuardedCallback(null, instance.componentWillUnmount, instance); + const unmountError = invokeGuardedCallback( + null, + callComponentWillUnmountWithTimerInDev, + null, + current, + instance, + ); if (unmountError) { captureError(current, unmountError); } @@ -423,11 +444,23 @@ module.exports = function( const instance = finishedWork.stateNode; if (finishedWork.effectTag & Update) { if (current === null) { + if (__DEV__) { + startPhaseTimer(finishedWork, 'componentDidMount'); + } instance.componentDidMount(); + if (__DEV__) { + stopPhaseTimer(); + } } else { const prevProps = current.memoizedProps; const prevState = current.memoizedState; + if (__DEV__) { + startPhaseTimer(finishedWork, 'componentDidUpdate'); + } instance.componentDidUpdate(prevProps, prevState); + if (__DEV__) { + stopPhaseTimer(); + } } } if ((finishedWork.effectTag & Callback) && finishedWork.updateQueue !== null) { diff --git a/src/renderers/shared/fiber/ReactFiberContext.js b/src/renderers/shared/fiber/ReactFiberContext.js index d921285a6b..7e0122fd9f 100644 --- a/src/renderers/shared/fiber/ReactFiberContext.js +++ b/src/renderers/shared/fiber/ReactFiberContext.js @@ -36,6 +36,10 @@ if (__DEV__) { var checkReactTypeSpec = require('checkReactTypeSpec'); var ReactDebugCurrentFrame = require('react/lib/ReactDebugCurrentFrame'); var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber'); + var { + startPhaseTimer, + stopPhaseTimer, + } = require('ReactDebugFiberPerf'); var warnedAboutMissingGetChildContext = {}; } @@ -172,7 +176,9 @@ function processChildContext(fiber : Fiber, parentContext : Object, isReconcilin let childContext; if (__DEV__) { ReactDebugCurrentFiber.phase = 'getChildContext'; + startPhaseTimer(fiber, 'getChildContext'); childContext = instance.getChildContext(); + stopPhaseTimer(); ReactDebugCurrentFiber.phase = null; } else { childContext = instance.getChildContext(); diff --git a/src/renderers/shared/fiber/ReactFiberScheduler.js b/src/renderers/shared/fiber/ReactFiberScheduler.js index ecd907835d..3f1698d4c8 100644 --- a/src/renderers/shared/fiber/ReactFiberScheduler.js +++ b/src/renderers/shared/fiber/ReactFiberScheduler.js @@ -95,6 +95,20 @@ if (__DEV__) { var warning = require('fbjs/lib/warning'); var ReactFiberInstrumentation = require('ReactFiberInstrumentation'); var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber'); + var { + recordEffect, + recordScheduleUpdate, + startWorkTimer, + stopWorkTimer, + startWorkLoopTimer, + stopWorkLoopTimer, + startCommitTimer, + stopCommitTimer, + startCommitHostEffectsTimer, + stopCommitHostEffectsTimer, + startCommitLifeCyclesTimer, + stopCommitLifeCyclesTimer, + } = require('ReactDebugFiberPerf'); var warnAboutUpdateOnUnmounted = function(instance : ReactClass) { const ctor = instance.constructor; @@ -293,6 +307,7 @@ module.exports = function(config : HostConfig(config : HostConfig(config : HostConfig(config : HostConfig(config : HostConfig(config : HostConfig(config : HostConfig(config : HostConfig(config : HostConfig(config : HostConfig(config : HostConfig(config : HostConfig(config : HostConfig(config : HostConfig(config : HostConfig { + let React; + let ReactCoroutine; + let ReactFeatureFlags; + let ReactNoop; + let ReactPortal; + + let root; + let activeMeasure; + let knownMarks; + let knownMeasures; + + function resetFlamechart() { + root = { + children: [], + indent: -1, + markName: null, + label: null, + parent: null, + toString() { + return this.children.map(c => c.toString()).join('\n'); + }, + }; + activeMeasure = root; + knownMarks = new Set(); + knownMeasures = new Set(); + } + + function addComment(comment) { + activeMeasure.children.push( + `${' '.repeat(activeMeasure.indent + 1)}// ${comment}` + ); + } + + function getFlameChart() { + // Make sure we unwind the measurement stack every time. + expect(activeMeasure.indent).toBe(-1); + expect(activeMeasure).toBe(root); + // We should always clean them up because browsers + // buffer user timing measurements forever. + expect(knownMarks.size).toBe(0); + expect(knownMeasures.size).toBe(0); + return root.toString(); + } + + function createUserTimingPolyfill() { + // This is not a true polyfill, but it gives us enough + // to capture measurements in a readable tree-like output. + // Reference: https://developer.mozilla.org/en-US/docs/Web/API/User_Timing_API + return { + mark(markName) { + const measure = { + children: [], + indent: activeMeasure.indent + 1, + markName: markName, + // Will be assigned on measure() call: + label: null, + parent: activeMeasure, + toString() { + return [ + ' '.repeat(this.indent) + this.label, + ...this.children.map(c => c.toString()), + ].join('\n') + ( + // Extra newline after each root reconciliation + this.indent === 0 ? '\n' : '' + ); + }, + }; + // Step one level deeper + activeMeasure.children.push(measure); + activeMeasure = measure; + knownMarks.add(markName); + }, + // We don't use the overload with three arguments. + measure(label, markName) { + if (markName !== activeMeasure.markName) { + throw new Error('Unexpected measure() call.'); + } + // Step one level up + activeMeasure.label = label; + activeMeasure = activeMeasure.parent; + knownMeasures.add(label); + }, + clearMarks(markName) { + if (markName === activeMeasure.markName) { + // Step one level up if we're in this measure + activeMeasure = activeMeasure.parent; + activeMeasure.children.length--; + } + knownMarks.delete(markName); + }, + clearMeasures(label) { + knownMeasures.delete(label); + }, + }; + } + + beforeEach(() => { + jest.resetModules(); + resetFlamechart(); + global.performance = createUserTimingPolyfill(); + + // Import after the polyfill is set up: + React = require('React'); + ReactCoroutine = require('ReactCoroutine'); + ReactFeatureFlags = require('ReactFeatureFlags'); + ReactNoop = require('ReactNoop'); + ReactPortal = require('ReactPortal'); + ReactFeatureFlags.disableNewFiberFeatures = false; + }); + + afterEach(() => { + delete global.performance; + }); + + function Parent(props) { + return
{props.children}
; + } + + function Child(props) { + return
{props.children}
; + } + + it('measures a simple reconciliation', () => { + ReactNoop.render(); + addComment('Mount'); + ReactNoop.flush(); + + ReactNoop.render(); + addComment('Update'); + ReactNoop.flush(); + + ReactNoop.render(null); + addComment('Unmount'); + ReactNoop.flush(); + + expect(getFlameChart()).toMatchSnapshot(); + }); + + it('skips parents during setState', () => { + class A extends React.Component { + render() { + return
{this.props.children}
; + } + } + + class B extends React.Component { + render() { + return
{this.props.children}
; + } + } + + let a; + let b; + ReactNoop.render( + + + + a = inst} /> + + + + b = inst} /> + + + ); + ReactNoop.flush(); + resetFlamechart(); + + a.setState({}); + b.setState({}); + addComment('Should include just A and B, no Parents'); + ReactNoop.flush(); + expect(getFlameChart()).toMatchSnapshot(); + }); + + it('warns on cascading renders from setState', () => { + class Cascading extends React.Component { + componentDidMount() { + this.setState({}); + } + render() { + return
{this.props.children}
; + } + } + + ReactNoop.render(); + addComment('Should print a warning'); + ReactNoop.flush(); + expect(getFlameChart()).toMatchSnapshot(); + }); + + it('warns on cascading renders from top-level render', () => { + class Cascading extends React.Component { + componentDidMount() { + ReactNoop.renderToRootWithID(, 'b'); + addComment('Scheduling another root from componentDidMount'); + ReactNoop.flush(); + } + render() { + return
{this.props.children}
; + } + } + + ReactNoop.renderToRootWithID(, 'a'); + addComment('Rendering the first root'); + ReactNoop.flush(); + expect(getFlameChart()).toMatchSnapshot(); + }); + + it('does not treat setState from cWM or cWRP as cascading', () => { + class NotCascading extends React.Component { + componentWillMount() { + this.setState({}); + } + componentWillReceiveProps() { + this.setState({}); + } + render() { + return
{this.props.children}
; + } + } + + ReactNoop.render(); + addComment('Should not print a warning'); + ReactNoop.flush(); + ReactNoop.render(); + addComment('Should not print a warning'); + ReactNoop.flush(); + expect(getFlameChart()).toMatchSnapshot(); + }); + + it('captures all lifecycles', () => { + class AllLifecycles extends React.Component { + static childContextTypes = { + foo: React.PropTypes.any, + }; + shouldComponentUpdate() { + return true; + } + getChildContext() { + return {foo: 42}; + } + componentWillMount() {} + componentDidMount() {} + componentWillReceiveProps() {} + componentWillUpdate() {} + componentDidUpdate() {} + componentWillUnmount() {} + render() { + return
; + } + } + ReactNoop.render(); + addComment('Mount'); + ReactNoop.flush(); + ReactNoop.render(); + addComment('Update'); + ReactNoop.flush(); + ReactNoop.render(null); + addComment('Unmount'); + ReactNoop.flush(); + expect(getFlameChart()).toMatchSnapshot(); + }); + + it('measures deprioritized work', () => { + ReactNoop.performAnimationWork(() => { + ReactNoop.render( + + + + ); + }); + addComment('Flush the parent'); + ReactNoop.flushAnimationPri(); + addComment('Flush the child'); + ReactNoop.flushDeferredPri(); + expect(getFlameChart()).toMatchSnapshot(); + }); + + it('measures deferred work in chunks', () => { + class A extends React.Component { + render() { + return
{this.props.children}
; + } + } + + class B extends React.Component { + render() { + return
{this.props.children}
; + } + } + + ReactNoop.render( + +
+ + + + + + + ); + addComment('Start mounting Parent and A'); + ReactNoop.flushDeferredPri(40); + addComment('Mount B just a little (but not enough to memoize)'); + ReactNoop.flushDeferredPri(10); + addComment('Complete B and Parent'); + ReactNoop.flushDeferredPri(); + expect(getFlameChart()).toMatchSnapshot(); + }); + + it('recovers from fatal errors', () => { + function Baddie() { + throw new Error('Game over'); + } + + ReactNoop.render(); + try { + addComment('Will fatal'); + ReactNoop.flush(); + } catch (err) { + expect(err.message).toBe('Game over'); + } + ReactNoop.render(); + addComment('Will reconcile from a clean state'); + ReactNoop.flush(); + expect(getFlameChart()).toMatchSnapshot(); + }); + + it('recovers from caught errors', () => { + function Baddie() { + throw new Error('Game over'); + } + + function ErrorReport() { + return
; + } + + class Boundary extends React.Component { + state = {error: null}; + unstable_handleError(error) { + this.setState({error}); + } + render() { + if (this.state.error) { + return ; + } + return this.props.children; + } + } + + ReactNoop.render( + + + + + + + + ); + addComment('Stop on Baddie and restart from Boundary'); + ReactNoop.flush(); + expect(getFlameChart()).toMatchSnapshot(); + }); + + it('deduplicates lifecycle names during commit to reduce overhead', () => { + class A extends React.Component { + componentDidUpdate() {} + render() { + return
; + } + } + + class B extends React.Component { + componentDidUpdate(prevProps) { + if (this.props.cascade && !prevProps.cascade) { + this.setState({}); + } + } + render() { + return
; + } + } + + ReactNoop.render( + + + + + + + ); + ReactNoop.flush(); + resetFlamechart(); + + ReactNoop.render( + + + + + + + ); + addComment('The commit phase should mention A and B just once'); + ReactNoop.flush(); + ReactNoop.render( + + + + + + + ); + addComment('Because of deduplication, we don\'t know B was cascading,'); + addComment('but we should still see the warning for the commit phase.'); + ReactNoop.flush(); + expect(getFlameChart()).toMatchSnapshot(); + }); + + it('supports coroutines', () => { + function Continuation({ isSame }) { + return ; + } + + function CoChild({ bar }) { + return ReactCoroutine.createYield({ + props: { + bar: bar, + }, + continuation: Continuation, + }); + } + + function Indirection() { + return [, ]; + } + + function HandleYields(props, yields) { + return yields.map(y => + + ); + } + + function CoParent(props) { + return ReactCoroutine.createCoroutine( + props.children, + HandleYields, + props + ); + } + + function App() { + return
; + } + + ReactNoop.render(); + ReactNoop.flush(); + expect(getFlameChart()).toMatchSnapshot(); + }); + + it('supports portals', () => { + const noopContainer = {children: []}; + ReactNoop.render( + + {ReactPortal.createPortal(, noopContainer, null)} + + ); + ReactNoop.flush(); + expect(getFlameChart()).toMatchSnapshot(); + }); +}); diff --git a/src/renderers/shared/fiber/__tests__/__snapshots__/ReactIncrementalPerf-test.js.snap b/src/renderers/shared/fiber/__tests__/__snapshots__/ReactIncrementalPerf-test.js.snap new file mode 100644 index 0000000000..4d51323884 --- /dev/null +++ b/src/renderers/shared/fiber/__tests__/__snapshots__/ReactIncrementalPerf-test.js.snap @@ -0,0 +1,260 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ReactDebugFiberPerf captures all lifecycles 1`] = ` +"// Mount +⚛ (React Tree Reconciliation) + ⚛ AllLifecycles [mount] + ⚛ AllLifecycles.componentWillMount + ⚛ AllLifecycles.getChildContext + ⚛ (Committing Changes) + ⚛ (Committing Host Effects: 1 Total) + ⚛ (Calling Lifecycle Methods: 1 Total) + ⚛ AllLifecycles.componentDidMount + +// Update +⚛ (React Tree Reconciliation) + ⚛ AllLifecycles [update] + ⚛ AllLifecycles.componentWillReceiveProps + ⚛ AllLifecycles.shouldComponentUpdate + ⚛ AllLifecycles.componentWillUpdate + ⚛ AllLifecycles.getChildContext + ⚛ (Committing Changes) + ⚛ (Committing Host Effects: 2 Total) + ⚛ (Calling Lifecycle Methods: 2 Total) + ⚛ AllLifecycles.componentDidUpdate + +// Unmount +⚛ (React Tree Reconciliation) + ⚛ (Committing Changes) + ⚛ (Committing Host Effects: 1 Total) + ⚛ AllLifecycles.componentWillUnmount + ⚛ (Calling Lifecycle Methods: 0 Total) +" +`; + +exports[`ReactDebugFiberPerf deduplicates lifecycle names during commit to reduce overhead 1`] = ` +"// The commit phase should mention A and B just once +⚛ (React Tree Reconciliation) + ⚛ Parent [update] + ⚛ A [update] + ⚛ B [update] + ⚛ A [update] + ⚛ B [update] + ⚛ (Committing Changes) + ⚛ (Committing Host Effects: 9 Total) + ⚛ (Calling Lifecycle Methods: 9 Total) + ⚛ A.componentDidUpdate + ⚛ B.componentDidUpdate + +// Because of deduplication, we don't know B was cascading, +// but we should still see the warning for the commit phase. +🛑 (React Tree Reconciliation) Warning: There were cascading updates + ⚛ Parent [update] + ⚛ A [update] + ⚛ B [update] + ⚛ A [update] + ⚛ B [update] + 🛑 (Committing Changes) Warning: Lifecycle hook scheduled a cascading update + ⚛ (Committing Host Effects: 9 Total) + ⚛ (Calling Lifecycle Methods: 9 Total) + ⚛ A.componentDidUpdate + ⚛ B.componentDidUpdate + ⚛ B [update] + 🛑 (Committing Changes) Warning: Caused by a cascading update in earlier commit + ⚛ (Committing Host Effects: 3 Total) + ⚛ (Calling Lifecycle Methods: 3 Total) + ⚛ B.componentDidUpdate +" +`; + +exports[`ReactDebugFiberPerf does not treat setState from cWM or cWRP as cascading 1`] = ` +"// Should not print a warning +⚛ (React Tree Reconciliation) + ⚛ Parent [mount] + ⚛ NotCascading [mount] + ⚛ NotCascading.componentWillMount + ⚛ (Committing Changes) + ⚛ (Committing Host Effects: 1 Total) + ⚛ (Calling Lifecycle Methods: 0 Total) + +// Should not print a warning +⚛ (React Tree Reconciliation) + ⚛ Parent [update] + ⚛ NotCascading [update] + ⚛ NotCascading.componentWillReceiveProps + ⚛ (Committing Changes) + ⚛ (Committing Host Effects: 2 Total) + ⚛ (Calling Lifecycle Methods: 2 Total) +" +`; + +exports[`ReactDebugFiberPerf measures a simple reconciliation 1`] = ` +"// Mount +⚛ (React Tree Reconciliation) + ⚛ Parent [mount] + ⚛ Child [mount] + ⚛ (Committing Changes) + ⚛ (Committing Host Effects: 1 Total) + ⚛ (Calling Lifecycle Methods: 0 Total) + +// Update +⚛ (React Tree Reconciliation) + ⚛ Parent [update] + ⚛ Child [update] + ⚛ (Committing Changes) + ⚛ (Committing Host Effects: 2 Total) + ⚛ (Calling Lifecycle Methods: 2 Total) + +// Unmount +⚛ (React Tree Reconciliation) + ⚛ (Committing Changes) + ⚛ (Committing Host Effects: 1 Total) + ⚛ (Calling Lifecycle Methods: 0 Total) +" +`; + +exports[`ReactDebugFiberPerf measures deferred work in chunks 1`] = ` +"// Start mounting Parent and A +⚛ (React Tree Reconciliation) + ⚛ Parent [mount] + ⚛ A [mount] + ⚛ Child [mount] + +// Mount B just a little (but not enough to memoize) +⚛ (React Tree Reconciliation) + ⚛ Parent [mount] + ⚛ B [mount] + +// Complete B and Parent +⚛ (React Tree Reconciliation) + ⚛ Parent [mount] + ⚛ B [mount] + ⚛ Child [mount] + ⚛ (Committing Changes) + ⚛ (Committing Host Effects: 1 Total) + ⚛ (Calling Lifecycle Methods: 0 Total) +" +`; + +exports[`ReactDebugFiberPerf measures deprioritized work 1`] = ` +"// Flush the parent +⚛ (React Tree Reconciliation) + ⚛ Parent [mount] + ⚛ (Committing Changes) + ⚛ (Committing Host Effects: 1 Total) + ⚛ (Calling Lifecycle Methods: 0 Total) + +// Flush the child +⚛ (React Tree Reconciliation) + ⚛ Child [mount] + ⚛ (Committing Changes) + ⚛ (Committing Host Effects: 3 Total) + ⚛ (Calling Lifecycle Methods: 2 Total) +" +`; + +exports[`ReactDebugFiberPerf recovers from caught errors 1`] = ` +"// Stop on Baddie and restart from Boundary +🛑 (React Tree Reconciliation) Warning: There were cascading updates + ⚛ Parent [mount] + ⚛ Boundary [mount] + ⚛ Parent [mount] + ⚛ Baddie [mount] + 🛑 (Committing Changes) Warning: Lifecycle hook scheduled a cascading update + ⚛ (Committing Host Effects: 2 Total) + ⚛ (Calling Lifecycle Methods: 1 Total) + ⚛ Boundary [update] + ⚛ ErrorReport [mount] + 🛑 (Committing Changes) Warning: Caused by a cascading update in earlier commit + ⚛ (Committing Host Effects: 2 Total) + ⚛ (Calling Lifecycle Methods: 1 Total) +" +`; + +exports[`ReactDebugFiberPerf recovers from fatal errors 1`] = ` +"// Will fatal +⚛ (React Tree Reconciliation) + ⚛ Parent [mount] + ⚛ Baddie [mount] + ⚛ (Committing Changes) + ⚛ (Committing Host Effects: 1 Total) + ⚛ (Calling Lifecycle Methods: 1 Total) + +// Will reconcile from a clean state +⚛ (React Tree Reconciliation) + ⚛ Parent [mount] + ⚛ Child [mount] + ⚛ (Committing Changes) + ⚛ (Committing Host Effects: 1 Total) + ⚛ (Calling Lifecycle Methods: 0 Total) +" +`; + +exports[`ReactDebugFiberPerf skips parents during setState 1`] = ` +"// Should include just A and B, no Parents +⚛ (React Tree Reconciliation) + ⚛ A [update] + ⚛ B [update] + ⚛ (Committing Changes) + ⚛ (Committing Host Effects: 6 Total) + ⚛ (Calling Lifecycle Methods: 6 Total) +" +`; + +exports[`ReactDebugFiberPerf supports coroutines 1`] = ` +"⚛ (React Tree Reconciliation) + ⚛ App [mount] + ⚛ CoParent [mount] + ⚛ HandleYields [mount] + ⚛ Indirection [mount] + ⚛ CoChild [mount] + ⚛ CoChild [mount] + ⚛ Continuation [mount] + ⚛ Continuation [mount] + ⚛ (Committing Changes) + ⚛ (Committing Host Effects: 3 Total) + ⚛ (Calling Lifecycle Methods: 0 Total) +" +`; + +exports[`ReactDebugFiberPerf supports portals 1`] = ` +"⚛ (React Tree Reconciliation) + ⚛ Parent [mount] + ⚛ Child [mount] + ⚛ (Committing Changes) + ⚛ (Committing Host Effects: 3 Total) + ⚛ (Calling Lifecycle Methods: 1 Total) +" +`; + +exports[`ReactDebugFiberPerf warns on cascading renders from setState 1`] = ` +"// Should print a warning +🛑 (React Tree Reconciliation) Warning: There were cascading updates + ⚛ Parent [mount] + ⚛ Cascading [mount] + 🛑 (Committing Changes) Warning: Lifecycle hook scheduled a cascading update + ⚛ (Committing Host Effects: 2 Total) + ⚛ (Calling Lifecycle Methods: 1 Total) + 🛑 Cascading.componentDidMount Warning: Scheduled a cascading update + ⚛ Cascading [update] + 🛑 (Committing Changes) Warning: Caused by a cascading update in earlier commit + ⚛ (Committing Host Effects: 2 Total) + ⚛ (Calling Lifecycle Methods: 2 Total) +" +`; + +exports[`ReactDebugFiberPerf warns on cascading renders from top-level render 1`] = ` +"// Rendering the first root +🛑 (React Tree Reconciliation) Warning: There were cascading updates + ⚛ Cascading [mount] + 🛑 (Committing Changes) Warning: Lifecycle hook scheduled a cascading update + ⚛ (Committing Host Effects: 1 Total) + ⚛ (Calling Lifecycle Methods: 1 Total) + 🛑 Cascading.componentDidMount Warning: Scheduled a cascading update + // Scheduling another root from componentDidMount + ⚛ Child [mount] + 🛑 (Committing Changes) Warning: Caused by a cascading update in earlier commit + ⚛ (Committing Host Effects: 1 Total) + ⚛ (Calling Lifecycle Methods: 0 Total) +" +`; From 266481dacf49dc73651cd5a33062cccfdeded84a Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 8 Mar 2017 21:03:21 +0000 Subject: [PATCH 28/32] Don't count skipped tests when calculating Fiber facts (#9136) --- scripts/fiber/record-tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/fiber/record-tests b/scripts/fiber/record-tests index 78cfabce04..b7c57bf18a 100755 --- a/scripts/fiber/record-tests +++ b/scripts/fiber/record-tests @@ -151,7 +151,7 @@ function recordTests(maxWorkers, trackFacts) { ); if (trackFacts) { - const fact = runResults.numPassedTests + '/' + runResults.numTotalTests; + const fact = runResults.numPassedTests + '/' + (runResults.numPassedTests + runResults.numFailedTests); // TODO: Shelling out here is silly. child_process.spawnSync( process.execPath, From 6559b10b11e5d35f730778b8c87386fa982a1c6e Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Thu, 9 Mar 2017 00:55:36 +0000 Subject: [PATCH 29/32] Use more widely supported emoji (#9139) --- .../shared/fiber/ReactDebugFiberPerf.js | 2 +- .../ReactIncrementalPerf-test.js.snap | 28 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/renderers/shared/fiber/ReactDebugFiberPerf.js b/src/renderers/shared/fiber/ReactDebugFiberPerf.js index 0c16eca8c0..0469cb9872 100644 --- a/src/renderers/shared/fiber/ReactDebugFiberPerf.js +++ b/src/renderers/shared/fiber/ReactDebugFiberPerf.js @@ -40,7 +40,7 @@ if (__DEV__) { // Prefix measurements so that it's possible to filter them. // Longer prefixes are hard to read in DevTools. const reactEmoji = '\u269B'; - const warningEmoji = '\uD83D\uDED1'; + const warningEmoji = '\u26D4'; const supportsUserTiming = typeof performance !== 'undefined' && typeof performance.mark === 'function' && diff --git a/src/renderers/shared/fiber/__tests__/__snapshots__/ReactIncrementalPerf-test.js.snap b/src/renderers/shared/fiber/__tests__/__snapshots__/ReactIncrementalPerf-test.js.snap index 4d51323884..760598a4ce 100644 --- a/src/renderers/shared/fiber/__tests__/__snapshots__/ReactIncrementalPerf-test.js.snap +++ b/src/renderers/shared/fiber/__tests__/__snapshots__/ReactIncrementalPerf-test.js.snap @@ -48,19 +48,19 @@ exports[`ReactDebugFiberPerf deduplicates lifecycle names during commit to reduc // Because of deduplication, we don't know B was cascading, // but we should still see the warning for the commit phase. -🛑 (React Tree Reconciliation) Warning: There were cascading updates +⛔ (React Tree Reconciliation) Warning: There were cascading updates ⚛ Parent [update] ⚛ A [update] ⚛ B [update] ⚛ A [update] ⚛ B [update] - 🛑 (Committing Changes) Warning: Lifecycle hook scheduled a cascading update + ⛔ (Committing Changes) Warning: Lifecycle hook scheduled a cascading update ⚛ (Committing Host Effects: 9 Total) ⚛ (Calling Lifecycle Methods: 9 Total) ⚛ A.componentDidUpdate ⚛ B.componentDidUpdate ⚛ B [update] - 🛑 (Committing Changes) Warning: Caused by a cascading update in earlier commit + ⛔ (Committing Changes) Warning: Caused by a cascading update in earlier commit ⚛ (Committing Host Effects: 3 Total) ⚛ (Calling Lifecycle Methods: 3 Total) ⚛ B.componentDidUpdate @@ -155,17 +155,17 @@ exports[`ReactDebugFiberPerf measures deprioritized work 1`] = ` exports[`ReactDebugFiberPerf recovers from caught errors 1`] = ` "// Stop on Baddie and restart from Boundary -🛑 (React Tree Reconciliation) Warning: There were cascading updates +⛔ (React Tree Reconciliation) Warning: There were cascading updates ⚛ Parent [mount] ⚛ Boundary [mount] ⚛ Parent [mount] ⚛ Baddie [mount] - 🛑 (Committing Changes) Warning: Lifecycle hook scheduled a cascading update + ⛔ (Committing Changes) Warning: Lifecycle hook scheduled a cascading update ⚛ (Committing Host Effects: 2 Total) ⚛ (Calling Lifecycle Methods: 1 Total) ⚛ Boundary [update] ⚛ ErrorReport [mount] - 🛑 (Committing Changes) Warning: Caused by a cascading update in earlier commit + ⛔ (Committing Changes) Warning: Caused by a cascading update in earlier commit ⚛ (Committing Host Effects: 2 Total) ⚛ (Calling Lifecycle Methods: 1 Total) " @@ -229,15 +229,15 @@ exports[`ReactDebugFiberPerf supports portals 1`] = ` exports[`ReactDebugFiberPerf warns on cascading renders from setState 1`] = ` "// Should print a warning -🛑 (React Tree Reconciliation) Warning: There were cascading updates +⛔ (React Tree Reconciliation) Warning: There were cascading updates ⚛ Parent [mount] ⚛ Cascading [mount] - 🛑 (Committing Changes) Warning: Lifecycle hook scheduled a cascading update + ⛔ (Committing Changes) Warning: Lifecycle hook scheduled a cascading update ⚛ (Committing Host Effects: 2 Total) ⚛ (Calling Lifecycle Methods: 1 Total) - 🛑 Cascading.componentDidMount Warning: Scheduled a cascading update + ⛔ Cascading.componentDidMount Warning: Scheduled a cascading update ⚛ Cascading [update] - 🛑 (Committing Changes) Warning: Caused by a cascading update in earlier commit + ⛔ (Committing Changes) Warning: Caused by a cascading update in earlier commit ⚛ (Committing Host Effects: 2 Total) ⚛ (Calling Lifecycle Methods: 2 Total) " @@ -245,15 +245,15 @@ exports[`ReactDebugFiberPerf warns on cascading renders from setState 1`] = ` exports[`ReactDebugFiberPerf warns on cascading renders from top-level render 1`] = ` "// Rendering the first root -🛑 (React Tree Reconciliation) Warning: There were cascading updates +⛔ (React Tree Reconciliation) Warning: There were cascading updates ⚛ Cascading [mount] - 🛑 (Committing Changes) Warning: Lifecycle hook scheduled a cascading update + ⛔ (Committing Changes) Warning: Lifecycle hook scheduled a cascading update ⚛ (Committing Host Effects: 1 Total) ⚛ (Calling Lifecycle Methods: 1 Total) - 🛑 Cascading.componentDidMount Warning: Scheduled a cascading update + ⛔ Cascading.componentDidMount Warning: Scheduled a cascading update // Scheduling another root from componentDidMount ⚛ Child [mount] - 🛑 (Committing Changes) Warning: Caused by a cascading update in earlier commit + ⛔ (Committing Changes) Warning: Caused by a cascading update in earlier commit ⚛ (Committing Host Effects: 1 Total) ⚛ (Calling Lifecycle Methods: 0 Total) " From 7fa311bcd95dbc8e1e0655c1aafc62ed61ddf022 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Thu, 9 Mar 2017 20:46:55 +0000 Subject: [PATCH 30/32] Reorder sections in alphabetical order (#9143) * Reorder sections in alphabetical order * Reorder here too --- docs/docs/installation.md | 16 ++++++++-------- docs/docs/optimizing-performance.md | 24 ++++++++++++------------ 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/docs/docs/installation.md b/docs/docs/installation.md index 15da2f4d17..d4ee06ae54 100644 --- a/docs/docs/installation.md +++ b/docs/docs/installation.md @@ -95,26 +95,26 @@ Similarly, you can render a React component inside a DOM element somewhere insid By default, React includes many helpful warnings. These warnings are very useful in development. However, they make React larger and slower so you should make sure to use the production version when you deploy the app. -#### Create React App - -If you use [Create React App](https://github.com/facebookincubator/create-react-app), `npm run build` will create an optimized build of your app in the `build` folder. - #### Brunch To create an optimized production build with Brunch, just add the `-p` flag to the build command. See the [Brunch docs](http://brunch.io/docs/commands) for more details. -#### Webpack - -Include both `DefinePlugin` and `UglifyJsPlugin` into your production Webpack configuration as described in [this guide](https://webpack.js.org/guides/production-build/). - #### Browserify Run Browserify with `NODE_ENV` environment variable set to `production` and use [UglifyJS](https://github.com/mishoo/UglifyJS) as the last build step so that development-only code gets stripped out. +#### Create React App + +If you use [Create React App](https://github.com/facebookincubator/create-react-app), `npm run build` will create an optimized build of your app in the `build` folder. + #### Rollup Use [rollup-plugin-replace](https://github.com/rollup/rollup-plugin-replace) plugin together with [rollup-plugin-commonjs](https://github.com/rollup/rollup-plugin-commonjs) (in that order) to remove development-only code. [See this gist](https://gist.github.com/Rich-Harris/cb14f4bc0670c47d00d191565be36bf0) for a complete setup example. +#### Webpack + +Include both `DefinePlugin` and `UglifyJsPlugin` into your production Webpack configuration as described in [this guide](https://webpack.js.org/guides/production-build/). + ### Using a CDN If you don't want to use npm to manage client packages, the `react` and `react-dom` npm packages also provide single-file distributions in `dist` folders, which are hosted on a CDN: diff --git a/docs/docs/optimizing-performance.md b/docs/docs/optimizing-performance.md index 86acb8cdb7..cbb1b9849b 100644 --- a/docs/docs/optimizing-performance.md +++ b/docs/docs/optimizing-performance.md @@ -11,21 +11,10 @@ Internally, React uses several clever techniques to minimize the number of costl If you're benchmarking or experiencing performance problems in your React apps, make sure you're testing with the minified production build: -* For Create React App, you need to run `npm run build` and follow the instructions. * For single-file builds, we offer production-ready `.min.js` versions. * For Brunch, you need to add the `-p` flag to the `build` command. * For Browserify, you need to run it with `NODE_ENV=production`. -* For Webpack, you need to add this to plugins in your production config: - -```js -new webpack.DefinePlugin({ - 'process.env': { - NODE_ENV: JSON.stringify('production') - } -}), -new webpack.optimize.UglifyJsPlugin() -``` - +* For Create React App, you need to run `npm run build` and follow the instructions. * For Rollup, you need to use the [replace](https://github.com/rollup/rollup-plugin-replace) plugin *before* the [commonjs](https://github.com/rollup/rollup-plugin-commonjs) plugin so that development-only modules are not imported. For a complete setup example [see this gist](https://gist.github.com/Rich-Harris/cb14f4bc0670c47d00d191565be36bf0). ```js @@ -38,6 +27,17 @@ plugins: [ ] ``` +* For Webpack, you need to add this to plugins in your production config: + +```js +new webpack.DefinePlugin({ + 'process.env': { + NODE_ENV: JSON.stringify('production') + } +}), +new webpack.optimize.UglifyJsPlugin() +``` + The development build includes extra warnings that are helpful when building your apps, but it is slower due to the extra bookkeeping it does. ## Profiling Components with Chrome Timeline From c96482d37f94b537648da8e32fcefb23fd172545 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Fri, 10 Mar 2017 01:17:27 +0000 Subject: [PATCH 31/32] Remove PR check from GH Pages build hook for stable branch (#9144) --- scripts/circleci/build_gh_pages.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/circleci/build_gh_pages.sh b/scripts/circleci/build_gh_pages.sh index fcf49f2af9..c0ecc81e35 100755 --- a/scripts/circleci/build_gh_pages.sh +++ b/scripts/circleci/build_gh_pages.sh @@ -2,7 +2,7 @@ set -e -if [ -z $CI_PULL_REQUEST ] && [ "$CIRCLE_BRANCH" = "$REACT_WEBSITE_BRANCH" ]; then +if [ "$CIRCLE_BRANCH" = "$REACT_WEBSITE_BRANCH" ]; then GH_PAGES_DIR=`pwd`/../react-gh-pages From 8de68c56f9fe9b9533d604853783be3e65a312fa Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Fri, 10 Mar 2017 19:29:20 +0000 Subject: [PATCH 32/32] [Fiber] Remove unnecessary second getComponentName() (#9153) * Remove unnecessary second getComponentName() We already have one. The other one is also less fragile and doesn't throw on null type. * Make setState in render warning check simpler This skips it earlier since in most cases phase won't be "render". This is unobservable. --- src/renderers/shared/fiber/ReactChildFiber.js | 2 +- .../shared/fiber/ReactFiberClassComponent.js | 5 +++-- src/renderers/shared/fiber/ReactFiberContext.js | 10 +++++----- .../shared/fiber/ReactFiberReconciler.js | 11 +++++++---- .../shared/fiber/ReactFiberTreeReflection.js | 15 ++------------- 5 files changed, 18 insertions(+), 25 deletions(-) diff --git a/src/renderers/shared/fiber/ReactChildFiber.js b/src/renderers/shared/fiber/ReactChildFiber.js index a79a124fcd..0d51f03db6 100644 --- a/src/renderers/shared/fiber/ReactChildFiber.js +++ b/src/renderers/shared/fiber/ReactChildFiber.js @@ -40,7 +40,7 @@ var ReactCurrentOwner = require('react/lib/ReactCurrentOwner'); if (__DEV__) { var { getCurrentFiberStackAddendum } = require('ReactDebugCurrentFiber'); - var { getComponentName } = require('ReactFiberTreeReflection'); + var getComponentName = require('getComponentName'); var warning = require('fbjs/lib/warning'); var didWarnAboutMaps = false; } diff --git a/src/renderers/shared/fiber/ReactFiberClassComponent.js b/src/renderers/shared/fiber/ReactFiberClassComponent.js index 47017cf292..2b0eb304b5 100644 --- a/src/renderers/shared/fiber/ReactFiberClassComponent.js +++ b/src/renderers/shared/fiber/ReactFiberClassComponent.js @@ -31,9 +31,10 @@ var { beginUpdateQueue, } = require('ReactFiberUpdateQueue'); var { hasContextChanged } = require('ReactFiberContext'); -var { getComponentName, isMounted } = require('ReactFiberTreeReflection'); +var { isMounted } = require('ReactFiberTreeReflection'); var ReactInstanceMap = require('ReactInstanceMap'); var emptyObject = require('fbjs/lib/emptyObject'); +var getComponentName = require('getComponentName'); var shallowEqual = require('fbjs/lib/shallowEqual'); var invariant = require('fbjs/lib/invariant'); @@ -119,7 +120,7 @@ module.exports = function( shouldUpdate !== undefined, '%s.shouldComponentUpdate(): Returned undefined instead of a ' + 'boolean value. Make sure to return true or false.', - getComponentName(workInProgress) + getComponentName(workInProgress) || 'Unknown' ); } diff --git a/src/renderers/shared/fiber/ReactFiberContext.js b/src/renderers/shared/fiber/ReactFiberContext.js index 7e0122fd9f..81410161d4 100644 --- a/src/renderers/shared/fiber/ReactFiberContext.js +++ b/src/renderers/shared/fiber/ReactFiberContext.js @@ -16,10 +16,10 @@ import type { Fiber } from 'ReactFiber'; import type { StackCursor } from 'ReactFiberStack'; var emptyObject = require('fbjs/lib/emptyObject'); +var getComponentName = require('getComponentName'); var invariant = require('fbjs/lib/invariant'); var warning = require('fbjs/lib/warning'); var { - getComponentName, isFiberMounted, } = require('ReactFiberTreeReflection'); var { @@ -96,7 +96,7 @@ exports.getMaskedContext = function(workInProgress : Fiber, unmaskedContext : Ob } if (__DEV__) { - const name = getComponentName(workInProgress); + const name = getComponentName(workInProgress) || 'Unknown'; ReactDebugCurrentFrame.current = workInProgress; checkReactTypeSpec(contextTypes, context, 'context', name); ReactDebugCurrentFrame.current = null; @@ -156,7 +156,7 @@ function processChildContext(fiber : Fiber, parentContext : Object, isReconcilin // It has only been added in Fiber to match the (unintentional) behavior in Stack. if (typeof instance.getChildContext !== 'function') { if (__DEV__) { - const componentName = getComponentName(fiber); + const componentName = getComponentName(fiber) || 'Unknown'; if (!warnedAboutMissingGetChildContext[componentName]) { warnedAboutMissingGetChildContext[componentName] = true; @@ -187,12 +187,12 @@ function processChildContext(fiber : Fiber, parentContext : Object, isReconcilin invariant( contextKey in childContextTypes, '%s.getChildContext(): key "%s" is not defined in childContextTypes.', - getComponentName(fiber), + getComponentName(fiber) || 'Unknown', contextKey ); } if (__DEV__) { - const name = getComponentName(fiber); + const name = getComponentName(fiber) || 'Unknown'; // We can only provide accurate element stacks if we pass work-in-progress tree // during the begin or complete phase. However currently this function is also // called from unstable_renderSubtree legacy implementation. In this case it unsafe to diff --git a/src/renderers/shared/fiber/ReactFiberReconciler.js b/src/renderers/shared/fiber/ReactFiberReconciler.js index 8cc4235790..f1ae92f1a3 100644 --- a/src/renderers/shared/fiber/ReactFiberReconciler.js +++ b/src/renderers/shared/fiber/ReactFiberReconciler.js @@ -33,7 +33,7 @@ if (__DEV__) { var warning = require('fbjs/lib/warning'); var ReactFiberInstrumentation = require('ReactFiberInstrumentation'); var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber'); - var { getComponentName } = require('ReactFiberTreeReflection'); + var getComponentName = require('getComponentName'); } var { findCurrentHostFiber } = require('ReactFiberTreeReflection'); @@ -148,14 +148,17 @@ module.exports = function( function scheduleTopLevelUpdate(current : Fiber, element : ReactNodeList, callback : ?Function) { if (__DEV__) { - if (ReactDebugCurrentFiber.current !== null) { + if ( + ReactDebugCurrentFiber.phase === 'render' && + ReactDebugCurrentFiber.current !== null + ) { warning( - ReactDebugCurrentFiber.phase !== 'render', + false, 'Render methods should be a pure function of props and state; ' + 'triggering nested component updates from render is not allowed. ' + 'If necessary, trigger nested updates in componentDidUpdate.\n\n' + 'Check the render method of %s.', - getComponentName(ReactDebugCurrentFiber.current) + getComponentName(ReactDebugCurrentFiber.current) || 'Unknown' ); } } diff --git a/src/renderers/shared/fiber/ReactFiberTreeReflection.js b/src/renderers/shared/fiber/ReactFiberTreeReflection.js index 491156dbf7..3dd34069df 100644 --- a/src/renderers/shared/fiber/ReactFiberTreeReflection.js +++ b/src/renderers/shared/fiber/ReactFiberTreeReflection.js @@ -17,6 +17,7 @@ import type { Fiber } from 'ReactFiber'; var ReactInstanceMap = require('ReactInstanceMap'); var ReactCurrentOwner = require('react/lib/ReactCurrentOwner'); +var getComponentName = require('getComponentName'); var invariant = require('fbjs/lib/invariant'); if (__DEV__) { @@ -84,7 +85,7 @@ exports.isMounted = function(component : ReactComponent) : boolea 'never access something that requires stale data from the previous ' + 'render, such as refs. Move this logic to componentDidMount and ' + 'componentDidUpdate instead.', - getComponentName(ownerFiber) + getComponentName(ownerFiber) || 'A component' ); instance._warnedAboutRefsInRender = true; } @@ -266,15 +267,3 @@ exports.findCurrentHostFiber = function(parent : Fiber) : Fiber | null { // eslint-disable-next-line no-unreachable return null; }; - -function getComponentName(fiber: Fiber): string { - const type = fiber.type; - const instance = fiber.stateNode; - const constructor = instance && instance.constructor; - return ( - type.displayName || (constructor && constructor.displayName) || - type.name || (constructor && constructor.name) || - 'A Component' - ); -} -exports.getComponentName = getComponentName;