Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion scripts/rollup/packaging.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const facebookWWWSrcDependencies = [

// these files need to be copied to the react-native build
const reactNativeSrcDependencies = [
'src/shared/utils/PooledClass.js',
'src/renderers/shared/utils/PooledClass.js',
'src/renderers/shared/fiber/isomorphic/ReactTypes.js',
'src/renderers/native/ReactNativeTypes.js',
];
Expand Down
24 changes: 12 additions & 12 deletions scripts/rollup/results.json
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
{
"bundleSizes": {
"react.development.js (UMD_DEV)": {
"size": 69376,
"gzip": 17780
"size": 65151,
"gzip": 16692
},
"react.production.min.js (UMD_PROD)": {
"size": 7585,
"gzip": 3002
"size": 6489,
"gzip": 2704
},
"react.development.js (NODE_DEV)": {
"size": 59667,
"gzip": 15411
"size": 55446,
"gzip": 14337
},
"react.production.min.js (NODE_PROD)": {
"size": 6451,
"gzip": 2576
"size": 5352,
"gzip": 2269
},
"React-dev.js (FB_DEV)": {
"size": 56905,
"gzip": 14622
"size": 52704,
"gzip": 13552
},
"React-prod.js (FB_PROD)": {
"size": 27099,
"gzip": 7215
"size": 24229,
"gzip": 6612
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

whoa. why is FB_PROD so much larger than NODE_PROD/UMD_PROD? Legacy files that are being consumed internally yet?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it's just not minified. We might minify it eventually but for now it doesn't seem necessary (it goes through our pipeline anyway), and it's easier to see what happens during the sync.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah! Great point. 👍

},
"react-dom.development.js (UMD_DEV)": {
"size": 624799,
Expand Down
287 changes: 238 additions & 49 deletions src/isomorphic/children/ReactChildren.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,39 +11,213 @@

'use strict';

var PooledClass = require('PooledClass');
var ReactElement = require('ReactElement');

var emptyFunction = require('fbjs/lib/emptyFunction');
var traverseAllChildren = require('traverseAllChildren');
var invariant = require('fbjs/lib/invariant');

var twoArgumentPooler = PooledClass.twoArgumentPooler;
var fourArgumentPooler = PooledClass.fourArgumentPooler;
if (__DEV__) {
var warning = require('fbjs/lib/warning');
var {getStackAddendum} = require('ReactDebugCurrentFrame');
}

var ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator;
var FAUX_ITERATOR_SYMBOL = '@@iterator'; // Before Symbol spec.
// The Symbol used to tag the ReactElement type. If there is no native Symbol
// nor polyfill, then a plain number is used for performance.
var REACT_ELEMENT_TYPE =
(typeof Symbol === 'function' && Symbol.for && Symbol.for('react.element')) ||
0xeac7;

var SEPARATOR = '.';
var SUBSEPARATOR = ':';

/**
* Escape and wrap key so it is safe to use as a reactid
*
* @param {string} key to be escaped.
* @return {string} the escaped key.
*/
function escape(key: string): string {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey,
Why you added types in flow style if this file have't flow comment?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably accidentally or by copy paste. Want to send a PR to Flow-ify it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep! I can delete useless flow typechecking or add comment flow. If I add flow comment maybe I can try to update types for all file? If you want this.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure.

var escapeRegex = /[=:]/g;
var escaperLookup = {
'=': '=0',
':': '=2',
};
var escapedString = ('' + key).replace(escapeRegex, function(match) {
return escaperLookup[match];
});

return '$' + escapedString;
}

/**
* TODO: Test that a single child and an array with one item have the same key
* pattern.
*/

var didWarnAboutMaps = false;

var userProvidedKeyEscapeRegex = /\/+/g;
function escapeUserProvidedKey(text) {
return ('' + text).replace(userProvidedKeyEscapeRegex, '$&/');
}

/**
* PooledClass representing the bookkeeping associated with performing a child
* traversal. Allows avoiding binding callbacks.
* @param {?*} children Children tree container.
* @param {!string} nameSoFar Name of the key path so far.
* @param {!function} callback Callback to invoke with each child found.
* @param {?*} traverseContext Used to pass information throughout the traversal
* process.
* @return {!number} The number of children in this subtree.
*/
function traverseAllChildrenImpl(
children,
nameSoFar,
callback,
traverseContext,
) {
var type = typeof children;

if (type === 'undefined' || type === 'boolean') {
// All of the above are perceived as null.
children = null;
}

if (
children === null ||
type === 'string' ||
type === 'number' ||
// The following is inlined from ReactElement. This means we can optimize
// some checks. React Fiber also inlines this logic for similar purposes.
(type === 'object' && children.$$typeof === REACT_ELEMENT_TYPE)
) {
callback(
traverseContext,
children,
// If it's the only child, treat the name as if it was wrapped in an array
// so that it's consistent if the number of children grows.
nameSoFar === '' ? SEPARATOR + getComponentKey(children, 0) : nameSoFar,
);
return 1;
}

var child;
var nextName;
var subtreeCount = 0; // Count of children found in the current subtree.
var nextNamePrefix = nameSoFar === '' ? SEPARATOR : nameSoFar + SUBSEPARATOR;

if (Array.isArray(children)) {
for (var i = 0; i < children.length; i++) {
child = children[i];
nextName = nextNamePrefix + getComponentKey(child, i);
subtreeCount += traverseAllChildrenImpl(
child,
nextName,
callback,
traverseContext,
);
}
} else {
var iteratorFn =
(ITERATOR_SYMBOL && children[ITERATOR_SYMBOL]) ||
children[FAUX_ITERATOR_SYMBOL];
if (typeof iteratorFn === 'function') {
if (__DEV__) {
// Warn about using Maps as children
if (iteratorFn === children.entries) {
warning(
didWarnAboutMaps,
'Using Maps as children is unsupported and will likely yield ' +
'unexpected results. Convert it to a sequence/iterable of keyed ' +
'ReactElements instead.%s',
getStackAddendum(),
);
didWarnAboutMaps = true;
}
}

var iterator = iteratorFn.call(children);
var step;
var ii = 0;
while (!(step = iterator.next()).done) {
child = step.value;
nextName = nextNamePrefix + getComponentKey(child, ii++);
subtreeCount += traverseAllChildrenImpl(
child,
nextName,
callback,
traverseContext,
);
}
} else if (type === 'object') {
var addendum = '';
if (__DEV__) {
addendum =
' If you meant to render a collection of children, use an array ' +
'instead.' +
getStackAddendum();
}
var childrenString = '' + children;
invariant(
false,
'Objects are not valid as a React child (found: %s).%s',
childrenString === '[object Object]'
? 'object with keys {' + Object.keys(children).join(', ') + '}'
: childrenString,
addendum,
);
}
}

return subtreeCount;
}

/**
* Traverses children that are typically specified as `props.children`, but
* might also be specified through attributes:
*
* @constructor ForEachBookKeeping
* @param {!function} forEachFunction Function to perform traversal with.
* @param {?*} forEachContext Context to perform context with.
* - `traverseAllChildren(this.props.children, ...)`
* - `traverseAllChildren(this.props.leftPanelChildren, ...)`
*
* The `traverseContext` is an optional argument that is passed through the
* entire traversal. It can be used to store accumulations or anything else that
* the callback might find relevant.
*
* @param {?*} children Children tree object.
* @param {!function} callback To invoke upon traversing each child.
* @param {?*} traverseContext Context for traversal.
* @return {!number} The number of children in this subtree.
*/
function ForEachBookKeeping(forEachFunction, forEachContext) {
this.func = forEachFunction;
this.context = forEachContext;
this.count = 0;
}
ForEachBookKeeping.prototype.destructor = function() {
this.func = null;
this.context = null;
this.count = 0;
};
PooledClass.addPoolingTo(ForEachBookKeeping, twoArgumentPooler);
function traverseAllChildren(children, callback, traverseContext) {
if (children == null) {
return 0;
}

return traverseAllChildrenImpl(children, '', callback, traverseContext);
}

/**
* Generate a key string that identifies a component within a set.
*
* @param {*} component A component that could contain a manual key.
* @param {number} index Index that is used if a manual key is not provided.
* @return {string}
*/
function getComponentKey(component, index) {
// Do some typechecking here since we call this blindly. We want to ensure
// that we don't block potential future ES APIs.
if (
typeof component === 'object' &&
component !== null &&
component.key != null
) {
// Explicit key
return escape(component.key);
}
// Implicit key determined by the index in the set
return index.toString(36);
}

function forEachSingleChild(bookKeeping, child, name) {
var {func, context} = bookKeeping;
Expand All @@ -66,38 +240,53 @@ function forEachChildren(children, forEachFunc, forEachContext) {
if (children == null) {
return children;
}
var traverseContext = ForEachBookKeeping.getPooled(
var traverseContext = getPooledTraverseContext(
null,
null,
forEachFunc,
forEachContext,
);
traverseAllChildren(children, forEachSingleChild, traverseContext);
ForEachBookKeeping.release(traverseContext);
releaseTraverseContext(traverseContext);
}

/**
* PooledClass representing the bookkeeping associated with performing a child
* mapping. Allows avoiding binding callbacks.
*
* @constructor MapBookKeeping
* @param {!*} mapResult Object containing the ordered map of results.
* @param {!function} mapFunction Function to perform mapping with.
* @param {?*} mapContext Context to perform mapping with.
*/
function MapBookKeeping(mapResult, keyPrefix, mapFunction, mapContext) {
this.result = mapResult;
this.keyPrefix = keyPrefix;
this.func = mapFunction;
this.context = mapContext;
this.count = 0;
}
MapBookKeeping.prototype.destructor = function() {
this.result = null;
this.keyPrefix = null;
this.func = null;
this.context = null;
this.count = 0;
};
PooledClass.addPoolingTo(MapBookKeeping, fourArgumentPooler);
var POOL_SIZE = 10;
var traverseContextPool = [];
function getPooledTraverseContext(
mapResult,
keyPrefix,
mapFunction,
mapContext,
) {
if (traverseContextPool.length) {
var traverseContext = traverseContextPool.pop();
traverseContext.result = mapResult;
traverseContext.keyPrefix = keyPrefix;
traverseContext.func = mapFunction;
traverseContext.context = mapContext;
traverseContext.count = 0;
return traverseContext;
} else {
return {
result: mapResult,
keyPrefix: keyPrefix,
func: mapFunction,
context: mapContext,
count: 0,
};
}
}

function releaseTraverseContext(traverseContext) {
traverseContext.result = null;
traverseContext.keyPrefix = null;
traverseContext.func = null;
traverseContext.context = null;
traverseContext.count = 0;
if (traverseContextPool.length < POOL_SIZE) {
traverseContextPool.push(traverseContext);
}
}

function mapSingleChildIntoContext(bookKeeping, child, childKey) {
var {result, keyPrefix, func, context} = bookKeeping;
Expand Down Expand Up @@ -132,14 +321,14 @@ function mapIntoWithKeyPrefixInternal(children, array, prefix, func, context) {
if (prefix != null) {
escapedPrefix = escapeUserProvidedKey(prefix) + '/';
}
var traverseContext = MapBookKeeping.getPooled(
var traverseContext = getPooledTraverseContext(
array,
escapedPrefix,
func,
context,
);
traverseAllChildren(children, mapSingleChildIntoContext, traverseContext);
MapBookKeeping.release(traverseContext);
releaseTraverseContext(traverseContext);
}

/**
Expand All @@ -164,7 +353,7 @@ function mapChildren(children, func, context) {
return result;
}

function forEachSingleChildDummy(traverseContext, child, name) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're already importing the emptyFunction library in ReactChildren, could use just use emptyFunction.thatReturnsNull and get rid of this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed.

function forEachSingleChildDummy() {
return null;
}

Expand Down
Loading