diff --git a/backend/ReactSymbols.js b/backend/ReactSymbols.js deleted file mode 100644 index a049c679a8..0000000000 --- a/backend/ReactSymbols.js +++ /dev/null @@ -1,30 +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 - */ -'use strict'; - -// Based on the React repo. - -module.exports = { - ASYNC_MODE_NUMBER: 0xeacf, - ASYNC_MODE_SYMBOL_STRING: 'Symbol(react.async_mode)', - CONTEXT_CONSUMER_NUMBER: 0xeace, - CONTEXT_CONSUMER_SYMBOL_STRING: 'Symbol(react.context)', - CONTEXT_PROVIDER_NUMBER: 0xeacd, - CONTEXT_PROVIDER_SYMBOL_STRING: 'Symbol(react.provider)', - FORWARD_REF_NUMBER: 0xead0, - FORWARD_REF_SYMBOL_STRING: 'Symbol(react.forward_ref)', - PROFILER_NUMBER: 0xead2, - PROFILER_SYMBOL_STRING: 'Symbol(react.profiler)', - STRICT_MODE_NUMBER: 0xeacc, - STRICT_MODE_SYMBOL_STRING: 'Symbol(react.strict_mode)', - PLACEHOLDER_NUMBER: 0xead1, - PLACEHOLDER_SYMBOL_STRING: 'Symbol(react.placeholder)', -}; diff --git a/backend/ReactTypeOfWork.js b/backend/ReactTypeOfWork.js deleted file mode 100644 index 90f488f3c0..0000000000 --- a/backend/ReactTypeOfWork.js +++ /dev/null @@ -1,33 +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 - */ -'use strict'; - -// Copied from React repo. - -module.exports = { - IndeterminateComponent: 0, // Before we know whether it is functional or class - FunctionalComponent: 1, - ClassComponent: 2, - HostRoot: 3, // Root of a host tree. Could be nested inside another node. - HostPortal: 4, // A subtree. Could be an entry point to a different renderer. - HostComponent: 5, - HostText: 6, - CoroutineComponent: 7, - CoroutineHandlerPhase: 8, - YieldComponent: 9, - Fragment: 10, - Mode: 11, - ContextConsumer: 12, - ContextProvider: 13, - ForwardRef: 14, - Profiler: 15, - Placeholder: 16, -}; diff --git a/backend/attachRendererFiber.js b/backend/attachRendererFiber.js index 2f7a311fd5..02b6ce3c86 100644 --- a/backend/attachRendererFiber.js +++ b/backend/attachRendererFiber.js @@ -10,19 +10,355 @@ */ 'use strict'; -import type {Hook, ReactRenderer, Helpers} from './types'; -var getDataFiber = require('./getDataFiber'); -var { - ClassComponent, - FunctionalComponent, - ContextConsumer, - HostRoot, -} = require('./ReactTypeOfWork'); - -// Inlined from ReactTypeOfSideEffect -var PerformedWork = 1; +import type {Hook, ReactRenderer, DataType, Helpers} from './types'; + +var semver = require('semver'); + +var copyWithSet = require('./copyWithSet'); +var getDisplayName = require('./getDisplayName'); + +function getInternalReactConstants(version) { + var ReactTypeOfWork; + var ReactSymbols; + var ReactTypeOfSideEffect; + + // ********************************************************** + // The section below is copy-pasted from files in React repo. + // Keep it in sync, and add version guards if it changes. + // ********************************************************** + if (semver.gte(version, '16.4.3')) { + ReactTypeOfWork = { + IndeterminateComponent: 0, + FunctionalComponent: 1, + FunctionalComponentLazy: 2, + ClassComponent: 3, + ClassComponentLazy: 4, + HostRoot: 5, + HostPortal: 6, + HostComponent: 7, + HostText: 8, + Fragment: 9, + Mode: 10, + ContextConsumer: 11, + ContextProvider: 12, + ForwardRef: 13, + ForwardRefLazy: 14, + Profiler: 15, + PlaceholderComponent: 16, + }; + } else { + ReactTypeOfWork = { + IndeterminateComponent: 0, + FunctionalComponent: 1, + ClassComponent: 2, + HostRoot: 3, + HostPortal: 4, + HostComponent: 5, + HostText: 6, + CoroutineComponent: 7, + CoroutineHandlerPhase: 8, + YieldComponent: 9, + Fragment: 10, + Mode: 11, + ContextConsumer: 12, + ContextProvider: 13, + ForwardRef: 14, + Profiler: 15, + Placeholder: 16, + }; + } + ReactSymbols = { + ASYNC_MODE_NUMBER: 0xeacf, + ASYNC_MODE_SYMBOL_STRING: 'Symbol(react.async_mode)', + CONTEXT_CONSUMER_NUMBER: 0xeace, + CONTEXT_CONSUMER_SYMBOL_STRING: 'Symbol(react.context)', + CONTEXT_PROVIDER_NUMBER: 0xeacd, + CONTEXT_PROVIDER_SYMBOL_STRING: 'Symbol(react.provider)', + FORWARD_REF_NUMBER: 0xead0, + FORWARD_REF_SYMBOL_STRING: 'Symbol(react.forward_ref)', + PROFILER_NUMBER: 0xead2, + PROFILER_SYMBOL_STRING: 'Symbol(react.profiler)', + STRICT_MODE_NUMBER: 0xeacc, + STRICT_MODE_SYMBOL_STRING: 'Symbol(react.strict_mode)', + PLACEHOLDER_NUMBER: 0xead1, + PLACEHOLDER_SYMBOL_STRING: 'Symbol(react.placeholder)', + }; + ReactTypeOfSideEffect = { + PerformedWork: 1, + }; + // ********************************************************** + // End of copy paste. + // ********************************************************** + return { + ReactTypeOfWork, + ReactSymbols, + ReactTypeOfSideEffect, + }; +} function attachRendererFiber(hook: Hook, rid: string, renderer: ReactRenderer): Helpers { + var {ReactTypeOfWork, ReactSymbols, ReactTypeOfSideEffect} = getInternalReactConstants(renderer.version); + var {PerformedWork} = ReactTypeOfSideEffect; + var { + FunctionalComponent, + ClassComponent, + ContextConsumer, + HostRoot, + HostPortal, + HostComponent, + HostText, + Fragment, + } = ReactTypeOfWork; + var { + ASYNC_MODE_NUMBER, + ASYNC_MODE_SYMBOL_STRING, + CONTEXT_CONSUMER_NUMBER, + CONTEXT_CONSUMER_SYMBOL_STRING, + CONTEXT_PROVIDER_NUMBER, + CONTEXT_PROVIDER_SYMBOL_STRING, + FORWARD_REF_NUMBER, + FORWARD_REF_SYMBOL_STRING, + PROFILER_NUMBER, + PROFILER_SYMBOL_STRING, + STRICT_MODE_NUMBER, + STRICT_MODE_SYMBOL_STRING, + PLACEHOLDER_NUMBER, + PLACEHOLDER_SYMBOL_STRING, + } = ReactSymbols; + + // TODO: we might want to change the data structure + // once we no longer suppport Stack versions of `getData`. + function getDataFiber(fiber: Object): DataType { + var type = fiber.type; + var key = fiber.key; + var ref = fiber.ref; + var source = fiber._debugSource; + var publicInstance = null; + var props = null; + var state = null; + var children = null; + var context = null; + var updater = null; + var nodeType = null; + var name = null; + var text = null; + + // Profiler data + var actualDuration = null; + var actualStartTime = null; + var treeBaseDuration = null; + + switch (fiber.tag) { + case FunctionalComponent: + case ClassComponent: + nodeType = 'Composite'; + name = getDisplayName(fiber.type); + publicInstance = fiber.stateNode; + props = fiber.memoizedProps; + state = fiber.memoizedState; + if (publicInstance != null) { + context = publicInstance.context; + if (context && Object.keys(context).length === 0) { + context = null; + } + } + const inst = publicInstance; + if (inst) { + updater = { + setState: inst.setState && inst.setState.bind(inst), + forceUpdate: inst.forceUpdate && inst.forceUpdate.bind(inst), + setInProps: inst.forceUpdate && setInProps.bind(null, fiber), + setInState: inst.forceUpdate && setInState.bind(null, inst), + setInContext: inst.forceUpdate && setInContext.bind(null, inst), + }; + } + children = []; + break; + case HostRoot: + nodeType = 'Wrapper'; + children = []; + break; + case HostPortal: + nodeType = 'Portal'; + name = 'ReactPortal'; + props = { + target: fiber.stateNode.containerInfo, + }; + children = []; + break; + case HostComponent: + nodeType = 'Native'; + name = fiber.type; + + // TODO (bvaughn) we plan to remove this prefix anyway. + // We can cut this special case out when it's gone. + name = name.replace('topsecret-', ''); + + publicInstance = fiber.stateNode; + props = fiber.memoizedProps; + if ( + typeof props.children === 'string' || + typeof props.children === 'number' + ) { + children = props.children.toString(); + } else { + children = []; + } + if (typeof fiber.stateNode.setNativeProps === 'function') { + // For editing styles in RN + updater = { + setNativeProps(nativeProps) { + fiber.stateNode.setNativeProps(nativeProps); + }, + }; + } + break; + case HostText: + nodeType = 'Text'; + text = fiber.memoizedProps; + break; + case Fragment: + nodeType = 'Wrapper'; + children = []; + break; + default: // Coroutines and yields + const symbolOrNumber = typeof type === 'object' && type !== null + ? type.$$typeof + : type; + // $FlowFixMe facebook/flow/issues/2362 + const switchValue = typeof symbolOrNumber === 'symbol' + ? symbolOrNumber.toString() + : symbolOrNumber; + + switch (switchValue) { + case ASYNC_MODE_NUMBER: + case ASYNC_MODE_SYMBOL_STRING: + nodeType = 'Special'; + name = 'AsyncMode'; + children = []; + break; + case CONTEXT_PROVIDER_NUMBER: + case CONTEXT_PROVIDER_SYMBOL_STRING: + nodeType = 'Special'; + props = fiber.memoizedProps; + name = 'Context.Provider'; + children = []; + break; + case CONTEXT_CONSUMER_NUMBER: + case CONTEXT_CONSUMER_SYMBOL_STRING: + nodeType = 'Special'; + props = fiber.memoizedProps; + // TODO: TraceUpdatesBackendManager currently depends on this. + // If you change .name, figure out a more resilient way to detect it. + name = 'Context.Consumer'; + children = []; + break; + case STRICT_MODE_NUMBER: + case STRICT_MODE_SYMBOL_STRING: + nodeType = 'Special'; + name = 'StrictMode'; + children = []; + break; + case FORWARD_REF_NUMBER: + case FORWARD_REF_SYMBOL_STRING: + const functionName = getDisplayName(fiber.type.render, ''); + nodeType = 'Special'; + name = functionName !== '' ? `ForwardRef(${functionName})` : 'ForwardRef'; + children = []; + break; + case PLACEHOLDER_NUMBER: + case PLACEHOLDER_SYMBOL_STRING: + nodeType = 'Special'; + name = 'Placeholder'; + props = fiber.memoizedProps; + children = []; + break; + case PROFILER_NUMBER: + case PROFILER_SYMBOL_STRING: + nodeType = 'Special'; + props = fiber.memoizedProps; + name = `Profiler(${fiber.memoizedProps.id})`; + children = []; + break; + default: + nodeType = 'Native'; + props = fiber.memoizedProps; + name = 'TODO_NOT_IMPLEMENTED_YET'; + children = []; + break; + } + break; + } + + if (Array.isArray(children)) { + let child = fiber.child; + while (child) { + children.push(getOpaqueNode(child)); + child = child.sibling; + } + } + + if (fiber.actualDuration !== undefined) { + actualDuration = fiber.actualDuration; + actualStartTime = fiber.actualStartTime; + treeBaseDuration = fiber.treeBaseDuration; + } + + // $FlowFixMe + return { + nodeType, + type, + key, + ref, + source, + name, + props, + state, + context, + children, + text, + updater, + publicInstance, + + // Profiler data + actualDuration, + actualStartTime, + treeBaseDuration, + }; + } + + function setInProps(fiber, path: Array, value: any) { + const inst = fiber.stateNode; + fiber.pendingProps = copyWithSet(inst.props, path, value); + if (fiber.alternate) { + // We don't know which fiber is the current one because DevTools may bail out of getDataFiber() call, + // and so the data object may refer to another version of the fiber. Therefore we update pendingProps + // on both. I hope that this is safe. + fiber.alternate.pendingProps = fiber.pendingProps; + } + fiber.stateNode.forceUpdate(); + } + + function setInState(inst, path: Array, value: any) { + setIn(inst.state, path, value); + inst.forceUpdate(); + } + + function setInContext(inst, path: Array, value: any) { + setIn(inst.context, path, value); + inst.forceUpdate(); + } + + function setIn(obj: Object, path: Array, value: any) { + var last = path.pop(); + var parent = path.reduce((obj_, attr) => obj_ ? obj_[attr] : null, obj); + if (parent) { + parent[last] = value; + } + } + + + // This is a slightly annoying indirection. // It is currently necessary because DevTools wants // to use unique objects as keys for instances. @@ -90,7 +426,7 @@ function attachRendererFiber(hook: Hook, rid: string, renderer: ReactRenderer): function enqueueMount(fiber) { pendingEvents.push({ internalInstance: getOpaqueNode(fiber), - data: getDataFiber(fiber, getOpaqueNode), + data: getDataFiber(fiber), renderer: rid, type: 'mount', }); @@ -117,7 +453,7 @@ function attachRendererFiber(hook: Hook, rid: string, renderer: ReactRenderer): if (haveProfilerTimesChanged(fiber.alternate, fiber)) { pendingEvents.push({ internalInstance: getOpaqueNode(fiber), - data: getDataFiber(fiber, getOpaqueNode), + data: getDataFiber(fiber), renderer: rid, type: 'updateProfileTimes', }); @@ -126,7 +462,7 @@ function attachRendererFiber(hook: Hook, rid: string, renderer: ReactRenderer): } pendingEvents.push({ internalInstance: getOpaqueNode(fiber), - data: getDataFiber(fiber, getOpaqueNode), + data: getDataFiber(fiber), renderer: rid, type: 'update', }); diff --git a/backend/getData.js b/backend/getData.js index 6356ce746f..cccb4fa1e4 100644 --- a/backend/getData.js +++ b/backend/getData.js @@ -10,6 +10,11 @@ */ 'use strict'; +// ---------------------------------------------------- +// This is Stack-only version. +// The Fiber version is inlined in attachRendererFiber. +// ---------------------------------------------------- + import type {DataType} from './types'; var copyWithSet = require('./copyWithSet'); var getDisplayName = require('./getDisplayName'); diff --git a/backend/getData012.js b/backend/getData012.js index 577b26b3fb..d5a329f748 100644 --- a/backend/getData012.js +++ b/backend/getData012.js @@ -10,6 +10,11 @@ */ 'use strict'; +// ---------------------------------------------------- +// This is Stack-only version. +// The Fiber version is inlined in attachRendererFiber. +// ---------------------------------------------------- + import type {DataType} from './types'; var copyWithSet = require('./copyWithSet'); diff --git a/backend/getDataFiber.js b/backend/getDataFiber.js deleted file mode 100644 index 9856e2c31f..0000000000 --- a/backend/getDataFiber.js +++ /dev/null @@ -1,273 +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 - */ -'use strict'; - -import type {DataType} from './types'; -var copyWithSet = require('./copyWithSet'); -var getDisplayName = require('./getDisplayName'); -var { - FunctionalComponent, - ClassComponent, - HostRoot, - HostPortal, - HostComponent, - HostText, - Fragment, -} = require('./ReactTypeOfWork'); -var { - ASYNC_MODE_NUMBER, - ASYNC_MODE_SYMBOL_STRING, - CONTEXT_CONSUMER_NUMBER, - CONTEXT_CONSUMER_SYMBOL_STRING, - CONTEXT_PROVIDER_NUMBER, - CONTEXT_PROVIDER_SYMBOL_STRING, - FORWARD_REF_NUMBER, - FORWARD_REF_SYMBOL_STRING, - PROFILER_NUMBER, - PROFILER_SYMBOL_STRING, - STRICT_MODE_NUMBER, - STRICT_MODE_SYMBOL_STRING, - PLACEHOLDER_NUMBER, - PLACEHOLDER_SYMBOL_STRING, -} = require('./ReactSymbols'); - -// TODO: we might want to change the data structure -// once we no longer suppport Stack versions of `getData`. -function getDataFiber(fiber: Object, getOpaqueNode: (fiber: Object) => Object): DataType { - var type = fiber.type; - var key = fiber.key; - var ref = fiber.ref; - var source = fiber._debugSource; - var publicInstance = null; - var props = null; - var state = null; - var children = null; - var context = null; - var updater = null; - var nodeType = null; - var name = null; - var text = null; - - // Profiler data - var actualDuration = null; - var actualStartTime = null; - var treeBaseDuration = null; - - switch (fiber.tag) { - case FunctionalComponent: - case ClassComponent: - nodeType = 'Composite'; - name = getDisplayName(fiber.type); - publicInstance = fiber.stateNode; - props = fiber.memoizedProps; - state = fiber.memoizedState; - if (publicInstance != null) { - context = publicInstance.context; - if (context && Object.keys(context).length === 0) { - context = null; - } - } - const inst = publicInstance; - if (inst) { - updater = { - setState: inst.setState && inst.setState.bind(inst), - forceUpdate: inst.forceUpdate && inst.forceUpdate.bind(inst), - setInProps: inst.forceUpdate && setInProps.bind(null, fiber), - setInState: inst.forceUpdate && setInState.bind(null, inst), - setInContext: inst.forceUpdate && setInContext.bind(null, inst), - }; - } - children = []; - break; - case HostRoot: - nodeType = 'Wrapper'; - children = []; - break; - case HostPortal: - nodeType = 'Portal'; - name = 'ReactPortal'; - props = { - target: fiber.stateNode.containerInfo, - }; - children = []; - break; - case HostComponent: - nodeType = 'Native'; - name = fiber.type; - - // TODO (bvaughn) we plan to remove this prefix anyway. - // We can cut this special case out when it's gone. - name = name.replace('topsecret-', ''); - - publicInstance = fiber.stateNode; - props = fiber.memoizedProps; - if ( - typeof props.children === 'string' || - typeof props.children === 'number' - ) { - children = props.children.toString(); - } else { - children = []; - } - if (typeof fiber.stateNode.setNativeProps === 'function') { - // For editing styles in RN - updater = { - setNativeProps(nativeProps) { - fiber.stateNode.setNativeProps(nativeProps); - }, - }; - } - break; - case HostText: - nodeType = 'Text'; - text = fiber.memoizedProps; - break; - case Fragment: - nodeType = 'Wrapper'; - children = []; - break; - default: // Coroutines and yields - const symbolOrNumber = typeof type === 'object' && type !== null - ? type.$$typeof - : type; - // $FlowFixMe facebook/flow/issues/2362 - const switchValue = typeof symbolOrNumber === 'symbol' - ? symbolOrNumber.toString() - : symbolOrNumber; - - switch (switchValue) { - case ASYNC_MODE_NUMBER: - case ASYNC_MODE_SYMBOL_STRING: - nodeType = 'Special'; - name = 'AsyncMode'; - children = []; - break; - case CONTEXT_PROVIDER_NUMBER: - case CONTEXT_PROVIDER_SYMBOL_STRING: - nodeType = 'Special'; - props = fiber.memoizedProps; - name = 'Context.Provider'; - children = []; - break; - case CONTEXT_CONSUMER_NUMBER: - case CONTEXT_CONSUMER_SYMBOL_STRING: - nodeType = 'Special'; - props = fiber.memoizedProps; - // TODO: TraceUpdatesBackendManager currently depends on this. - // If you change .name, figure out a more resilient way to detect it. - name = 'Context.Consumer'; - children = []; - break; - case STRICT_MODE_NUMBER: - case STRICT_MODE_SYMBOL_STRING: - nodeType = 'Special'; - name = 'StrictMode'; - children = []; - break; - case FORWARD_REF_NUMBER: - case FORWARD_REF_SYMBOL_STRING: - const functionName = getDisplayName(fiber.type.render, ''); - nodeType = 'Special'; - name = functionName !== '' ? `ForwardRef(${functionName})` : 'ForwardRef'; - children = []; - break; - case PLACEHOLDER_NUMBER: - case PLACEHOLDER_SYMBOL_STRING: - nodeType = 'Special'; - name = 'Placeholder'; - props = fiber.memoizedProps; - children = []; - break; - case PROFILER_NUMBER: - case PROFILER_SYMBOL_STRING: - nodeType = 'Special'; - props = fiber.memoizedProps; - name = `Profiler(${fiber.memoizedProps.id})`; - children = []; - break; - default: - nodeType = 'Native'; - props = fiber.memoizedProps; - name = 'TODO_NOT_IMPLEMENTED_YET'; - children = []; - break; - } - break; - } - - if (Array.isArray(children)) { - let child = fiber.child; - while (child) { - children.push(getOpaqueNode(child)); - child = child.sibling; - } - } - - if (fiber.actualDuration !== undefined) { - actualDuration = fiber.actualDuration; - actualStartTime = fiber.actualStartTime; - treeBaseDuration = fiber.treeBaseDuration; - } - - // $FlowFixMe - return { - nodeType, - type, - key, - ref, - source, - name, - props, - state, - context, - children, - text, - updater, - publicInstance, - - // Profiler data - actualDuration, - actualStartTime, - treeBaseDuration, - }; -} - -function setInProps(fiber, path: Array, value: any) { - const inst = fiber.stateNode; - fiber.pendingProps = copyWithSet(inst.props, path, value); - if (fiber.alternate) { - // We don't know which fiber is the current one because DevTools may bail out of getDataFiber() call, - // and so the data object may refer to another version of the fiber. Therefore we update pendingProps - // on both. I hope that this is safe. - fiber.alternate.pendingProps = fiber.pendingProps; - } - fiber.stateNode.forceUpdate(); -} - -function setInState(inst, path: Array, value: any) { - setIn(inst.state, path, value); - inst.forceUpdate(); -} - -function setInContext(inst, path: Array, value: any) { - setIn(inst.context, path, value); - inst.forceUpdate(); -} - -function setIn(obj: Object, path: Array, value: any) { - var last = path.pop(); - var parent = path.reduce((obj_, attr) => obj_ ? obj_[attr] : null, obj); - if (parent) { - parent[last] = value; - } -} - -module.exports = getDataFiber; diff --git a/package.json b/package.json index 5fd3e916fe..9b74be359f 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "react-portal": "^3.1.0", "react-virtualized-auto-sizer": "^1.0.2", "react-window": "^1.1.1", + "semver": "^5.5.1", "webpack": "1.12.9" }, "license": "BSD-3-Clause", diff --git a/yarn.lock b/yarn.lock index 12e7358542..c591900478 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6186,6 +6186,10 @@ semver@^5.0.1, semver@^5.4.1: version "5.5.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" +semver@^5.5.1: + version "5.5.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.1.tgz#7dfdd8814bdb7cabc7be0fb1d734cfb66c940477" + set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"