From c22b94f14a809abb376f07a53f36860a7c6a342e Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Wed, 24 May 2017 17:06:30 +0100 Subject: [PATCH] ReactNative flat renderer bundles (#9626) * Split ReactNativeFiber into separate ReactNativeFiberRenderer module Hopefully this is sufficient to work around Rollup circular dependency problems. (To be seen in subsequent commits...) * Split findNodeHandle into findNodeHandleFiber + findNodeHandleStack This allowed me to remove the ReactNative -> findNodeHandle injections, which should in turn allow me to require a fully-functional findNodeHandle without going through ReactNative. This will hopefully allow ReactNativeBaseomponent to avoid a circular dependency. * Un-forked findNodeHandle in favor of just inlining the findNode function impl * takeSnapshot no longer requires/depends-on ReactNative for findNodeHandle Instead it uses the new, renderer-specific wrappers (eg findNodeHandleFiberWrapper and findNodeHandleStackWrapper) to ensure the returned value is numeric (or null). This avoids a circular dependency that would trip up Rollup. * NativeMethodsMixin requires findNodeHandler wrapper(s) directly rather than ReactNative This works around a potential circular dependency that would break the Rollup build * Add RN_* build targets to hash-finle-name check * Strip @providesModule annotations from headers for RN_* builds * Added process.env.REACT_NATIVE_USE_FIBER to ReactNativeFeatureFlags This is kind of a hacky solution, but it is temporary. It works around the fact that ReactNativeFeatureFlag values need to be set at build time in order to avoid a mismatch between runtime flag values. DOM avoids the need to do this by using injection but Native is not able to use this same approach due to circular dependency issues. * Moved a couple of SECRET exports to dev-only. Removed SyntheticEvent and PooledClass from SECRET exports. Converted Rollup helper function to use named params. * Split NativeMethodsMixins interface and object-type * Add @noflow header to flat-bundle template to avoid triggering Flow problems When Flow tries to infer such a large file, it consumes massive amounts of CPU/RAM and can often lead to programs crashing. It is better for such large files to use .flow.js types instead. * NativeMethodsMixin and ReactNativeFiberHostComponent now share the same Flow type * Collocated (externally exposed) ReactTypes and ReactNativeTypes into single files to be synced to fbsource. ReactNativeFiber and ReactNativeStack use ReactNativeType Flow type * Build script syncs RN types and PooledClass automatically * Added optional sync-RN step to Rollup build script * Added results.json for new RN bundles --- scripts/rollup/build.js | 97 +++- scripts/rollup/bundles.js | 16 +- scripts/rollup/header.js | 1 + scripts/rollup/packaging.js | 31 +- scripts/rollup/results.json | 16 + .../shims/react-native/NativeMethodsMixin.js | 7 +- ...oledClass.js => ReactGlobalSharedState.js} | 5 +- .../rollup/shims/react-native/ReactNative.js | 30 ++ .../react-native/ReactNativeFeatureFlags.js | 19 + .../shims/react-native/findNodeHandle.js | 30 -- .../{ReactErrorUtils.js => takeSnapshot.js} | 4 +- scripts/rollup/sync.js | 37 ++ scripts/rollup/utils.js | 30 ++ src/renderers/native/NativeMethodsMixin.js | 41 +- .../native/NativeMethodsMixinUtils.js | 40 -- .../native/ReactNativeFeatureFlags.js | 4 +- src/renderers/native/ReactNativeFiber.js | 420 ++---------------- .../native/ReactNativeFiberHostComponent.js | 11 +- .../native/ReactNativeFiberRenderer.js | 380 ++++++++++++++++ src/renderers/native/ReactNativeStack.js | 41 +- .../native/ReactNativeStackInjection.js | 4 - src/renderers/native/ReactNativeTypes.js | 89 ++++ src/renderers/native/findNodeHandle.js | 24 +- .../native/findNumericNodeHandleFiber.js | 29 ++ .../native/findNumericNodeHandleStack.js | 29 ++ src/renderers/native/takeSnapshot.js | 10 +- src/renderers/shared/fiber/ReactChildFiber.js | 3 +- src/renderers/shared/fiber/ReactFiber.js | 9 +- .../shared/fiber/ReactFiberBeginWork.js | 2 +- .../shared/fiber/ReactFiberCompleteWork.js | 2 +- .../shared/fiber/isomorphic/ReactCoroutine.js | 15 +- .../shared/fiber/isomorphic/ReactPortal.js | 11 +- .../shared/fiber/isomorphic/ReactTypes.js | 26 +- 33 files changed, 924 insertions(+), 589 deletions(-) rename scripts/rollup/shims/react-native/{PooledClass.js => ReactGlobalSharedState.js} (75%) create mode 100644 scripts/rollup/shims/react-native/ReactNative.js create mode 100644 scripts/rollup/shims/react-native/ReactNativeFeatureFlags.js delete mode 100644 scripts/rollup/shims/react-native/findNodeHandle.js rename scripts/rollup/shims/react-native/{ReactErrorUtils.js => takeSnapshot.js} (80%) create mode 100644 scripts/rollup/sync.js create mode 100644 scripts/rollup/utils.js create mode 100644 src/renderers/native/ReactNativeFiberRenderer.js create mode 100644 src/renderers/native/ReactNativeTypes.js create mode 100644 src/renderers/native/findNumericNodeHandleFiber.js create mode 100644 src/renderers/native/findNumericNodeHandleStack.js diff --git a/scripts/rollup/build.js b/scripts/rollup/build.js index 47ff469529..e77d1b79d7 100644 --- a/scripts/rollup/build.js +++ b/scripts/rollup/build.js @@ -18,6 +18,7 @@ const Bundles = require('./bundles'); const propertyMangleWhitelist = require('./mangle').propertyMangleWhitelist; const sizes = require('./plugins/sizes-plugin'); const Stats = require('./stats'); +const syncReactNative = require('./sync').syncReactNative; const Packaging = require('./packaging'); const Header = require('./header'); @@ -37,6 +38,7 @@ const requestedBundleTypes = (argv.type || '') const requestedBundleNames = (argv._[0] || '') .split(',') .map(type => type.toLowerCase()); +const syncFbsource = argv['sync-fbsource']; // used for when we property mangle with uglify/gcc const mangleRegex = new RegExp( @@ -46,6 +48,32 @@ const mangleRegex = new RegExp( 'g' ); +function getHeaderSanityCheck(bundleType, hasteName) { + switch (bundleType) { + case FB_DEV: + case FB_PROD: + case RN_DEV: + case RN_PROD: + let hasteFinalName = hasteName; + switch (bundleType) { + case FB_DEV: + case RN_DEV: + hasteFinalName += '-dev'; + break; + case FB_PROD: + case RN_PROD: + hasteFinalName += '-prod'; + break; + } + return hasteFinalName; + case UMD_DEV: + case UMD_PROD: + return reactVersion; + default: + return null; + } +} + function getBanner(bundleType, hasteName, filename) { switch (bundleType) { case FB_DEV: @@ -55,9 +83,11 @@ function getBanner(bundleType, hasteName, filename) { let hasteFinalName = hasteName; switch (bundleType) { case FB_DEV: + case RN_DEV: hasteFinalName += '-dev'; break; case FB_PROD: + case RN_PROD: hasteFinalName += '-prod'; break; } @@ -122,6 +152,12 @@ function updateBundleConfig(config, filename, format, bundleType, hasteName) { }); } +function setReactNativeUseFiberEnvVariable(useFiber) { + return { + 'process.env.REACT_NATIVE_USE_FIBER': useFiber, + }; +} + function stripEnvVariables(production) { return { __DEV__: production ? 'false' : 'true', @@ -165,7 +201,13 @@ function getFilename(name, hasteName, bundleType) { } } -function uglifyConfig(mangle, manglePropertiesOnProd, preserveVersionHeader) { +function uglifyConfig({ + mangle, + manglePropertiesOnProd, + preserveVersionHeader, + removeComments, + headerSanityCheck, +}) { return { warnings: false, compress: { @@ -186,7 +228,10 @@ function uglifyConfig(mangle, manglePropertiesOnProd, preserveVersionHeader) { comments(node, comment) { if (preserveVersionHeader && comment.pos === 0 && comment.col === 0) { // Keep the very first comment (the bundle header) in prod bundles. - if (comment.value.indexOf(reactVersion) === -1) { + if ( + headerSanityCheck && + comment.value.indexOf(headerSanityCheck) === -1 + ) { // Sanity check: this doesn't look like the bundle header! throw new Error( 'Expected the first comment to be the file header but got: ' + @@ -195,8 +240,7 @@ function uglifyConfig(mangle, manglePropertiesOnProd, preserveVersionHeader) { } return true; } - // Keep all comments in FB bundles. - return !mangle; + return !removeComments; }, }, mangleProperties: mangle && manglePropertiesOnProd @@ -242,8 +286,10 @@ function getPlugins( paths, filename, bundleType, + hasteName, isRenderer, - manglePropertiesOnProd + manglePropertiesOnProd, + useFiber ) { const plugins = [ babel(updateBabelConfig(babelOpts, bundleType)), @@ -259,11 +305,12 @@ function getPlugins( plugins.unshift(replace(replaceModules)); } + const headerSanityCheck = getHeaderSanityCheck(bundleType, hasteName); + switch (bundleType) { case UMD_DEV: case NODE_DEV: case FB_DEV: - case RN_DEV: plugins.push( replace(stripEnvVariables(false)), // needs to happen after strip env @@ -273,17 +320,36 @@ function getPlugins( case UMD_PROD: case NODE_PROD: case FB_PROD: - case RN_PROD: plugins.push( replace(stripEnvVariables(true)), // needs to happen after strip env commonjs(getCommonJsConfig(bundleType)), uglify( - uglifyConfig( - bundleType !== FB_PROD, + uglifyConfig({ + mangle: bundleType !== FB_PROD, manglePropertiesOnProd, - bundleType === UMD_PROD - ) + preserveVersionHeader: bundleType === UMD_PROD, + removeComments: bundleType === FB_PROD, + headerSanityCheck, + }) + ) + ); + break; + case RN_DEV: + case RN_PROD: + plugins.push( + replace(stripEnvVariables(bundleType === RN_PROD)), + replace(setReactNativeUseFiberEnvVariable(useFiber)), + // needs to happen after strip env + commonjs(getCommonJsConfig(bundleType)), + uglify( + uglifyConfig({ + mangle: false, + manglePropertiesOnProd, + preserveVersionHeader: true, + removeComments: true, + headerSanityCheck, + }) ) ); break; @@ -349,8 +415,10 @@ function createBundle(bundle, bundleType) { bundle.paths, filename, bundleType, + bundle.hasteName, bundle.isRenderer, - bundle.manglePropertiesOnProd + bundle.manglePropertiesOnProd, + bundle.useFiber ), }) .then(result => @@ -406,6 +474,11 @@ rimraf('build', () => { () => createBundle(bundle, RN_PROD) ); } + if (syncFbsource) { + tasks.push(() => + syncReactNative(join('build', 'react-native'), syncFbsource) + ); + } // rather than run concurently, opt to run them serially // this helps improve console/warning/error output // and fixes a bunch of IO failures that sometimes occured diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index 1278e2f9d9..fb7e04b39c 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -19,8 +19,8 @@ const NODE_DEV = bundleTypes.NODE_DEV; const NODE_PROD = bundleTypes.NODE_PROD; const FB_DEV = bundleTypes.FB_DEV; const FB_PROD = bundleTypes.FB_PROD; -// const RN_DEV = bundleTypes.RN_DEV; -// const RN_PROD = bundleTypes.RN_PROD; +const RN_DEV = bundleTypes.RN_DEV; +const RN_PROD = bundleTypes.RN_PROD; const babelOptsReact = { exclude: 'node_modules/**', @@ -284,9 +284,7 @@ const bundles = [ /******* React Native *******/ { babelOpts: babelOptsReact, - bundleTypes: [ - /* RN_DEV, RN_PROD */ - ], + bundleTypes: [RN_DEV, RN_PROD], config: { destDir: 'build/', moduleName: 'ReactNativeStack', @@ -304,6 +302,7 @@ const bundles = [ 'deepDiffer', 'deepFreezeAndThrowOnMutationInDev', 'flattenStyle', + 'prop-types/checkPropTypes', ], hasteName: 'ReactNativeStack', isRenderer: true, @@ -317,12 +316,11 @@ const bundles = [ 'src/ReactVersion.js', 'src/shared/**/*.js', ], + useFiber: false, }, { babelOpts: babelOptsReact, - bundleTypes: [ - /* RN_DEV, RN_PROD */ - ], + bundleTypes: [RN_DEV, RN_PROD], config: { destDir: 'build/', moduleName: 'ReactNativeFiber', @@ -340,6 +338,7 @@ const bundles = [ 'deepDiffer', 'deepFreezeAndThrowOnMutationInDev', 'flattenStyle', + 'prop-types/checkPropTypes', ], hasteName: 'ReactNativeFiber', isRenderer: true, @@ -353,6 +352,7 @@ const bundles = [ 'src/ReactVersion.js', 'src/shared/**/*.js', ], + useFiber: true, }, /******* React Test Renderer *******/ diff --git a/scripts/rollup/header.js b/scripts/rollup/header.js index 182c5d3869..40ea03d640 100644 --- a/scripts/rollup/header.js +++ b/scripts/rollup/header.js @@ -11,6 +11,7 @@ function getProvidesHeader(hasteFinalName, bundleType, fbDevCode) { * 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. * + * @noflow * @providesModule ${hasteFinalName} */${bundleType === FB_DEV ? fbDevCode : ''} `; diff --git a/scripts/rollup/packaging.js b/scripts/rollup/packaging.js index d8510607aa..d2d842ecbb 100644 --- a/scripts/rollup/packaging.js +++ b/scripts/rollup/packaging.js @@ -1,11 +1,11 @@ 'use strict'; const basename = require('path').basename; -const ncp = require('ncp').ncp; const fs = require('fs'); const join = require('path').join; const resolve = require('path').resolve; const Bundles = require('./bundles'); +const asyncCopyTo = require('./utils').asyncCopyTo; const UMD_DEV = Bundles.bundleTypes.UMD_DEV; const UMD_PROD = Bundles.bundleTypes.UMD_PROD; @@ -23,17 +23,12 @@ const facebookWWWSrcDependencies = [ 'src/renderers/dom/shared/eventPlugins/TapEventPlugin.js', ]; -function asyncCopyTo(from, to) { - return new Promise(_resolve => { - ncp(from, to, error => { - if (error) { - console.error(error); - process.exit(1); - } - _resolve(); - }); - }); -} +// these files need to be copied to the react-native build +const reactNativeSrcDependencies = [ + 'src/shared/utils/PooledClass.js', + 'src/renderers/shared/fiber/isomorphic/ReactTypes.js', + 'src/renderers/native/ReactNativeTypes.js', +]; function getPackageName(name) { if (name.indexOf('/') !== -1) { @@ -51,7 +46,17 @@ function createReactNativeBuild() { const from = join('scripts', 'rollup', 'shims', 'react-native'); const to = join('build', 'react-native', 'shims'); - return asyncCopyTo(from, to); + return asyncCopyTo(from, to).then(() => { + let promises = []; + // we also need to copy over some specific files from src + // defined in reactNativeSrcDependencies + for (const srcDependency of reactNativeSrcDependencies) { + promises.push( + asyncCopyTo(resolve(srcDependency), join(to, basename(srcDependency))) + ); + } + return Promise.all(promises); + }); } function createFacebookWWWBuild() { diff --git a/scripts/rollup/results.json b/scripts/rollup/results.json index 90348bfd06..5d806f5ffc 100644 --- a/scripts/rollup/results.json +++ b/scripts/rollup/results.json @@ -163,6 +163,22 @@ "ReactDOMServerStream-prod.js (FB_PROD)": { "size": 345920, "gzip": 83497 + }, + "ReactNativeStack-dev.js (RN_DEV)": { + "size": 350743, + "gzip": 63769 + }, + "ReactNativeStack-prod.js (RN_PROD)": { + "size": 269602, + "gzip": 46634 + }, + "ReactNativeFiber-dev.js (RN_DEV)": { + "size": 327677, + "gzip": 59441 + }, + "ReactNativeFiber-prod.js (RN_PROD)": { + "size": 248866, + "gzip": 43107 } } } \ No newline at end of file diff --git a/scripts/rollup/shims/react-native/NativeMethodsMixin.js b/scripts/rollup/shims/react-native/NativeMethodsMixin.js index dd17a59b61..085c653bfe 100644 --- a/scripts/rollup/shims/react-native/NativeMethodsMixin.js +++ b/scripts/rollup/shims/react-native/NativeMethodsMixin.js @@ -16,5 +16,8 @@ const { __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED, } = require('ReactNative'); -module.exports = - __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.NativeMethodsMixin; +import type {NativeMethodsMixinType} from 'ReactNativeTypes'; + +const {NativeMethodsMixin} = __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED; + +module.exports = ((NativeMethodsMixin: any): $Exact); diff --git a/scripts/rollup/shims/react-native/PooledClass.js b/scripts/rollup/shims/react-native/ReactGlobalSharedState.js similarity index 75% rename from scripts/rollup/shims/react-native/PooledClass.js rename to scripts/rollup/shims/react-native/ReactGlobalSharedState.js index 269ce9cd36..a2b5840201 100644 --- a/scripts/rollup/shims/react-native/PooledClass.js +++ b/scripts/rollup/shims/react-native/ReactGlobalSharedState.js @@ -6,7 +6,7 @@ * 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 PooledClass + * @providesModule ReactGlobalSharedState */ 'use strict'; @@ -15,4 +15,5 @@ const { __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED, } = require('ReactNative'); -module.exports = __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.PooledClass; +module.exports = + __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactGlobalSharedState; diff --git a/scripts/rollup/shims/react-native/ReactNative.js b/scripts/rollup/shims/react-native/ReactNative.js new file mode 100644 index 0000000000..ed27f10253 --- /dev/null +++ b/scripts/rollup/shims/react-native/ReactNative.js @@ -0,0 +1,30 @@ +/** + * 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. + * + * @providesModule ReactNative + * @flow + */ +'use strict'; + +const ReactNativeFeatureFlags = require('ReactNativeFeatureFlags'); + +import type {ReactNativeType} from 'ReactNativeTypes'; + +let ReactNative; + +if (__DEV__) { + ReactNative = ReactNativeFeatureFlags.useFiber + ? require('ReactNativeFiber-dev') + : require('ReactNativeStack-dev'); +} else { + ReactNative = ReactNativeFeatureFlags.useFiber + ? require('ReactNativeFiber-prod') + : require('ReactNativeStack-prod'); +} + +module.exports = (ReactNative: ReactNativeType); diff --git a/scripts/rollup/shims/react-native/ReactNativeFeatureFlags.js b/scripts/rollup/shims/react-native/ReactNativeFeatureFlags.js new file mode 100644 index 0000000000..1224b86f7b --- /dev/null +++ b/scripts/rollup/shims/react-native/ReactNativeFeatureFlags.js @@ -0,0 +1,19 @@ +/** + * 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 ReactNativeFeatureFlags + * @flow + */ + +'use strict'; + +var ReactNativeFeatureFlags = { + useFiber: false, +}; + +module.exports = ReactNativeFeatureFlags; diff --git a/scripts/rollup/shims/react-native/findNodeHandle.js b/scripts/rollup/shims/react-native/findNodeHandle.js deleted file mode 100644 index d17da1cc19..0000000000 --- a/scripts/rollup/shims/react-native/findNodeHandle.js +++ /dev/null @@ -1,30 +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 findNodeHandle - * @flow - */ - -'use strict'; - -// While ReactNative renderer bundle is initializing, some -// code (e.g. UIManager) imports from ReactNative. - -// We use an indirection to avoid a circular dependency. - -let realFindNodeHandle = null; - -function findNodeHandle(componentOrHandle: any): ?number { - if (realFindNodeHandle === null) { - realFindNodeHandle = require('ReactNative') - .__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.findNodeHandle; - } - return realFindNodeHandle(componentOrHandle); -} - -module.exports = findNodeHandle; diff --git a/scripts/rollup/shims/react-native/ReactErrorUtils.js b/scripts/rollup/shims/react-native/takeSnapshot.js similarity index 80% rename from scripts/rollup/shims/react-native/ReactErrorUtils.js rename to scripts/rollup/shims/react-native/takeSnapshot.js index 0a424191e1..e594b97837 100644 --- a/scripts/rollup/shims/react-native/ReactErrorUtils.js +++ b/scripts/rollup/shims/react-native/takeSnapshot.js @@ -6,7 +6,7 @@ * 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 ReactErrorUtils + * @providesModule takeSnapshot */ 'use strict'; @@ -16,4 +16,4 @@ const { } = require('ReactNative'); module.exports = - __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactErrorUtils; + __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.takeSnapshot; diff --git a/scripts/rollup/sync.js b/scripts/rollup/sync.js new file mode 100644 index 0000000000..d5563c8249 --- /dev/null +++ b/scripts/rollup/sync.js @@ -0,0 +1,37 @@ +'use strict'; + +const asyncCopyTo = require('./utils').asyncCopyTo; +const chalk = require('chalk'); +const resolvePath = require('./utils').resolvePath; + +const DEFAULT_FB_SOURCE_PATH = '~/fbsource/'; +const RELATIVE_RN_PATH = 'xplat/js/react-native-github/Libraries/Renderer/'; + +function syncReactNative(buildPath, fbSourcePath) { + fbSourcePath = typeof fbSourcePath === 'string' + ? fbSourcePath + : DEFAULT_FB_SOURCE_PATH; + + if (fbSourcePath.charAt(fbSourcePath.length - 1) !== '/') { + fbSourcePath += '/'; + } + + const destPath = resolvePath(fbSourcePath + RELATIVE_RN_PATH); + + console.log( + `${chalk.bgYellow.black(' SYNCING ')} ReactNative to ${destPath}` + ); + + const promise = asyncCopyTo(buildPath, destPath); + promise.then(() => { + console.log( + `${chalk.bgGreen.black(' SYNCED ')} ReactNative to ${destPath}` + ); + }); + + return promise; +} + +module.exports = { + syncReactNative: syncReactNative, +}; diff --git a/scripts/rollup/utils.js b/scripts/rollup/utils.js new file mode 100644 index 0000000000..40e85921c8 --- /dev/null +++ b/scripts/rollup/utils.js @@ -0,0 +1,30 @@ +'use strict'; + +const ncp = require('ncp').ncp; +const join = require('path').join; +const resolve = require('path').resolve; + +function asyncCopyTo(from, to) { + return new Promise(_resolve => { + ncp(from, to, error => { + if (error) { + console.error(error); + process.exit(1); + } + _resolve(); + }); + }); +} + +function resolvePath(path) { + if (path[0] === '~') { + return join(process.env.HOME, path.slice(1)); + } else { + return resolve(path); + } +} + +module.exports = { + asyncCopyTo: asyncCopyTo, + resolvePath: resolvePath, +}; diff --git a/src/renderers/native/NativeMethodsMixin.js b/src/renderers/native/NativeMethodsMixin.js index 44b5dcc238..5f2fd32cb9 100644 --- a/src/renderers/native/NativeMethodsMixin.js +++ b/src/renderers/native/NativeMethodsMixin.js @@ -11,7 +11,6 @@ */ 'use strict'; -var ReactNative = require('ReactNative'); var ReactNativeFeatureFlags = require('ReactNativeFeatureFlags'); var ReactNativeAttributePayload = require('ReactNativeAttributePayload'); var TextInputState = require('TextInputState'); @@ -30,11 +29,16 @@ import type { MeasureInWindowOnSuccessCallback, MeasureLayoutOnSuccessCallback, MeasureOnSuccessCallback, -} from 'NativeMethodsMixinUtils'; + NativeMethodsMixinType, +} from 'ReactNativeTypes'; import type { ReactNativeBaseComponentViewConfig, } from 'ReactNativeViewConfigRegistry'; +const findNumericNodeHandle = ReactNativeFeatureFlags.useFiber + ? require('findNumericNodeHandleFiber') + : require('findNumericNodeHandleStack'); + /** * `NativeMethodsMixin` provides methods to access the underlying native * component directly. This can be useful in cases when you want to focus @@ -46,12 +50,11 @@ import type { * generally include most components that you define in your own app. For more * information, see [Direct * Manipulation](docs/direct-manipulation.html). + * + * Note the Flow $Exact<> syntax is required to support mixins. + * React createClass mixins can only be used with exact types. */ -// TODO (bvaughn) Figure out how to use the NativeMethodsInterface type to- -// ensure that these mixins and ReactNativeFiberHostComponent stay in sync. -// Unfortunately, using it causes Flow to complain WRT createClass mixins: -// "call of method `createClass`. Expected an exact object instead of ..." -var NativeMethodsMixin = { +var NativeMethodsMixin: $Exact = { /** * Determines the location on screen, width, and height of the given view and * returns the values via an async callback. If successful, the callback will @@ -71,7 +74,7 @@ var NativeMethodsMixin = { */ measure: function(callback: MeasureOnSuccessCallback) { UIManager.measure( - ReactNative.findNodeHandle(this), + findNumericNodeHandle(this), mountSafeCallback(this, callback), ); }, @@ -93,7 +96,7 @@ var NativeMethodsMixin = { */ measureInWindow: function(callback: MeasureInWindowOnSuccessCallback) { UIManager.measureInWindow( - ReactNative.findNodeHandle(this), + findNumericNodeHandle(this), mountSafeCallback(this, callback), ); }, @@ -104,7 +107,7 @@ var NativeMethodsMixin = { * are relative to the origin x, y of the ancestor view. * * As always, to obtain a native node handle for a component, you can use - * `ReactNative.findNodeHandle(component)`. + * `findNumericNodeHandle(component)`. */ measureLayout: function( relativeToNativeNode: number, @@ -112,7 +115,7 @@ var NativeMethodsMixin = { onFail: () => void /* currently unused */, ) { UIManager.measureLayout( - ReactNative.findNodeHandle(this), + findNumericNodeHandle(this), relativeToNativeNode, mountSafeCallback(this, onFail), mountSafeCallback(this, onSuccess), @@ -126,14 +129,6 @@ var NativeMethodsMixin = { * Manipulation](docs/direct-manipulation.html)). */ setNativeProps: function(nativeProps: Object) { - // Ensure ReactNative factory function has configured findNodeHandle. - // Requiring it won't execute the factory function until first referenced. - // It's possible for tests that use ReactTestRenderer to reach this point, - // Without having executed ReactNative. - // Defer the factory function until now to avoid a cycle with UIManager. - // TODO (bvaughn) Remove this once ReactNativeStack is dropped. - require('ReactNative'); - injectedSetNativeProps(this, nativeProps); }, @@ -142,14 +137,14 @@ var NativeMethodsMixin = { * will depend on the platform and type of view. */ focus: function() { - TextInputState.focusTextInput(ReactNative.findNodeHandle(this)); + TextInputState.focusTextInput(findNumericNodeHandle(this)); }, /** * Removes focus from an input or view. This is the opposite of `focus()`. */ blur: function() { - TextInputState.blurTextInput(ReactNative.findNodeHandle(this)); + TextInputState.blurTextInput(findNumericNodeHandle(this)); }, }; @@ -158,7 +153,7 @@ function setNativePropsFiber(componentOrHandle: any, nativeProps: Object) { // Class components don't have viewConfig -> validateAttributes. // Nor does it make sense to set native props on a non-native component. // Instead, find the nearest host component and set props on it. - // Use findNodeHandle() rather than ReactNative.findNodeHandle() because + // Use findNodeHandle() rather than findNumericNodeHandle() because // We want the instance/wrapper (not the native tag). let maybeInstance; @@ -200,7 +195,7 @@ function setNativePropsStack(componentOrHandle: any, nativeProps: Object) { // Class components don't have viewConfig -> validateAttributes. // Nor does it make sense to set native props on a non-native component. // Instead, find the nearest host component and set props on it. - // Use findNodeHandle() rather than ReactNative.findNodeHandle() because + // Use findNodeHandle() rather than findNumericNodeHandle() because // We want the instance/wrapper (not the native tag). let maybeInstance = findNodeHandle(componentOrHandle); diff --git a/src/renderers/native/NativeMethodsMixinUtils.js b/src/renderers/native/NativeMethodsMixinUtils.js index 81a0c55cae..dad8a56dfc 100644 --- a/src/renderers/native/NativeMethodsMixinUtils.js +++ b/src/renderers/native/NativeMethodsMixinUtils.js @@ -11,46 +11,6 @@ */ 'use strict'; -export type MeasureOnSuccessCallback = ( - x: number, - y: number, - width: number, - height: number, - pageX: number, - pageY: number, -) => void; - -export type MeasureInWindowOnSuccessCallback = ( - x: number, - y: number, - width: number, - height: number, -) => void; - -export type MeasureLayoutOnSuccessCallback = ( - left: number, - top: number, - width: number, - height: number, -) => void; - -/** - * Shared between ReactNativeFiberHostComponent and NativeMethodsMixin to keep - * API in sync. - */ -export interface NativeMethodsInterface { - blur(): void, - focus(): void, - measure(callback: MeasureOnSuccessCallback): void, - measureInWindow(callback: MeasureInWindowOnSuccessCallback): void, - measureLayout( - relativeToNativeNode: number, - onSuccess: MeasureLayoutOnSuccessCallback, - onFail: () => void, - ): void, - setNativeProps(nativeProps: Object): void, -} - /** * In the future, we should cleanup callbacks by cancelling them instead of * using this. diff --git a/src/renderers/native/ReactNativeFeatureFlags.js b/src/renderers/native/ReactNativeFeatureFlags.js index 1224b86f7b..4c48e7f6f1 100644 --- a/src/renderers/native/ReactNativeFeatureFlags.js +++ b/src/renderers/native/ReactNativeFeatureFlags.js @@ -12,8 +12,10 @@ 'use strict'; +// Read from process.env in order to support Rollup flat bundles. +// Jest test script will also write this value for Fiber tests. var ReactNativeFeatureFlags = { - useFiber: false, + useFiber: process.env.REACT_NATIVE_USE_FIBER, }; module.exports = ReactNativeFeatureFlags; diff --git a/src/renderers/native/ReactNativeFiber.js b/src/renderers/native/ReactNativeFiber.js index b8e59bfe9f..ab301ecdc4 100644 --- a/src/renderers/native/ReactNativeFiber.js +++ b/src/renderers/native/ReactNativeFiber.js @@ -13,412 +13,40 @@ 'use strict'; const ReactFiberErrorLogger = require('ReactFiberErrorLogger'); -const ReactFiberReconciler = require('ReactFiberReconciler'); const ReactGenericBatching = require('ReactGenericBatching'); -const ReactNativeAttributePayload = require('ReactNativeAttributePayload'); -const ReactNativeComponentTree = require('ReactNativeComponentTree'); const ReactNativeFiberErrorDialog = require('ReactNativeFiberErrorDialog'); -const ReactNativeFiberHostComponent = require('ReactNativeFiberHostComponent'); const ReactNativeInjection = require('ReactNativeInjection'); -const ReactNativeTagHandles = require('ReactNativeTagHandles'); -const ReactNativeViewConfigRegistry = require('ReactNativeViewConfigRegistry'); const ReactPortal = require('ReactPortal'); +const ReactNativeComponentTree = require('ReactNativeComponentTree'); +const ReactNativeFiberRenderer = require('ReactNativeFiberRenderer'); const ReactNativeFiberInspector = require('ReactNativeFiberInspector'); const ReactVersion = require('ReactVersion'); const UIManager = require('UIManager'); -const deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev'); -const emptyObject = require('fbjs/lib/emptyObject'); -const findNodeHandle = require('findNodeHandle'); -const invariant = require('fbjs/lib/invariant'); +const findNumericNodeHandle = require('findNumericNodeHandleFiber'); const {injectInternals} = require('ReactFiberDevToolsHook'); import type {Element} from 'React'; -import type {Fiber} from 'ReactFiber'; -import type { - ReactNativeBaseComponentViewConfig, -} from 'ReactNativeViewConfigRegistry'; +import type {ReactNativeType} from 'ReactNativeTypes'; import type {ReactNodeList} from 'ReactTypes'; -const { - precacheFiberNode, - uncacheFiberNode, - updateFiberProps, -} = ReactNativeComponentTree; ReactNativeInjection.inject(); -type Container = number; -export type Instance = { - _children: Array, - _nativeTag: number, - viewConfig: ReactNativeBaseComponentViewConfig, -}; -type Props = Object; -type TextInstance = number; - -function recursivelyUncacheFiberNode(node: Instance | TextInstance) { - if (typeof node === 'number') { - // Leaf node (eg text) - uncacheFiberNode(node); - } else { - uncacheFiberNode((node: any)._nativeTag); - - (node: any)._children.forEach(recursivelyUncacheFiberNode); - } -} - -const NativeRenderer = ReactFiberReconciler({ - appendChild( - parentInstance: Instance | Container, - child: Instance | TextInstance, - ): void { - const childTag = typeof child === 'number' ? child : child._nativeTag; - - if (typeof parentInstance === 'number') { - // Root container - UIManager.setChildren( - parentInstance, // containerTag - [childTag], // reactTags - ); - } else { - const children = parentInstance._children; - - const index = children.indexOf(child); - - if (index >= 0) { - children.splice(index, 1); - children.push(child); - - UIManager.manageChildren( - parentInstance._nativeTag, // containerTag - [index], // moveFromIndices - [children.length - 1], // moveToIndices - [], // addChildReactTags - [], // addAtIndices - [], // removeAtIndices - ); - } else { - children.push(child); - - UIManager.manageChildren( - parentInstance._nativeTag, // containerTag - [], // moveFromIndices - [], // moveToIndices - [childTag], // addChildReactTags - [children.length - 1], // addAtIndices - [], // removeAtIndices - ); - } - } - }, - - appendInitialChild( - parentInstance: Instance, - child: Instance | TextInstance, - ): void { - parentInstance._children.push(child); - }, - - commitTextUpdate( - textInstance: TextInstance, - oldText: string, - newText: string, - ): void { - UIManager.updateView( - textInstance, // reactTag - 'RCTRawText', // viewName - {text: newText}, // props - ); - }, - - commitMount( - instance: Instance, - type: string, - newProps: Props, - internalInstanceHandle: Object, - ): void { - // Noop - }, - - commitUpdate( - instance: Instance, - updatePayloadTODO: Object, - type: string, - oldProps: Props, - newProps: Props, - internalInstanceHandle: Object, - ): void { - const viewConfig = instance.viewConfig; - - updateFiberProps(instance._nativeTag, newProps); - - const updatePayload = ReactNativeAttributePayload.diff( - oldProps, - newProps, - viewConfig.validAttributes, - ); - - UIManager.updateView( - instance._nativeTag, // reactTag - viewConfig.uiViewClassName, // viewName - updatePayload, // props - ); - }, - - createInstance( - type: string, - props: Props, - rootContainerInstance: Container, - hostContext: {}, - internalInstanceHandle: Object, - ): Instance { - const tag = ReactNativeTagHandles.allocateTag(); - const viewConfig = ReactNativeViewConfigRegistry.get(type); - - if (__DEV__) { - for (const key in viewConfig.validAttributes) { - if (props.hasOwnProperty(key)) { - deepFreezeAndThrowOnMutationInDev(props[key]); - } - } - } - - const updatePayload = ReactNativeAttributePayload.create( - props, - viewConfig.validAttributes, - ); - - UIManager.createView( - tag, // reactTag - viewConfig.uiViewClassName, // viewName - rootContainerInstance, // rootTag - updatePayload, // props - ); - - const component = new ReactNativeFiberHostComponent(tag, viewConfig); - - precacheFiberNode(internalInstanceHandle, tag); - updateFiberProps(tag, props); - - // Not sure how to avoid this cast. Flow is okay if the component is defined - // in the same file but if it's external it can't see the types. - return ((component: any): Instance); - }, - - createTextInstance( - text: string, - rootContainerInstance: Container, - hostContext: {}, - internalInstanceHandle: Object, - ): TextInstance { - const tag = ReactNativeTagHandles.allocateTag(); - - UIManager.createView( - tag, // reactTag - 'RCTRawText', // viewName - rootContainerInstance, // rootTag - {text: text}, // props - ); - - precacheFiberNode(internalInstanceHandle, tag); - - return tag; - }, - - finalizeInitialChildren( - parentInstance: Instance, - type: string, - props: Props, - rootContainerInstance: Container, - ): boolean { - // Don't send a no-op message over the bridge. - if (parentInstance._children.length === 0) { - return false; - } - - // Map from child objects to native tags. - // Either way we need to pass a copy of the Array to prevent it from being frozen. - const nativeTags = parentInstance._children.map( - child => - (typeof child === 'number' - ? child // Leaf node (eg text) - : child._nativeTag), - ); - - UIManager.setChildren( - parentInstance._nativeTag, // containerTag - nativeTags, // reactTags - ); - - return false; - }, - - getRootHostContext(): {} { - return emptyObject; - }, - - getChildHostContext(): {} { - return emptyObject; - }, - - getPublicInstance(instance) { - return instance; - }, - - insertBefore( - parentInstance: Instance | Container, - child: Instance | TextInstance, - beforeChild: Instance | TextInstance, - ): void { - // TODO (bvaughn): Remove this check when... - // We create a wrapper object for the container in ReactNative render() - // Or we refactor to remove wrapper objects entirely. - // For more info on pros/cons see PR #8560 description. - invariant( - typeof parentInstance !== 'number', - 'Container does not support insertBefore operation', - ); - - const children = (parentInstance: any)._children; - - const index = children.indexOf(child); - - // Move existing child or add new child? - if (index >= 0) { - children.splice(index, 1); - const beforeChildIndex = children.indexOf(beforeChild); - children.splice(beforeChildIndex, 0, child); - - UIManager.manageChildren( - (parentInstance: any)._nativeTag, // containerID - [index], // moveFromIndices - [beforeChildIndex], // moveToIndices - [], // addChildReactTags - [], // addAtIndices - [], // removeAtIndices - ); - } else { - const beforeChildIndex = children.indexOf(beforeChild); - children.splice(beforeChildIndex, 0, child); - - const childTag = typeof child === 'number' ? child : child._nativeTag; - - UIManager.manageChildren( - (parentInstance: any)._nativeTag, // containerID - [], // moveFromIndices - [], // moveToIndices - [childTag], // addChildReactTags - [beforeChildIndex], // addAtIndices - [], // removeAtIndices - ); - } - }, - - prepareForCommit(): void { - // Noop - }, - - prepareUpdate( - instance: Instance, - type: string, - oldProps: Props, - newProps: Props, - rootContainerInstance: Container, - hostContext: {}, - ): null | Object { - return emptyObject; - }, - - removeChild( - parentInstance: Instance | Container, - child: Instance | TextInstance, - ): void { - recursivelyUncacheFiberNode(child); - - if (typeof parentInstance === 'number') { - UIManager.manageChildren( - parentInstance, // containerID - [], // moveFromIndices - [], // moveToIndices - [], // addChildReactTags - [], // addAtIndices - [0], // removeAtIndices - ); - } else { - const children = parentInstance._children; - const index = children.indexOf(child); - - children.splice(index, 1); - - UIManager.manageChildren( - parentInstance._nativeTag, // containerID - [], // moveFromIndices - [], // moveToIndices - [], // addChildReactTags - [], // addAtIndices - [index], // removeAtIndices - ); - } - }, - - resetAfterCommit(): void { - // Noop - }, - - resetTextContent(instance: Instance): void { - // Noop - }, - - shouldDeprioritizeSubtree(type: string, props: Props): boolean { - return false; - }, - - scheduleAnimationCallback: global.requestAnimationFrame, - - scheduleDeferredCallback: global.requestIdleCallback, - - shouldSetTextContent(props: Props): boolean { - // TODO (bvaughn) Revisit this decision. - // Always returning false simplifies the createInstance() implementation, - // But creates an additional child Fiber for raw text children. - // No additional native views are created though. - // It's not clear to me which is better so I'm deferring for now. - // More context @ github.com/facebook/react/pull/8560#discussion_r92111303 - return false; - }, - - useSyncScheduling: true, -}); - ReactGenericBatching.injection.injectFiberBatchedUpdates( - NativeRenderer.batchedUpdates, + ReactNativeFiberRenderer.batchedUpdates, ); const roots = new Map(); -findNodeHandle.injection.injectFindNode((fiber: Fiber) => - NativeRenderer.findHostInstance(fiber), -); -findNodeHandle.injection.injectFindRootNodeID(instance => instance); - // Intercept lifecycle errors and ensure they are shown with the correct stack // trace within the native redbox component. ReactFiberErrorLogger.injection.injectDialog( ReactNativeFiberErrorDialog.showDialog, ); -const ReactNative = { - // External users of findNodeHandle() expect the host tag number return type. - // The injected findNodeHandle() strategy returns the instance wrapper though. - // See NativeMethodsMixin#setNativeProps for more info on why this is done. - findNodeHandle(componentOrHandle: any): ?number { - const instance: any = findNodeHandle(componentOrHandle); - if (instance == null || typeof instance === 'number') { - return instance; - } - return instance._nativeTag; - }, +var ReactNative: ReactNativeType = { + findNodeHandle: findNumericNodeHandle, render(element: Element, containerTag: any, callback: ?Function) { let root = roots.get(containerTag); @@ -426,19 +54,19 @@ const ReactNative = { if (!root) { // TODO (bvaughn): If we decide to keep the wrapper component, // We could create a wrapper for containerTag as well to reduce special casing. - root = NativeRenderer.createContainer(containerTag); + root = ReactNativeFiberRenderer.createContainer(containerTag); roots.set(containerTag, root); } - NativeRenderer.updateContainer(element, root, null, callback); + ReactNativeFiberRenderer.updateContainer(element, root, null, callback); - return NativeRenderer.getPublicRootInstance(root); + return ReactNativeFiberRenderer.getPublicRootInstance(root); }, unmountComponentAtNode(containerTag: number) { const root = roots.get(containerTag); if (root) { // TODO: Is it safe to reset this now or should I wait since this unmount could be deferred? - NativeRenderer.updateContainer(null, root, null, () => { + ReactNativeFiberRenderer.updateContainer(null, root, null, () => { roots.delete(containerTag); }); } @@ -460,12 +88,36 @@ const ReactNative = { }, unstable_batchedUpdates: ReactGenericBatching.batchedUpdates, + + __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: { + // Used as a mixin in many createClass-based components + NativeMethodsMixin: require('NativeMethodsMixin'), + + // Used by react-native-github/Libraries/ components + ReactGlobalSharedState: require('ReactGlobalSharedState'), // Systrace + ReactNativeComponentTree: require('ReactNativeComponentTree'), // InspectorUtils, ScrollResponder + ReactNativePropRegistry: require('ReactNativePropRegistry'), // flattenStyle, Stylesheet + TouchHistoryMath: require('TouchHistoryMath'), // PanResponder + createReactNativeComponentClass: require('createReactNativeComponentClass'), // eg Text + takeSnapshot: require('takeSnapshot'), // react-native-implementation + }, }; +if (__DEV__) { + // $FlowFixMe + Object.assign( + ReactNative.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED, + { + ReactDebugTool: require('ReactDebugTool'), // RCTRenderingPerf, Systrace + ReactPerf: require('ReactPerf'), // ReactPerfStallHandler, RCTRenderingPerf + }, + ); +} + if (typeof injectInternals === 'function') { injectInternals({ findFiberByHostInstance: ReactNativeComponentTree.getClosestInstanceFromNode, - findHostInstanceByFiber: NativeRenderer.findHostInstance, + findHostInstanceByFiber: ReactNativeFiberRenderer.findHostInstance, getInspectorDataForViewTag: ReactNativeFiberInspector.getInspectorDataForViewTag, // This is an enum because we may add more (e.g. profiler build) bundleType: __DEV__ ? 1 : 0, diff --git a/src/renderers/native/ReactNativeFiberHostComponent.js b/src/renderers/native/ReactNativeFiberHostComponent.js index 017f08b847..f6cfdce8b0 100644 --- a/src/renderers/native/ReactNativeFiberHostComponent.js +++ b/src/renderers/native/ReactNativeFiberHostComponent.js @@ -23,9 +23,9 @@ import type { MeasureInWindowOnSuccessCallback, MeasureLayoutOnSuccessCallback, MeasureOnSuccessCallback, - NativeMethodsInterface, -} from 'NativeMethodsMixinUtils'; -import type {Instance} from 'ReactNativeFiber'; + NativeMethodsMixinType, +} from 'ReactNativeTypes'; +import type {Instance} from 'ReactNativeFiberRenderer'; import type { ReactNativeBaseComponentViewConfig, } from 'ReactNativeViewConfigRegistry'; @@ -37,7 +37,7 @@ import type { * ReactNativeFiber depends on this component and NativeMethodsMixin depends on * ReactNativeFiber). */ -class ReactNativeFiberHostComponent implements NativeMethodsInterface { +class ReactNativeFiberHostComponent { _children: Array; _nativeTag: number; viewConfig: ReactNativeBaseComponentViewConfig; @@ -98,4 +98,7 @@ class ReactNativeFiberHostComponent implements NativeMethodsInterface { } } +// eslint-disable-next-line no-unused-expressions +(ReactNativeFiberHostComponent.prototype: NativeMethodsMixinType); + module.exports = ReactNativeFiberHostComponent; diff --git a/src/renderers/native/ReactNativeFiberRenderer.js b/src/renderers/native/ReactNativeFiberRenderer.js new file mode 100644 index 0000000000..991301ccb0 --- /dev/null +++ b/src/renderers/native/ReactNativeFiberRenderer.js @@ -0,0 +1,380 @@ +/** + * 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 ReactNativeFiberRenderer + * @flow + */ + +'use strict'; + +const ReactFiberReconciler = require('ReactFiberReconciler'); +const ReactNativeAttributePayload = require('ReactNativeAttributePayload'); +const ReactNativeComponentTree = require('ReactNativeComponentTree'); +const ReactNativeFiberHostComponent = require('ReactNativeFiberHostComponent'); +const ReactNativeTagHandles = require('ReactNativeTagHandles'); +const ReactNativeViewConfigRegistry = require('ReactNativeViewConfigRegistry'); +const UIManager = require('UIManager'); + +const deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev'); +const emptyObject = require('fbjs/lib/emptyObject'); +const invariant = require('fbjs/lib/invariant'); + +import type { + ReactNativeBaseComponentViewConfig, +} from 'ReactNativeViewConfigRegistry'; + +export type Container = number; +export type Instance = { + _children: Array, + _nativeTag: number, + viewConfig: ReactNativeBaseComponentViewConfig, +}; +export type Props = Object; +export type TextInstance = number; + +const { + precacheFiberNode, + uncacheFiberNode, + updateFiberProps, +} = ReactNativeComponentTree; + +function recursivelyUncacheFiberNode(node: Instance | TextInstance) { + if (typeof node === 'number') { + // Leaf node (eg text) + uncacheFiberNode(node); + } else { + uncacheFiberNode((node: any)._nativeTag); + + (node: any)._children.forEach(recursivelyUncacheFiberNode); + } +} + +const NativeRenderer = ReactFiberReconciler({ + appendChild( + parentInstance: Instance | Container, + child: Instance | TextInstance, + ): void { + const childTag = typeof child === 'number' ? child : child._nativeTag; + + if (typeof parentInstance === 'number') { + // Root container + UIManager.setChildren( + parentInstance, // containerTag + [childTag], // reactTags + ); + } else { + const children = parentInstance._children; + + const index = children.indexOf(child); + + if (index >= 0) { + children.splice(index, 1); + children.push(child); + + UIManager.manageChildren( + parentInstance._nativeTag, // containerTag + [index], // moveFromIndices + [children.length - 1], // moveToIndices + [], // addChildReactTags + [], // addAtIndices + [], // removeAtIndices + ); + } else { + children.push(child); + + UIManager.manageChildren( + parentInstance._nativeTag, // containerTag + [], // moveFromIndices + [], // moveToIndices + [childTag], // addChildReactTags + [children.length - 1], // addAtIndices + [], // removeAtIndices + ); + } + } + }, + + appendInitialChild( + parentInstance: Instance, + child: Instance | TextInstance, + ): void { + parentInstance._children.push(child); + }, + + commitTextUpdate( + textInstance: TextInstance, + oldText: string, + newText: string, + ): void { + UIManager.updateView( + textInstance, // reactTag + 'RCTRawText', // viewName + {text: newText}, // props + ); + }, + + commitMount( + instance: Instance, + type: string, + newProps: Props, + internalInstanceHandle: Object, + ): void { + // Noop + }, + + commitUpdate( + instance: Instance, + updatePayloadTODO: Object, + type: string, + oldProps: Props, + newProps: Props, + internalInstanceHandle: Object, + ): void { + const viewConfig = instance.viewConfig; + + updateFiberProps(instance._nativeTag, newProps); + + const updatePayload = ReactNativeAttributePayload.diff( + oldProps, + newProps, + viewConfig.validAttributes, + ); + + UIManager.updateView( + instance._nativeTag, // reactTag + viewConfig.uiViewClassName, // viewName + updatePayload, // props + ); + }, + + createInstance( + type: string, + props: Props, + rootContainerInstance: Container, + hostContext: {}, + internalInstanceHandle: Object, + ): Instance { + const tag = ReactNativeTagHandles.allocateTag(); + const viewConfig = ReactNativeViewConfigRegistry.get(type); + + if (__DEV__) { + for (const key in viewConfig.validAttributes) { + if (props.hasOwnProperty(key)) { + deepFreezeAndThrowOnMutationInDev(props[key]); + } + } + } + + const updatePayload = ReactNativeAttributePayload.create( + props, + viewConfig.validAttributes, + ); + + UIManager.createView( + tag, // reactTag + viewConfig.uiViewClassName, // viewName + rootContainerInstance, // rootTag + updatePayload, // props + ); + + const component = new ReactNativeFiberHostComponent(tag, viewConfig); + + precacheFiberNode(internalInstanceHandle, tag); + updateFiberProps(tag, props); + + // Not sure how to avoid this cast. Flow is okay if the component is defined + // in the same file but if it's external it can't see the types. + return ((component: any): Instance); + }, + + createTextInstance( + text: string, + rootContainerInstance: Container, + hostContext: {}, + internalInstanceHandle: Object, + ): TextInstance { + const tag = ReactNativeTagHandles.allocateTag(); + + UIManager.createView( + tag, // reactTag + 'RCTRawText', // viewName + rootContainerInstance, // rootTag + {text: text}, // props + ); + + precacheFiberNode(internalInstanceHandle, tag); + + return tag; + }, + + finalizeInitialChildren( + parentInstance: Instance, + type: string, + props: Props, + rootContainerInstance: Container, + ): boolean { + // Don't send a no-op message over the bridge. + if (parentInstance._children.length === 0) { + return false; + } + + // Map from child objects to native tags. + // Either way we need to pass a copy of the Array to prevent it from being frozen. + const nativeTags = parentInstance._children.map( + child => + (typeof child === 'number' + ? child // Leaf node (eg text) + : child._nativeTag), + ); + + UIManager.setChildren( + parentInstance._nativeTag, // containerTag + nativeTags, // reactTags + ); + + return false; + }, + + getRootHostContext(): {} { + return emptyObject; + }, + + getChildHostContext(): {} { + return emptyObject; + }, + + getPublicInstance(instance) { + return instance; + }, + + insertBefore( + parentInstance: Instance | Container, + child: Instance | TextInstance, + beforeChild: Instance | TextInstance, + ): void { + // TODO (bvaughn): Remove this check when... + // We create a wrapper object for the container in ReactNative render() + // Or we refactor to remove wrapper objects entirely. + // For more info on pros/cons see PR #8560 description. + invariant( + typeof parentInstance !== 'number', + 'Container does not support insertBefore operation', + ); + + const children = (parentInstance: any)._children; + + const index = children.indexOf(child); + + // Move existing child or add new child? + if (index >= 0) { + children.splice(index, 1); + const beforeChildIndex = children.indexOf(beforeChild); + children.splice(beforeChildIndex, 0, child); + + UIManager.manageChildren( + (parentInstance: any)._nativeTag, // containerID + [index], // moveFromIndices + [beforeChildIndex], // moveToIndices + [], // addChildReactTags + [], // addAtIndices + [], // removeAtIndices + ); + } else { + const beforeChildIndex = children.indexOf(beforeChild); + children.splice(beforeChildIndex, 0, child); + + const childTag = typeof child === 'number' ? child : child._nativeTag; + + UIManager.manageChildren( + (parentInstance: any)._nativeTag, // containerID + [], // moveFromIndices + [], // moveToIndices + [childTag], // addChildReactTags + [beforeChildIndex], // addAtIndices + [], // removeAtIndices + ); + } + }, + + prepareForCommit(): void { + // Noop + }, + + prepareUpdate( + instance: Instance, + type: string, + oldProps: Props, + newProps: Props, + rootContainerInstance: Container, + hostContext: {}, + ): null | Object { + return emptyObject; + }, + + removeChild( + parentInstance: Instance | Container, + child: Instance | TextInstance, + ): void { + recursivelyUncacheFiberNode(child); + + if (typeof parentInstance === 'number') { + UIManager.manageChildren( + parentInstance, // containerID + [], // moveFromIndices + [], // moveToIndices + [], // addChildReactTags + [], // addAtIndices + [0], // removeAtIndices + ); + } else { + const children = parentInstance._children; + const index = children.indexOf(child); + + children.splice(index, 1); + + UIManager.manageChildren( + parentInstance._nativeTag, // containerID + [], // moveFromIndices + [], // moveToIndices + [], // addChildReactTags + [], // addAtIndices + [index], // removeAtIndices + ); + } + }, + + resetAfterCommit(): void { + // Noop + }, + + resetTextContent(instance: Instance): void { + // Noop + }, + + shouldDeprioritizeSubtree(type: string, props: Props): boolean { + return false; + }, + + scheduleAnimationCallback: global.requestAnimationFrame, + + scheduleDeferredCallback: global.requestIdleCallback, + + shouldSetTextContent(props: Props): boolean { + // TODO (bvaughn) Revisit this decision. + // Always returning false simplifies the createInstance() implementation, + // But creates an additional child Fiber for raw text children. + // No additional native views are created though. + // It's not clear to me which is better so I'm deferring for now. + // More context @ github.com/facebook/react/pull/8560#discussion_r92111303 + return false; + }, + + useSyncScheduling: true, +}); + +module.exports = NativeRenderer; diff --git a/src/renderers/native/ReactNativeStack.js b/src/renderers/native/ReactNativeStack.js index e3ca928834..3202a352da 100644 --- a/src/renderers/native/ReactNativeStack.js +++ b/src/renderers/native/ReactNativeStack.js @@ -18,7 +18,9 @@ var ReactNativeStackInjection = require('ReactNativeStackInjection'); var ReactUpdates = require('ReactUpdates'); var ReactNativeStackInspector = require('ReactNativeStackInspector'); -var findNodeHandle = require('findNodeHandle'); +var findNumericNodeHandle = require('findNumericNodeHandleStack'); + +import type {ReactNativeType} from 'ReactNativeTypes'; ReactNativeInjection.inject(); ReactNativeStackInjection.inject(); @@ -31,19 +33,10 @@ var render = function( return ReactNativeMount.renderComponent(element, mountInto, callback); }; -var ReactNative = { +var ReactNative: ReactNativeType = { hasReactNativeInitialized: false, - // External users of findNodeHandle() expect the host tag number return type. - // The injected findNodeHandle() strategy returns the instance wrapper though. - // See NativeMethodsMixin#setNativeProps for more info on why this is done. - findNodeHandle(componentOrHandle: any): ?number { - const nodeHandle = findNodeHandle(componentOrHandle); - if (nodeHandle == null || typeof nodeHandle === 'number') { - return nodeHandle; - } - return nodeHandle.getHostNode(); - }, + findNodeHandle: findNumericNodeHandle, render: render, @@ -54,8 +47,32 @@ var ReactNative = { /* eslint-enable camelcase */ unmountComponentAtNodeAndRemoveContainer: ReactNativeMount.unmountComponentAtNodeAndRemoveContainer, + + __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: { + // Used as a mixin in many createClass-based components + NativeMethodsMixin: require('NativeMethodsMixin'), + + // Used by react-native-github/Libraries/ components + ReactGlobalSharedState: require('ReactGlobalSharedState'), // Systrace + ReactNativeComponentTree: require('ReactNativeComponentTree'), // InspectorUtils, ScrollResponder + ReactNativePropRegistry: require('ReactNativePropRegistry'), // flattenStyle, Stylesheet + TouchHistoryMath: require('TouchHistoryMath'), // PanResponder + createReactNativeComponentClass: require('createReactNativeComponentClass'), // eg Text + takeSnapshot: require('takeSnapshot'), // react-native-implementation + }, }; +if (__DEV__) { + // $FlowFixMe + Object.assign( + ReactNative.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED, + { + ReactDebugTool: require('ReactDebugTool'), // RCTRenderingPerf, Systrace + ReactPerf: require('ReactPerf'), // ReactPerfStallHandler, RCTRenderingPerf + }, + ); +} + // Inject the runtime into a devtools global hook regardless of browser. // Allows for debugging when the hook is injected on the page. /* globals __REACT_DEVTOOLS_GLOBAL_HOOK__ */ diff --git a/src/renderers/native/ReactNativeStackInjection.js b/src/renderers/native/ReactNativeStackInjection.js index 5aa3d3d879..0b8d13d9cf 100644 --- a/src/renderers/native/ReactNativeStackInjection.js +++ b/src/renderers/native/ReactNativeStackInjection.js @@ -30,7 +30,6 @@ var ReactNativeTextComponent = require('ReactNativeTextComponent'); var ReactSimpleEmptyComponent = require('ReactSimpleEmptyComponent'); var ReactUpdates = require('ReactUpdates'); -var findNodeHandle = require('findNodeHandle'); var invariant = require('fbjs/lib/invariant'); function inject() { @@ -60,9 +59,6 @@ function inject() { ); }; - findNodeHandle.injection.injectFindNode(instance => instance); - findNodeHandle.injection.injectFindRootNodeID(instance => instance); - ReactEmptyComponent.injection.injectEmptyComponentFactory(EmptyComponent); ReactHostComponent.injection.injectTextComponentClass( diff --git a/src/renderers/native/ReactNativeTypes.js b/src/renderers/native/ReactNativeTypes.js new file mode 100644 index 0000000000..594b8ac5ae --- /dev/null +++ b/src/renderers/native/ReactNativeTypes.js @@ -0,0 +1,89 @@ +/** + * 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. + * + * @providesModule ReactNativeTypes + * @flow + */ +'use strict'; + +import type React from 'react'; + +export type MeasureOnSuccessCallback = ( + x: number, + y: number, + width: number, + height: number, + pageX: number, + pageY: number, +) => void; + +export type MeasureInWindowOnSuccessCallback = ( + x: number, + y: number, + width: number, + height: number, +) => void; + +export type MeasureLayoutOnSuccessCallback = ( + left: number, + top: number, + width: number, + height: number, +) => void; + +/** + * This type keeps ReactNativeFiberHostComponent and NativeMethodsMixin in sync. + * It can also provide types for ReactNative applications that use NMM or refs. + */ +export type NativeMethodsMixinType = { + blur(): void, + focus(): void, + measure(callback: MeasureOnSuccessCallback): void, + measureInWindow(callback: MeasureInWindowOnSuccessCallback): void, + measureLayout( + relativeToNativeNode: number, + onSuccess: MeasureLayoutOnSuccessCallback, + onFail: () => void, + ): void, + setNativeProps(nativeProps: Object): void, +}; + +type ReactNativeBaseComponentViewConfig = { + validAttributes: Object, + uiViewClassName: string, + propTypes?: Object, +}; + +type SecretInternalsType = { + NativeMethodsMixin: NativeMethodsMixinType, + createReactNativeComponentClass( + viewConfig: ReactNativeBaseComponentViewConfig, + ): any, + ReactNativeComponentTree: any, + ReactNativePropRegistry: any, + // TODO (bvaughn) Decide which additional types to expose here? + // And how much information to fill in for the above types. +}; + +/** + * Flat ReactNative renderer bundles are too big for Flow to parse effeciently. + * Provide minimal Flow typing for the high-level RN API and call it a day. + */ +export type ReactNativeType = { + findNodeHandle(componentOrHandle: any): ?number, + render( + element: React.Element, + containerTag: any, + callback: ?Function, + ): any, + unmountComponentAtNode(containerTag: number): any, + unmountComponentAtNodeAndRemoveContainer(containerTag: number): any, + unstable_batchedUpdates: any, // TODO (bvaughn) Add types + + __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: SecretInternalsType, +}; diff --git a/src/renderers/native/findNodeHandle.js b/src/renderers/native/findNodeHandle.js index 10008836ff..e401cbfd63 100644 --- a/src/renderers/native/findNodeHandle.js +++ b/src/renderers/native/findNodeHandle.js @@ -13,11 +13,14 @@ 'use strict'; var ReactInstanceMap = require('ReactInstanceMap'); +var ReactNativeFeatureFlags = require('ReactNativeFeatureFlags'); +var ReactNativeFiberRenderer = require('ReactNativeFiberRenderer'); var {ReactCurrentOwner} = require('ReactGlobalSharedState'); var invariant = require('fbjs/lib/invariant'); var warning = require('fbjs/lib/warning'); +import type {Fiber} from 'ReactFiber'; import type {ReactInstance} from 'ReactInstanceType'; /** @@ -50,8 +53,10 @@ import type {ReactInstance} from 'ReactInstanceType'; * nodeHandle N/A rootNodeID tag */ -let injectedFindNode; -let injectedFindRootNodeID; +// Rollup will strip the ReactNativeFiberRenderer from the Stack build. +const injectedFindNode = ReactNativeFeatureFlags.useFiber + ? (fiber: Fiber) => ReactNativeFiberRenderer.findHostInstance(fiber) + : instance => instance; // TODO (bvaughn) Rename the findNodeHandle module to something more descriptive // eg findInternalHostInstance. This will reduce the likelihood of someone @@ -90,9 +95,8 @@ function findNodeHandle(componentOrHandle: any): any { if (internalInstance) { return injectedFindNode(internalInstance); } else { - var rootNodeID = injectedFindRootNodeID(component); - if (rootNodeID) { - return rootNodeID; + if (component) { + return component; } else { invariant( // Native @@ -115,14 +119,4 @@ function findNodeHandle(componentOrHandle: any): any { } } -// Fiber and stack implementations differ; each must inject a strategy -findNodeHandle.injection = { - injectFindNode(findNode) { - injectedFindNode = findNode; - }, - injectFindRootNodeID(findRootNodeID) { - injectedFindRootNodeID = findRootNodeID; - }, -}; - module.exports = findNodeHandle; diff --git a/src/renderers/native/findNumericNodeHandleFiber.js b/src/renderers/native/findNumericNodeHandleFiber.js new file mode 100644 index 0000000000..e2bae9e99e --- /dev/null +++ b/src/renderers/native/findNumericNodeHandleFiber.js @@ -0,0 +1,29 @@ +/** + * 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. + * + * @providesModule findNumericNodeHandleFiber + * @flow + */ +'use strict'; + +var findNodeHandle = require('findNodeHandle'); + +/** + * External users of findNodeHandle() expect the host tag number return type. + * The injected findNodeHandle() strategy returns the instance wrapper though. + * See NativeMethodsMixin#setNativeProps for more info on why this is done. + */ +module.exports = function findNumericNodeHandleFiber( + componentOrHandle: any, +): ?number { + const instance: any = findNodeHandle(componentOrHandle); + if (instance == null || typeof instance === 'number') { + return instance; + } + return instance._nativeTag; +}; diff --git a/src/renderers/native/findNumericNodeHandleStack.js b/src/renderers/native/findNumericNodeHandleStack.js new file mode 100644 index 0000000000..4588ba8433 --- /dev/null +++ b/src/renderers/native/findNumericNodeHandleStack.js @@ -0,0 +1,29 @@ +/** + * 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. + * + * @providesModule findNumericNodeHandleStack + * @flow + */ +'use strict'; + +var findNodeHandle = require('findNodeHandle'); + +/** + * External users of findNodeHandle() expect the host tag number return type. + * The injected findNodeHandle() strategy returns the instance wrapper though. + * See NativeMethodsMixin#setNativeProps for more info on why this is done. + */ +module.exports = function findNumericNodeHandleStack( + componentOrHandle: any, +): ?number { + const nodeHandle = findNodeHandle(componentOrHandle); + if (nodeHandle == null || typeof nodeHandle === 'number') { + return nodeHandle; + } + return nodeHandle.getHostNode(); +}; diff --git a/src/renderers/native/takeSnapshot.js b/src/renderers/native/takeSnapshot.js index 98608c83e5..252bc5dc8e 100644 --- a/src/renderers/native/takeSnapshot.js +++ b/src/renderers/native/takeSnapshot.js @@ -11,11 +11,15 @@ */ 'use strict'; -var ReactNative = require('ReactNative'); -var UIManager = require('UIManager'); +const ReactNativeFeatureFlags = require('ReactNativeFeatureFlags'); +const UIManager = require('UIManager'); import type {Element} from 'React'; +const findNumericNodeHandle = ReactNativeFeatureFlags.useFiber + ? require('findNumericNodeHandleFiber') + : require('findNumericNodeHandleStack'); + /** * Capture an image of the screen, window or an individual view. The image * will be stored in a temporary file that will only exist for as long as the @@ -43,7 +47,7 @@ function takeSnapshot( }, ): Promise { if (typeof view !== 'number' && view !== 'window') { - view = ReactNative.findNodeHandle(view) || 'window'; + view = findNumericNodeHandle(view) || 'window'; } // Call the hidden '__takeSnapshot' method; the main one throws an error to diff --git a/src/renderers/shared/fiber/ReactChildFiber.js b/src/renderers/shared/fiber/ReactChildFiber.js index 3c1f06e911..3cb1cad247 100644 --- a/src/renderers/shared/fiber/ReactChildFiber.js +++ b/src/renderers/shared/fiber/ReactChildFiber.js @@ -13,8 +13,7 @@ 'use strict'; import type {ReactElement} from 'ReactElementType'; -import type {ReactCoroutine, ReactYield} from 'ReactCoroutine'; -import type {ReactPortal} from 'ReactPortal'; +import type {ReactCoroutine, ReactPortal, ReactYield} from 'ReactTypes'; import type {Fiber} from 'ReactFiber'; import type {ReactInstance} from 'ReactInstanceType'; import type {PriorityLevel} from 'ReactPriorityLevel'; diff --git a/src/renderers/shared/fiber/ReactFiber.js b/src/renderers/shared/fiber/ReactFiber.js index b7c6ed30c0..6cf9645a92 100644 --- a/src/renderers/shared/fiber/ReactFiber.js +++ b/src/renderers/shared/fiber/ReactFiber.js @@ -14,9 +14,12 @@ import type {ReactElement, Source} from 'ReactElementType'; import type {ReactInstance, DebugID} from 'ReactInstanceType'; -import type {ReactFragment} from 'ReactTypes'; -import type {ReactCoroutine, ReactYield} from 'ReactCoroutine'; -import type {ReactPortal} from 'ReactPortal'; +import type { + ReactCoroutine, + ReactFragment, + ReactPortal, + ReactYield, +} from 'ReactTypes'; import type {TypeOfWork} from 'ReactTypeOfWork'; import type {TypeOfInternalContext} from 'ReactTypeOfInternalContext'; import type {TypeOfSideEffect} from 'ReactTypeOfSideEffect'; diff --git a/src/renderers/shared/fiber/ReactFiberBeginWork.js b/src/renderers/shared/fiber/ReactFiberBeginWork.js index f0393658fe..dce9c2b931 100644 --- a/src/renderers/shared/fiber/ReactFiberBeginWork.js +++ b/src/renderers/shared/fiber/ReactFiberBeginWork.js @@ -12,7 +12,7 @@ 'use strict'; -import type {ReactCoroutine} from 'ReactCoroutine'; +import type {ReactCoroutine} from 'ReactTypes'; import type {Fiber} from 'ReactFiber'; import type {HostContext} from 'ReactFiberHostContext'; import type {FiberRoot} from 'ReactFiberRoot'; diff --git a/src/renderers/shared/fiber/ReactFiberCompleteWork.js b/src/renderers/shared/fiber/ReactFiberCompleteWork.js index 644356983b..c6e0082ed9 100644 --- a/src/renderers/shared/fiber/ReactFiberCompleteWork.js +++ b/src/renderers/shared/fiber/ReactFiberCompleteWork.js @@ -12,7 +12,7 @@ 'use strict'; -import type {ReactCoroutine} from 'ReactCoroutine'; +import type {ReactCoroutine} from 'ReactTypes'; import type {Fiber} from 'ReactFiber'; import type {HostContext} from 'ReactFiberHostContext'; import type {FiberRoot} from 'ReactFiberRoot'; diff --git a/src/renderers/shared/fiber/isomorphic/ReactCoroutine.js b/src/renderers/shared/fiber/isomorphic/ReactCoroutine.js index a11cb5b0c2..3ce87530e1 100644 --- a/src/renderers/shared/fiber/isomorphic/ReactCoroutine.js +++ b/src/renderers/shared/fiber/isomorphic/ReactCoroutine.js @@ -12,7 +12,7 @@ 'use strict'; -import type {ReactNodeList} from 'ReactTypes'; +import type {ReactCoroutine, ReactNodeList, ReactYield} from 'ReactTypes'; // The Symbol used to tag the special React types. If there is no native Symbol // nor polyfill, then a plain number is used for performance. @@ -28,19 +28,6 @@ if (typeof Symbol === 'function' && Symbol.for) { type CoroutineHandler = (props: T, yields: Array) => ReactNodeList; -export type ReactCoroutine = { - $$typeof: Symbol | number, - key: null | string, - children: any, - // This should be a more specific CoroutineHandler - handler: (props: any, yields: Array) => ReactNodeList, - props: any, -}; -export type ReactYield = { - $$typeof: Symbol | number, - value: mixed, -}; - exports.createCoroutine = function( children: mixed, handler: CoroutineHandler, diff --git a/src/renderers/shared/fiber/isomorphic/ReactPortal.js b/src/renderers/shared/fiber/isomorphic/ReactPortal.js index e7045ac724..78198e099d 100644 --- a/src/renderers/shared/fiber/isomorphic/ReactPortal.js +++ b/src/renderers/shared/fiber/isomorphic/ReactPortal.js @@ -12,7 +12,7 @@ 'use strict'; -import type {ReactNodeList} from 'ReactTypes'; +import type {ReactNodeList, ReactPortal} from 'ReactTypes'; // The Symbol used to tag the special React types. If there is no native Symbol // nor polyfill, then a plain number is used for performance. @@ -20,15 +20,6 @@ var REACT_PORTAL_TYPE = (typeof Symbol === 'function' && Symbol.for && Symbol.for('react.portal')) || 0xeaca; -export type ReactPortal = { - $$typeof: Symbol | number, - key: null | string, - containerInfo: any, - children: ReactNodeList, - // TODO: figure out the API for cross-renderer implementation. - implementation: any, -}; - exports.createPortal = function( children: ReactNodeList, containerInfo: any, diff --git a/src/renderers/shared/fiber/isomorphic/ReactTypes.js b/src/renderers/shared/fiber/isomorphic/ReactTypes.js index 2a3c05a1f5..f63948d403 100644 --- a/src/renderers/shared/fiber/isomorphic/ReactTypes.js +++ b/src/renderers/shared/fiber/isomorphic/ReactTypes.js @@ -12,9 +12,6 @@ 'use strict'; -import type {ReactCoroutine, ReactYield} from 'ReactCoroutine'; -import type {ReactPortal} from 'ReactPortal'; - export type ReactNode = | ReactElement | ReactCoroutine @@ -30,3 +27,26 @@ export type ReactNodeList = ReactEmpty | ReactNode; export type ReactText = string | number; export type ReactEmpty = null | void | boolean; + +export type ReactCoroutine = { + $$typeof: Symbol | number, + key: null | string, + children: any, + // This should be a more specific CoroutineHandler + handler: (props: any, yields: Array) => ReactNodeList, + props: any, +}; + +export type ReactYield = { + $$typeof: Symbol | number, + value: mixed, +}; + +export type ReactPortal = { + $$typeof: Symbol | number, + key: null | string, + containerInfo: any, + children: ReactNodeList, + // TODO: figure out the API for cross-renderer implementation. + implementation: any, +};