diff --git a/performance/modules/benchmark.js b/performance/benchmark.js
similarity index 100%
rename from performance/modules/benchmark.js
rename to performance/benchmark.js
diff --git a/performance/components/AppText/index.js b/performance/components/AppText/index.js
new file mode 100644
index 00000000..4447f4db
--- /dev/null
+++ b/performance/components/AppText/index.js
@@ -0,0 +1,115 @@
+import theme from '../theme';
+import React, { PropTypes, PureComponent } from 'react';
+import { StyleSheet, Text } from 'react-native';
+
+class AppText extends PureComponent {
+ static displayName = 'AppText';
+
+ static propTypes = {
+ align: PropTypes.oneOf(['center', 'left', 'right']),
+ color: PropTypes.oneOf(['blue', 'deepGray', 'normal', 'red', 'white']),
+ fontStyle: PropTypes.oneOf(['normal', 'italic']),
+ size: PropTypes.oneOf(['small', 'normal', 'large']),
+ uppercase: PropTypes.bool,
+ weight: PropTypes.oneOf(['normal', 'bold'])
+ };
+
+ render() {
+ const {
+ align,
+ color,
+ fontStyle,
+ size,
+ uppercase,
+ weight,
+ ...other
+ } = this.props;
+
+ const style = [
+ styles.root,
+ align && alignStyles[align],
+ color && colorStyles[color],
+ fontStyle && fontStyles[fontStyle],
+ size && sizeStyles[size],
+ weight && weightStyles[weight],
+ uppercase === true && styles.uppercase
+ ];
+
+ return ;
+ }
+}
+
+const styles = StyleSheet.create({
+ root: {
+ fontFamily: theme.fontFamily,
+ fontSize: theme.fontSize.normal,
+ fontWeight: 'normal',
+ lineHeight: theme.createLength(theme.lineHeight),
+ wordWrap: 'break-word'
+ },
+ uppercase: {
+ textTransform: 'uppercase'
+ }
+});
+
+const alignStyles = StyleSheet.create({
+ center: {
+ textAlign: 'center'
+ },
+ left: {
+ textAlign: 'left'
+ },
+ right: {
+ textAlign: 'right'
+ }
+});
+
+const colorStyles = StyleSheet.create({
+ blue: {
+ color: theme.colors.blue
+ },
+ deepGray: {
+ color: theme.colors.deepGray
+ },
+ normal: {
+ color: theme.colors.textBlack
+ },
+ red: {
+ color: theme.colors.red
+ },
+ white: {
+ color: theme.colors.white
+ }
+});
+
+const fontStyles = StyleSheet.create({
+ normal: {
+ fontStyle: 'normal'
+ },
+ italic: {
+ fontStyle: 'italic'
+ }
+});
+
+const sizeStyles = StyleSheet.create({
+ small: {
+ fontSize: theme.fontSize.small
+ },
+ normal: {
+ fontSize: theme.fontSize.normal
+ },
+ large: {
+ fontSize: theme.fontSize.large
+ }
+});
+
+const weightStyles = StyleSheet.create({
+ normal: {
+ fontWeight: '400'
+ },
+ bold: {
+ fontWeight: 'bold'
+ }
+});
+
+export default AppText;
diff --git a/performance/components/AspectRatio/index.js b/performance/components/AspectRatio/index.js
new file mode 100644
index 00000000..be85624e
--- /dev/null
+++ b/performance/components/AspectRatio/index.js
@@ -0,0 +1,40 @@
+import React, { PureComponent, PropTypes } from 'react';
+import { StyleSheet, View } from 'react-native';
+
+class AspectRatio extends PureComponent {
+ static displayName = 'AspectRatio';
+
+ static propTypes = {
+ children: PropTypes.any,
+ ratio: PropTypes.number,
+ style: PropTypes.object
+ };
+
+ static defaultProps = {
+ ratio: 1
+ };
+
+ render() {
+ const { children, ratio, style } = this.props;
+ const percentage = 100 / ratio;
+
+ return (
+
+
+ {children}
+
+ );
+ }
+}
+
+const styles = StyleSheet.create({
+ root: {
+ overflow: 'hidden'
+ },
+ shim: {
+ display: 'block',
+ width: '100%'
+ }
+});
+
+export default AspectRatio;
diff --git a/performance/implementations/css-modules/Box/index.js b/performance/components/Box/index.css-modules.js
similarity index 90%
rename from performance/implementations/css-modules/Box/index.js
rename to performance/components/Box/index.css-modules.js
index c0acfa05..7123f1ee 100644
--- a/performance/implementations/css-modules/Box/index.js
+++ b/performance/components/Box/index.css-modules.js
@@ -1,7 +1,7 @@
/* eslint-disable react/prop-types */
import classnames from 'classnames';
import React from 'react';
-import View from '../View';
+import View from '../View/index.css-modules';
import styles from './styles.css';
const Box = ({ color, fixed = false, layout = 'column', outer = false, ...other }) => (
diff --git a/performance/implementations/glamor/Box/index.js b/performance/components/Box/index.glamor.js
similarity index 95%
rename from performance/implementations/glamor/Box/index.js
rename to performance/components/Box/index.glamor.js
index 822c2ec4..24a324af 100644
--- a/performance/implementations/glamor/Box/index.js
+++ b/performance/components/Box/index.glamor.js
@@ -1,7 +1,7 @@
/* eslint-disable react/prop-types */
import { css } from 'glamor';
import React from 'react';
-import View from '../View';
+import View from '../View/index.glamor';
const Box = ({ color, fixed = false, layout = 'column', outer = false, ...other }) => (
(
(
{
switch (color) {
diff --git a/performance/implementations/css-modules/Box/styles.css b/performance/components/Box/styles.css
similarity index 100%
rename from performance/implementations/css-modules/Box/styles.css
rename to performance/components/Box/styles.css
diff --git a/performance/components/GridView/index.js b/performance/components/GridView/index.js
new file mode 100644
index 00000000..808f5254
--- /dev/null
+++ b/performance/components/GridView/index.js
@@ -0,0 +1,50 @@
+import { StyleSheet, View } from 'react-native';
+import React, { Component, PropTypes } from 'react';
+import theme from '../theme';
+
+class GridView extends Component {
+ static displayName = 'GridView';
+
+ static propTypes = {
+ children: PropTypes.node,
+ hasGap: PropTypes.bool,
+ style: PropTypes.object
+ };
+
+ render() {
+ const { children, hasGap, style, ...other } = this.props;
+
+ return (
+
+ {React.Children.map(children, child => {
+ return child &&
+ React.cloneElement(child, {
+ style: [child.props.style, styles.column, hasGap && styles.hasGapColumn]
+ });
+ })}
+
+ );
+ }
+}
+
+const styles = StyleSheet.create({
+ root: {
+ flexDirection: 'row'
+ },
+ /**
+ * 1. Distribute all space (rather than extra space)
+ * 2. Prevent wide content from forcing wider flex columns
+ */
+ column: {
+ flexBasis: 0, // 1
+ minWidth: 0 // 2
+ },
+ hasGap: {
+ marginHorizontal: theme.createLength(theme.spaceX * (-0.5), 'rem')
+ },
+ hasGapColumn: {
+ marginHorizontal: theme.createLength(theme.spaceX * 0.5, 'rem')
+ }
+});
+
+export default GridView;
diff --git a/performance/components/Icons/DirectMessage.js b/performance/components/Icons/DirectMessage.js
new file mode 100644
index 00000000..b6aa2161
--- /dev/null
+++ b/performance/components/Icons/DirectMessage.js
@@ -0,0 +1,20 @@
+import { createDOMElement } from 'react-native';
+import React from 'react';
+import styles from './styles';
+
+const IconDirectMessage = props => createDOMElement('svg', {
+ children: (
+
+
+
+
+ ),
+ style: [styles.icon, props.style],
+ viewBox: '0 0 56 72'
+});
+
+IconDirectMessage.metadata = { height: 72, width: 56 };
+
+export default IconDirectMessage;
diff --git a/performance/components/Icons/Heart.js b/performance/components/Icons/Heart.js
new file mode 100644
index 00000000..d5695aa1
--- /dev/null
+++ b/performance/components/Icons/Heart.js
@@ -0,0 +1,19 @@
+import { createDOMElement } from 'react-native';
+import React from 'react';
+import styles from './styles';
+
+const IconHeart = props => createDOMElement('svg', {
+ children: (
+
+
+
+ ),
+ style: [styles.icon, props.style],
+ viewBox: '0 0 54 72'
+});
+
+IconHeart.metadata = { height: 72, width: 54 };
+
+export default IconHeart;
diff --git a/performance/components/Icons/Reply.js b/performance/components/Icons/Reply.js
new file mode 100644
index 00000000..699c5fec
--- /dev/null
+++ b/performance/components/Icons/Reply.js
@@ -0,0 +1,19 @@
+import { createDOMElement } from 'react-native';
+import React from 'react';
+import styles from './styles';
+
+const IconReply = props => createDOMElement('svg', {
+ children: (
+
+
+
+ ),
+ style: [styles.icon, props.style],
+ viewBox: '0 0 62 72'
+});
+
+IconReply.metadata = { height: 72, width: 62 };
+
+export default IconReply;
diff --git a/performance/components/Icons/Retweet.js b/performance/components/Icons/Retweet.js
new file mode 100644
index 00000000..daff6649
--- /dev/null
+++ b/performance/components/Icons/Retweet.js
@@ -0,0 +1,19 @@
+import { createDOMElement } from 'react-native';
+import React from 'react';
+import styles from './styles';
+
+const IconRetweet = props => createDOMElement('svg', {
+ children: (
+
+
+
+ ),
+ style: [styles.icon, props.style],
+ viewBox: '0 0 74 72'
+});
+
+IconRetweet.metadata = { height: 72, width: 74 };
+
+export default IconRetweet;
diff --git a/performance/components/Icons/styles.js b/performance/components/Icons/styles.js
new file mode 100644
index 00000000..380d7594
--- /dev/null
+++ b/performance/components/Icons/styles.js
@@ -0,0 +1,15 @@
+import { StyleSheet } from 'react-native';
+
+const styles = StyleSheet.create({
+ icon: {
+ display: 'inline-block',
+ fill: 'currentcolor',
+ height: '1.25em',
+ maxWidth: '100%',
+ position: 'relative',
+ userSelect: 'none',
+ verticalAlign: 'text-bottom'
+ }
+});
+
+export default styles;
diff --git a/performance/modules/NestedTree.js b/performance/components/NestedTree/index.js
similarity index 100%
rename from performance/modules/NestedTree.js
rename to performance/components/NestedTree/index.js
diff --git a/performance/components/Tweet/index.js b/performance/components/Tweet/index.js
new file mode 100644
index 00000000..fc4a05df
--- /dev/null
+++ b/performance/components/Tweet/index.js
@@ -0,0 +1,144 @@
+import AspectRatio from '../AspectRatio';
+import GridView from '../GridView';
+import TweetActionsBar from '../TweetActionsBar';
+import TweetText from '../TweetText';
+import UserAvatar from '../UserAvatar';
+import UserNames from '../UserNames';
+import { Image, StyleSheet, Text, View } from 'react-native';
+import React, { Component, PropTypes } from 'react';
+import theme from '../theme';
+
+export class Tweet extends Component {
+ static displayName = 'Tweet';
+
+ static propTypes = {
+ tweet: PropTypes.object.isRequired
+ };
+
+ render() {
+ const { tweet } = this.props;
+ const { id, lang, media, textParts, timestamp, user } = tweet;
+ const { fullName, profileImageUrl, screenName } = user;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {media
+ ?
+
+
+
+
+ : null}
+
+
+
+
+
+
+
+ );
+ }
+}
+
+const styles = StyleSheet.create({
+ root: {
+ paddingVertical: theme.createLength(theme.spaceY * 0.75, 'rem'),
+ paddingHorizontal: theme.createLength(theme.spaceX, 'rem')
+ },
+ avatarColumn: {
+ flexGrow: 1,
+ minWidth: 32
+ },
+ bodyColumn: {
+ flexGrow: 7
+ },
+ row: {
+ flexDirection: 'row',
+ justifyContent: 'space-between'
+ },
+ avatarLink: {
+ display: 'block',
+ flexShrink: 1,
+ flexGrow: 0,
+ width: '100%'
+ },
+ avatar: {
+ width: '100%'
+ },
+ body: {
+ marginTop: '-0.15rem'
+ },
+ timestamp: {
+ color: theme.colors.deepGray,
+ marginLeft: theme.createLength(theme.spaceX, 'rem'),
+ order: 1,
+ textDecorationLine: 'none',
+ whiteSpace: 'nowrap'
+ },
+ actionBar: {
+ marginTop: theme.createLength(theme.spaceY * 0.5, 'rem')
+ },
+ richContent: {
+ borderRadius: '0.35rem',
+ marginTop: theme.createLength(theme.spaceY * 0.5, 'rem'),
+ overflow: 'hidden'
+ },
+ media: {
+ ...StyleSheet.absoluteFillObject,
+ margin: 'auto',
+ width: 'auto',
+ height: 'auto'
+ }
+});
+
+export default Tweet;
diff --git a/performance/components/TweetAction/index.js b/performance/components/TweetAction/index.js
new file mode 100644
index 00000000..d1c4ec4f
--- /dev/null
+++ b/performance/components/TweetAction/index.js
@@ -0,0 +1,77 @@
+import IconReply from '../Icons/Reply';
+import IconHeart from '../Icons/Heart';
+import IconRetweet from '../Icons/Retweet';
+import IconDirectMessage from '../Icons/DirectMessage';
+import { Text, View, StyleSheet } from 'react-native';
+import React, { PropTypes } from 'react';
+import theme from '../theme';
+
+const getIcon = (icon, highlighted) => {
+ switch (icon) {
+ case 'like':
+ return ;
+ case 'reply':
+ return ;
+ case 'retweet':
+ return ;
+ case 'directMessage':
+ return ;
+ default:
+ return null;
+ }
+};
+
+export default class TweetAction extends React.Component {
+ static displayName = 'TweetAction';
+
+ static propTypes = {
+ count: PropTypes.number,
+ displayMode: PropTypes.oneOf(['like', 'reply', 'retweet', 'directMessage']),
+ highlighted: PropTypes.bool,
+ onPress: PropTypes.func,
+ style: PropTypes.object
+ };
+
+ render() {
+ const { count, displayMode, highlighted, onPress, style } = this.props;
+
+ return (
+
+
+ {getIcon(displayMode, highlighted)}
+ {count > 0 ? {count} : null}
+
+
+ );
+ }
+}
+
+const styles = StyleSheet.create({
+ root: {
+ minHeight: theme.createLength(theme.lineHeight, 'rem'),
+ overflow: 'visible',
+ userSelect: 'none',
+ whiteSpace: 'nowrap'
+ },
+ inner: {
+ alignItems: 'center',
+ color: theme.colors.deepGray,
+ display: 'flex',
+ flexDirection: 'row'
+ },
+ count: {
+ marginLeft: '0.25em'
+ },
+ retweetedColor: {
+ color: theme.colors.green
+ },
+ likedColor: {
+ color: theme.colors.red
+ }
+});
diff --git a/performance/components/TweetActionsBar/index.js b/performance/components/TweetActionsBar/index.js
new file mode 100644
index 00000000..7ca164d3
--- /dev/null
+++ b/performance/components/TweetActionsBar/index.js
@@ -0,0 +1,51 @@
+import TweetAction from '../TweetAction';
+import { View, StyleSheet } from 'react-native';
+import React, { PropTypes, PureComponent } from 'react';
+
+const actionNames = ['reply', 'retweet', 'like', 'directMessage'];
+
+export default class TweetActionsBar extends PureComponent {
+ static propTypes = {
+ actions: PropTypes.arrayOf(
+ PropTypes.shape({
+ count: PropTypes.number,
+ label: PropTypes.string,
+ highlighted: PropTypes.bool,
+ name: PropTypes.oneOf(actionNames).isRequired,
+ onPress: PropTypes.func
+ })
+ ),
+ style: PropTypes.object
+ };
+
+ render() {
+ const { actions, style } = this.props;
+
+ /* eslint-disable react/jsx-handler-names */
+ return (
+
+ {actions.map((action, i) => (
+
+ ))}
+
+ );
+ }
+}
+
+const styles = StyleSheet.create({
+ root: {
+ flexDirection: 'row'
+ },
+ action: {
+ display: 'block',
+ marginRight: '10%'
+ }
+});
diff --git a/performance/components/TweetText/index.js b/performance/components/TweetText/index.js
new file mode 100644
index 00000000..741c233a
--- /dev/null
+++ b/performance/components/TweetText/index.js
@@ -0,0 +1,28 @@
+import AppText from '../AppText';
+import TweetTextPart from '../TweetTextPart';
+import React, { PropTypes } from 'react';
+
+class TweetText extends React.Component {
+ static displayName = 'TweetText';
+
+ static propTypes = {
+ displayMode: TweetTextPart.propTypes.displayMode,
+ lang: PropTypes.string,
+ numberOfLines: PropTypes.number,
+ textParts: PropTypes.array.isRequired
+ };
+
+ render() {
+ const { displayMode, lang, numberOfLines, textParts, ...other } = this.props;
+
+ return (
+
+ {textParts.map((part, i) => (
+
+ ))}
+
+ );
+ }
+}
+
+export default TweetText;
diff --git a/performance/components/TweetTextPart/index.js b/performance/components/TweetTextPart/index.js
new file mode 100644
index 00000000..e12245d0
--- /dev/null
+++ b/performance/components/TweetTextPart/index.js
@@ -0,0 +1,112 @@
+/* eslint-disable react/prop-types */
+import { Image, StyleSheet, Text } from 'react-native';
+import React, { PropTypes } from 'react';
+import theme from '../theme';
+
+const createTextEntity = ({ part }) => {`${part.prefix}${part.text}`};
+
+const createTwemojiEntity = ({ part }) => (
+
+);
+
+// @mention, #hashtag, $cashtag
+const createSymbolEntity = ({ displayMode, part }) => {
+ const links = displayMode === 'links';
+ return (
+
+ {`${part.prefix}${part.text}`}
+
+ );
+};
+
+// internal links
+const createLinkEntity = ({ displayMode, part }) => {
+ const { displayUrl, linkRelation, url } = part;
+ const links = displayMode === 'links';
+
+ return (
+
+ {displayUrl}
+
+ );
+};
+
+// external links
+const createExternalLinkEntity = ({ displayMode, part }) => {
+ const { displayUrl, linkRelation, url } = part;
+ const links = displayMode === 'links';
+
+ return (
+
+ {displayUrl}
+
+ );
+};
+
+class TweetTextPart extends React.Component {
+ static displayName = 'TweetTextPart';
+
+ static propTypes = {
+ displayMode: PropTypes.oneOf(['links', 'no-links']),
+ part: PropTypes.object
+ };
+
+ static defaultProps = {
+ displayMode: 'links'
+ };
+
+ render() {
+ let renderer;
+ const { isEmoji, isEntity, isHashtag, isMention, isMedia, isUrl } = this.props.part;
+
+ if (isEmoji || isEntity || isUrl || isMedia) {
+ if (isUrl) {
+ renderer = createExternalLinkEntity;
+ } else if (isHashtag || isMention) {
+ renderer = createSymbolEntity;
+ } else if (isEmoji) {
+ renderer = createTwemojiEntity;
+ } else {
+ renderer = createLinkEntity;
+ }
+ } else {
+ renderer = createTextEntity;
+ }
+
+ return renderer(this.props);
+ }
+}
+
+const styles = StyleSheet.create({
+ link: {
+ color: theme.colors.blue,
+ textDecorationLine: 'none',
+ unicodeBidi: 'embed'
+ },
+ twemoji: {
+ display: 'inline-block',
+ height: '1.25em',
+ width: '1.25em',
+ paddingRight: '0.05em',
+ paddingLeft: '0.1em',
+ verticalAlign: '-0.2em'
+ }
+});
+
+export default TweetTextPart;
diff --git a/performance/components/UserAvatar/index.js b/performance/components/UserAvatar/index.js
new file mode 100644
index 00000000..d35c01a9
--- /dev/null
+++ b/performance/components/UserAvatar/index.js
@@ -0,0 +1,64 @@
+import AspectRatio from '../AspectRatio';
+import { Image, StyleSheet } from 'react-native';
+import React, { PropTypes, PureComponent } from 'react';
+import theme from '../theme';
+
+class UserAvatar extends PureComponent {
+ static displayName = 'UserAvatar';
+
+ static propTypes = {
+ accessibilityLabel: PropTypes.string,
+ circle: PropTypes.bool,
+ style: PropTypes.object,
+ uri: PropTypes.string
+ };
+
+ static defaultProps = {
+ circle: false
+ };
+
+ render() {
+ const { accessibilityLabel, circle, style, uri } = this.props;
+
+ return (
+
+ {uri
+ ?
+ : null}
+
+ );
+ }
+
+ _handleLoad = () => {
+ this._imageRef && this._imageRef.setNativeProps(nativeProps);
+ };
+
+ _setImageRef = component => {
+ this._imageRef = component;
+ };
+}
+
+const nativeProps = { style: { backgroundColor: '#fff' } };
+
+const styles = StyleSheet.create({
+ root: {
+ borderRadius: '0.35rem'
+ },
+ circle: {
+ borderRadius: '9999px'
+ },
+ image: {
+ backgroundColor: theme.colors.fadedGray,
+ display: 'block',
+ height: '100%',
+ width: '100%'
+ }
+});
+
+export default UserAvatar;
diff --git a/performance/components/UserNames/index.js b/performance/components/UserNames/index.js
new file mode 100644
index 00000000..cce85ac4
--- /dev/null
+++ b/performance/components/UserNames/index.js
@@ -0,0 +1,56 @@
+import AppText from '../AppText';
+import { StyleSheet } from 'react-native';
+import React, { PropTypes, PureComponent } from 'react';
+
+class UserNames extends PureComponent {
+ static displayName = 'UserNames';
+
+ static propTypes = {
+ fullName: PropTypes.string,
+ layout: PropTypes.oneOf(['nowrap', 'stack']),
+ onPress: PropTypes.func,
+ screenName: PropTypes.string,
+ style: PropTypes.object
+ };
+
+ static defaultProps = {
+ layout: 'nowrap'
+ };
+
+ render() {
+ const {
+ fullName,
+ layout,
+ onPress,
+ screenName,
+ style,
+ ...other
+ } = this.props;
+
+ return (
+
+ {fullName}
+ {layout === 'stack' ? ' \u000A' : ' '}
+ {`@${screenName}`}
+
+ );
+ }
+}
+
+const styles = StyleSheet.create({
+ root: {
+ display: 'inline-block'
+ },
+ screenName: {
+ unicodeBidi: 'embed',
+ writingDirection: 'ltr'
+ }
+});
+
+export default UserNames;
diff --git a/performance/implementations/css-modules/View/index.js b/performance/components/View/index.css-modules.js
similarity index 100%
rename from performance/implementations/css-modules/View/index.js
rename to performance/components/View/index.css-modules.js
diff --git a/performance/implementations/glamor/View/index.js b/performance/components/View/index.glamor.js
similarity index 100%
rename from performance/implementations/glamor/View/index.js
rename to performance/components/View/index.glamor.js
diff --git a/performance/implementations/platform/View/index.js b/performance/components/View/index.platform.js
similarity index 100%
rename from performance/implementations/platform/View/index.js
rename to performance/components/View/index.platform.js
diff --git a/performance/implementations/styled-components/View/index.js b/performance/components/View/index.styled.js
similarity index 100%
rename from performance/implementations/styled-components/View/index.js
rename to performance/components/View/index.styled.js
diff --git a/performance/implementations/css-modules/View/styles.css b/performance/components/View/styles.css
similarity index 100%
rename from performance/implementations/css-modules/View/styles.css
rename to performance/components/View/styles.css
diff --git a/performance/components/css-modules.js b/performance/components/css-modules.js
new file mode 100644
index 00000000..d2b2bcad
--- /dev/null
+++ b/performance/components/css-modules.js
@@ -0,0 +1,7 @@
+import Box from './Box/index.css-modules';
+import View from './View/index.css-modules';
+
+export default {
+ Box,
+ View
+};
diff --git a/performance/components/glamor.js b/performance/components/glamor.js
new file mode 100644
index 00000000..5bcb010b
--- /dev/null
+++ b/performance/components/glamor.js
@@ -0,0 +1,7 @@
+import Box from './Box/index.glamor';
+import View from './View/index.glamor';
+
+export default {
+ Box,
+ View
+};
diff --git a/performance/components/platform.js b/performance/components/platform.js
new file mode 100644
index 00000000..57cc08e0
--- /dev/null
+++ b/performance/components/platform.js
@@ -0,0 +1,7 @@
+import Box from './Box/index.platform';
+import View from './View/index.platform';
+
+export default {
+ Box,
+ View
+};
diff --git a/performance/components/react-native-web.js b/performance/components/react-native-web.js
new file mode 100644
index 00000000..fdafe21a
--- /dev/null
+++ b/performance/components/react-native-web.js
@@ -0,0 +1,9 @@
+import Box from './Box';
+import Tweet from './Tweet';
+import { View } from 'react-native';
+
+export default {
+ Box,
+ Tweet,
+ View
+};
diff --git a/performance/components/styled-components.js b/performance/components/styled-components.js
new file mode 100644
index 00000000..87dbebec
--- /dev/null
+++ b/performance/components/styled-components.js
@@ -0,0 +1,7 @@
+import Box from './Box/index.styled';
+import View from './View/index.styled';
+
+export default {
+ Box,
+ View
+};
diff --git a/performance/components/theme.js b/performance/components/theme.js
new file mode 100644
index 00000000..08ed60e3
--- /dev/null
+++ b/performance/components/theme.js
@@ -0,0 +1,37 @@
+const colors = {
+ blue: '#1B95E0',
+ lightBlue: '#71C9F8',
+ green: '#17BF63',
+ orange: '#F45D22',
+ purple: '#794BC4',
+ red: '#E0245E',
+ white: '#FFFFFF',
+ yellow: '#FFAD1F',
+ deepGray: '#657786',
+ fadedGray: '#E6ECF0',
+ faintGray: '#F5F8FA',
+ gray: '#AAB8C2',
+ lightGray: '#CCD6DD',
+ textBlack: '#14171A'
+};
+
+const fontSize = {
+ root: '14px',
+ // font scale
+ small: '0.85rem',
+ normal: '1rem',
+ large: '1.25rem'
+};
+
+module.exports = {
+ colors,
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, "Helvetica Neue", sans-serif, ' +
+ '"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"', // emoji fonts
+ fontSize,
+ lineHeight: 1.3125,
+ spaceX: 0.6,
+ spaceY: 1.3125,
+ createLength(num, unit) {
+ return `${num}${unit}`;
+ }
+};
diff --git a/performance/modules/createRenderBenchmark.js b/performance/createRenderBenchmark.js
similarity index 100%
rename from performance/modules/createRenderBenchmark.js
rename to performance/createRenderBenchmark.js
diff --git a/performance/implementations/css-modules/index.js b/performance/implementations/css-modules/index.js
deleted file mode 100644
index 0ca161ab..00000000
--- a/performance/implementations/css-modules/index.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import Box from './Box';
-import View from './View';
-
-export default {
- Box,
- View
-};
diff --git a/performance/implementations/glamor/index.js b/performance/implementations/glamor/index.js
deleted file mode 100644
index 0ca161ab..00000000
--- a/performance/implementations/glamor/index.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import Box from './Box';
-import View from './View';
-
-export default {
- Box,
- View
-};
diff --git a/performance/implementations/platform/index.js b/performance/implementations/platform/index.js
deleted file mode 100644
index 0ca161ab..00000000
--- a/performance/implementations/platform/index.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import Box from './Box';
-import View from './View';
-
-export default {
- Box,
- View
-};
diff --git a/performance/implementations/react-native-web/View/index.js b/performance/implementations/react-native-web/View/index.js
deleted file mode 100644
index a7c7f7dd..00000000
--- a/performance/implementations/react-native-web/View/index.js
+++ /dev/null
@@ -1,2 +0,0 @@
-import View from 'react-native/components/View';
-export default View;
diff --git a/performance/implementations/react-native-web/index.js b/performance/implementations/react-native-web/index.js
deleted file mode 100644
index 0ca161ab..00000000
--- a/performance/implementations/react-native-web/index.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import Box from './Box';
-import View from './View';
-
-export default {
- Box,
- View
-};
diff --git a/performance/implementations/styled-components/index.js b/performance/implementations/styled-components/index.js
deleted file mode 100644
index 0ca161ab..00000000
--- a/performance/implementations/styled-components/index.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import Box from './Box';
-import View from './View';
-
-export default {
- Box,
- View
-};
diff --git a/performance/index.js b/performance/index.js
index 5eb7f99f..9a55d141 100644
--- a/performance/index.js
+++ b/performance/index.js
@@ -1,13 +1,16 @@
-import cssModules from './implementations/css-modules';
-import glamor from './implementations/glamor';
-import platform from './implementations/platform';
-import reactNative from './implementations/react-native-web';
-import styledComponents from './implementations/styled-components';
+import cssModules from './components/css-modules';
+import glamor from './components/glamor';
+import platform from './components/platform';
+import reactNative from './components/react-native-web';
+import styledComponents from './components/styled-components';
import renderDeepTree from './tests/renderDeepTree';
+import renderTweet from './tests/renderTweet';
import renderWideTree from './tests/renderWideTree';
const tests = [
+ // tweet
+ () => renderTweet('react-native-web', reactNative),
// deep tree
() => renderDeepTree('platform', platform),
() => renderDeepTree('css-modules', cssModules),
diff --git a/performance/tests/renderDeepTree.js b/performance/tests/renderDeepTree.js
index 6deecfe5..dfd51031 100644
--- a/performance/tests/renderDeepTree.js
+++ b/performance/tests/renderDeepTree.js
@@ -1,5 +1,5 @@
-import createRenderBenchmark from '../modules/createRenderBenchmark';
-import NestedTree from '../modules/NestedTree';
+import createRenderBenchmark from '../createRenderBenchmark';
+import NestedTree from '../components/NestedTree';
import React from 'react';
const renderDeepTree = (label, components) => createRenderBenchmark({
diff --git a/performance/tests/renderTweet.js b/performance/tests/renderTweet.js
new file mode 100644
index 00000000..2e0086f9
--- /dev/null
+++ b/performance/tests/renderTweet.js
@@ -0,0 +1,112 @@
+import createRenderBenchmark from '../createRenderBenchmark';
+import Tweet from '../components/Tweet';
+import React from 'react';
+
+const tweet1 = {
+ favorite_count: 30,
+ favorited: true,
+ id: '834889712556875776',
+ lang: 'en',
+ retweet_count: 6,
+ retweeted: false,
+ textParts: [
+ {
+ prefix: '',
+ text: 'Living burrito to burrito '
+ },
+ {
+ emoji: 'https://abs-0.twimg.com/emoji/v2/svg/1f32f.svg',
+ isEmoji: true,
+ prefix: '',
+ text: '🌯'
+ },
+ {
+ emoji: 'https://abs-0.twimg.com/emoji/v2/svg/1f32f.svg',
+ isEmoji: true,
+ prefix: '',
+ text: '🌯'
+ },
+ {
+ emoji: 'https://abs-0.twimg.com/emoji/v2/svg/1f32f.svg',
+ isEmoji: true,
+ prefix: '',
+ text: '🌯'
+ }
+ ],
+ timestamp: 'Feb 23',
+ user: {
+ fullName: 'Nicolas',
+ screenName: 'necolas',
+ profileImageUrl: 'https://pbs.twimg.com/profile_images/804365942360719360/dQnPejph_normal.jpg'
+ }
+};
+
+const tweet2 = {
+ favorite_count: 84,
+ favorited: false,
+ id: '730896800060579840',
+ lang: 'en',
+ media: {
+ source: {
+ uri: 'https://pbs.twimg.com/media/CiSqvsJVEAAtLZ1.jpg',
+ width: 600,
+ height: 338
+ }
+ },
+ retweet_count: 4,
+ retweeted: true,
+ textParts: [
+ {
+ prefix: '',
+ text: 'Presenting '
+ },
+ {
+ displayUrl: 'mobile.twitter.com',
+ expandedUrl: 'https://mobile.twitter.com',
+ isEntity: true,
+ isUrl: true,
+ linkRelation: 'nofollow',
+ prefix: '',
+ text: '',
+ textDirection: 'ltr',
+ url: 'https://t.co/4hRCAxiUUG'
+ },
+ {
+ prefix: '',
+ text: ' with '
+ },
+ {
+ isEntity: true,
+ isMention: true,
+ prefix: '@',
+ text: 'davidbellona',
+ textDirection: 'ltr',
+ url: '/davidbellona'
+ },
+ {
+ prefix: '',
+ text: " at Twitter's all hands meeting "
+ }
+ ],
+ timestamp: 'May 12',
+ user: {
+ fullName: 'Nicolas',
+ screenName: 'necolas',
+ profileImageUrl: 'https://pbs.twimg.com/profile_images/804365942360719360/dQnPejph_normal.jpg'
+ }
+};
+
+const renderTweet = (label, components) => createRenderBenchmark({
+ name: `Tweet [${label}]`,
+ runs: 10,
+ getElement() {
+ return (
+
+
+
+
+ );
+ }
+});
+
+export default renderTweet;
diff --git a/performance/tests/renderWideTree.js b/performance/tests/renderWideTree.js
index 731256a7..3c6dcf84 100644
--- a/performance/tests/renderWideTree.js
+++ b/performance/tests/renderWideTree.js
@@ -1,5 +1,5 @@
-import createRenderBenchmark from '../modules/createRenderBenchmark';
-import NestedTree from '../modules/NestedTree';
+import createRenderBenchmark from '../createRenderBenchmark';
+import NestedTree from '../components/NestedTree';
import React from 'react';
const renderWideTree = (label, components) => createRenderBenchmark({