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
This commit is contained in:
Brian Vaughn 2017-05-24 17:06:30 +01:00 committed by GitHub
parent 6ac91d24c4
commit c22b94f14a
33 changed files with 924 additions and 589 deletions

View File

@ -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

View File

@ -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 *******/

View File

@ -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 : ''}
`;

View File

@ -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() {

View File

@ -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
}
}
}

View File

@ -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<NativeMethodsMixinType>);

View File

@ -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;

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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;

37
scripts/rollup/sync.js Normal file
View File

@ -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,
};

30
scripts/rollup/utils.js Normal file
View File

@ -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,
};

View File

@ -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<NativeMethodsMixinType> = {
/**
* 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);

View File

@ -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.

View File

@ -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;

View File

@ -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<Instance | number>,
_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<any>, 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,

View File

@ -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<Instance | number>;
_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;

View File

@ -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<Instance | number>,
_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;

View File

@ -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__ */

View File

@ -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(

View File

@ -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<any>,
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,
};

View File

@ -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;

View File

@ -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;
};

View File

@ -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();
};

View File

@ -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<any> {
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

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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<T> = (props: T, yields: Array<mixed>) => ReactNodeList;
export type ReactCoroutine = {
$$typeof: Symbol | number,
key: null | string,
children: any,
// This should be a more specific CoroutineHandler
handler: (props: any, yields: Array<mixed>) => ReactNodeList,
props: any,
};
export type ReactYield = {
$$typeof: Symbol | number,
value: mixed,
};
exports.createCoroutine = function<T>(
children: mixed,
handler: CoroutineHandler<T>,

View File

@ -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,

View File

@ -12,9 +12,6 @@
'use strict';
import type {ReactCoroutine, ReactYield} from 'ReactCoroutine';
import type {ReactPortal} from 'ReactPortal';
export type ReactNode =
| ReactElement<any>
| 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<mixed>) => 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,
};