Skip to content

Commit

Permalink
[change] StyleSheet performance rewrite
Browse files Browse the repository at this point in the history
Improves StyleSheet benchmark performance by 13x. Removes undocumented
`StyleSheet.resolve` API.

Typical mean (and first) task duration.

[benchmark] DeepTree: depth=3, breadth=10, wrap=4)
  -master  2809.76ms (3117.64ms)
  -patch     211.2ms  (364.28ms)

[benchmark] DeepTree: depth=5, breadth=3, wrap=1)
  -master   421.25ms (428.15ms)
  -patch     32.46ms  (47.36ms)

This patch adds memoization of DOM prop resolution (~3-4x faster), and
re-introduces a `className`-based styling strategy (~3-4x faster).
Styles map to "atomic css" rules.

Fix #307
  • Loading branch information
necolas committed Jan 2, 2017
1 parent a2cafe5 commit d87f71e
Show file tree
Hide file tree
Showing 41 changed files with 2,358 additions and 2,290 deletions.
4 changes: 2 additions & 2 deletions docs/apis/StyleSheet.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ Each key of the object passed to `create` must define a style object.

Flattens an array of styles into a single style object.

**render**: function
**renderToString**: function

Returns a React `<style>` element for use in server-side rendering.
Returns a string of the stylesheet for use in server-side rendering.

## Properties

Expand Down
5 changes: 4 additions & 1 deletion examples/.storybook/webpack.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
const path = require('path')
const webpack = require('webpack')

const DEV = process.env.NODE_ENV !== 'production';

module.exports = {
module: {
loaders: [
Expand All @@ -19,7 +21,8 @@ module.exports = {
},
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
'process.env.__REACT_NATIVE_DEBUG_ENABLED__': DEV
}),
// https://github.com/animatedjs/animated/issues/40
new webpack.NormalModuleReplacementPlugin(
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"dependencies": {
"animated": "^0.1.3",
"array-find-index": "^1.0.2",
"asap": "^2.0.5",
"babel-runtime": "^6.11.6",
"debounce": "^1.0.0",
"deep-assign": "^2.0.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
exports[`apis/AppRegistry/renderApplication getApplication 1`] = `
"<style id=\"react-native-stylesheet\">
html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}
body{margin:0}
button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}
input::-webkit-inner-spin-button,input::-webkit-outer-spin-button,input::-webkit-search-cancel-button,input::-webkit-search-decoration,input::-webkit-search-results-button,input::-webkit-search-results-decoration{display:none}
.rn_pointerEvents\\:auto, .rn_pointerEvents\\:box-only, .rn_pointerEvents\\:box-none * {pointer-events:auto}.rn_pointerEvents\\:none, .rn_pointerEvents\\:box-only *, .rn_pointerEvents\\:box-none {pointer-events:none}
.rn-bottom\\:0px{bottom:0px;}
.rn-left\\:0px{left:0px;}
.rn-position\\:absolute{position:absolute;}
.rn-right\\:0px{right:0px;}
.rn-top\\:0px{top:0px;}
.rn-alignItems\\:stretch{-webkit-align-items:stretch;-ms-flex-align:stretch;-webkit-box-align:stretch;align-items:stretch;}
.rn-backgroundColor\\:transparent{background-color:transparent;}
.rn-borderTopStyle\\:solid{border-top-style:solid;}
.rn-borderRightStyle\\:solid{border-right-style:solid;}
.rn-borderBottomStyle\\:solid{border-bottom-style:solid;}
.rn-borderLeftStyle\\:solid{border-left-style:solid;}
.rn-borderTopWidth\\:0px{border-top-width:0px;}
.rn-borderRightWidth\\:0px{border-right-width:0px;}
.rn-borderBottomWidth\\:0px{border-bottom-width:0px;}
.rn-borderLeftWidth\\:0px{border-left-width:0px;}
.rn-boxSizing\\:border-box{-moz-box-sizing:border-box;box-sizing:border-box;}
.rn-color\\:inherit{color:inherit;}
.rn-display\\:flex{display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;}
.rn-flexBasis\\:auto{-webkit-flex-basis:auto;-ms-preferred-size:auto;flex-basis:auto;}
.rn-flexDirection\\:column{-webkit-flex-direction:column;-ms-flex-direction:column;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-direction:column;}
.rn-font\\:inherit{font:inherit;}
.rn-listStyle\\:none{list-style:none;}
.rn-marginTop\\:0px{margin-top:0px;}
.rn-marginRight\\:0px{margin-right:0px;}
.rn-marginBottom\\:0px{margin-bottom:0px;}
.rn-marginLeft\\:0px{margin-left:0px;}
.rn-minHeight\\:0px{min-height:0px;}
.rn-minWidth\\:0px{min-width:0px;}
.rn-paddingTop\\:0px{padding-top:0px;}
.rn-paddingRight\\:0px{padding-right:0px;}
.rn-paddingBottom\\:0px{padding-bottom:0px;}
.rn-paddingLeft\\:0px{padding-left:0px;}
.rn-position\\:relative{position:relative;}
.rn-textAlign\\:inherit{text-align:inherit;}
.rn-textDecoration\\:none{text-decoration:none;}
.rn-flexShrink\\:0{-webkit-flex-shrink:0px;-ms-flex-negative:0px;flex-shrink:0;}
</style>"
`;
2 changes: 1 addition & 1 deletion src/apis/AppRegistry/__tests__/renderApplication-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ describe('apis/AppRegistry/renderApplication', () => {
const { element, stylesheet } = getApplication(component, {});

expect(element).toBeTruthy();
expect(stylesheet.type).toEqual('style');
expect(stylesheet).toMatchSnapshot();
});
});
2 changes: 1 addition & 1 deletion src/apis/AppRegistry/renderApplication.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,6 @@ export function getApplication(RootComponent: Component, initialProps: Object):
rootComponent={RootComponent}
/>
);
const stylesheet = StyleSheet.render();
const stylesheet = StyleSheet.renderToString();
return { element, stylesheet };
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
exports[`apis/StyleSheet/createReactDOMStyle converts ReactNative style to ReactDOM style 1`] = `
Object {
"borderBottomWidth": "1px",
"borderLeftWidth": "1px",
"borderRightWidth": "1px",
"borderTopWidth": "1px",
"borderWidthLeft": "2px",
"borderWidthRight": "3px",
"boxShadow": "1px 1px 1px 1px #000, 1px 2px 0px rgba(255,0,0,1)",
"display": "flex",
"marginBottom": "0px",
"marginTop": "0px",
"opacity": 0,
}
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
exports[`apis/StyleSheet/generateCss generates correct css 1`] = `"-webkit-transition-duration:0.1s;transition-duration:0.1s;position:absolute;border-width-right:3px;border-width-left:2px;box-shadow:1px 1px 1px 1px #000;"`;
22 changes: 13 additions & 9 deletions src/apis/StyleSheet/__tests__/__snapshots__/index-test.js.snap
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
exports[`apis/StyleSheet resolve 1`] = `
Object {
"className": "test __style_df __style_pebn",
"style": Object {
"display": null,
"opacity": 1,
"pointerEvents": null,
},
}
exports[`apis/StyleSheet renderToString 1`] = `
"<style id=\"react-native-stylesheet\">
.rn-borderTopColor\\:red{border-top-color:red;}
.rn-borderRightColor\\:red{border-right-color:red;}
.rn-borderBottomColor\\:red{border-bottom-color:red;}
.rn-borderLeftColor\\:red{border-left-color:red;}
.rn-borderTopWidth\\:0px{border-top-width:0px;}
.rn-borderRightWidth\\:0px{border-right-width:0px;}
.rn-borderBottomWidth\\:0px{border-bottom-width:0px;}
.rn-borderLeftWidth\\:0px{border-left-width:0px;}
.rn-left\\:50px{left:50px;}
.rn-position\\:absolute{position:absolute;}
</style>"
`;
179 changes: 179 additions & 0 deletions src/apis/StyleSheet/__tests__/__snapshots__/registry-test.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
exports[`apis/StyleSheet/registry resolve with stylesheet, resolves to className 1`] = `
Object {
"className": "
rn-borderTopColor:red
rn-borderRightColor:red
rn-borderBottomColor:red
rn-borderLeftColor:red
rn-borderTopWidth:0px
rn-borderRightWidth:0px
rn-borderBottomWidth:0px
rn-borderLeftWidth:0px
rn-left:50px
rn-pointerEvents:box-only
rn-position:absolute
rn-width:100px",
"style": Object {},
}
`;

exports[`apis/StyleSheet/registry resolve with stylesheet, resolves to className 2`] = `
Object {
"className": "
rn-borderTopColor:red
rn-borderRightColor:red
rn-borderBottomColor:red
rn-borderLeftColor:red
rn-borderTopWidth:0px
rn-borderRightWidth:0px
rn-borderBottomWidth:0px
rn-borderLeftWidth:0px
rn-left:50px
rn-pointerEvents:box-only
rn-position:absolute
rn-width:200px",
"style": Object {},
}
`;

exports[`apis/StyleSheet/registry resolve with stylesheet, resolves to className 3`] = `
Object {
"className": "
rn-borderTopColor:red
rn-borderRightColor:red
rn-borderBottomColor:red
rn-borderLeftColor:red
rn-borderTopWidth:0px
rn-borderRightWidth:0px
rn-borderBottomWidth:0px
rn-borderLeftWidth:0px
rn-left:50px
rn-pointerEvents:box-only
rn-position:absolute
rn-width:100px",
"style": Object {},
}
`;

exports[`apis/StyleSheet/registry resolve with stylesheet, resolves to mixed 1`] = `
Object {
"className": "
rn-left:50px
rn-pointerEvents:box-only
rn-position:absolute",
"style": Object {
"borderBottomColor": "red",
"borderBottomWidth": "0px",
"borderLeftColor": "red",
"borderLeftWidth": "0px",
"borderRightColor": "red",
"borderRightWidth": "0px",
"borderTopColor": "red",
"borderTopWidth": "0px",
"width": "100px",
},
}
`;

exports[`apis/StyleSheet/registry resolve with stylesheet, resolves to mixed 2`] = `
Object {
"className": "
rn-left:50px
rn-pointerEvents:box-only
rn-position:absolute
rn-width:200px",
"style": Object {
"borderBottomColor": "red",
"borderBottomWidth": "0px",
"borderLeftColor": "red",
"borderLeftWidth": "0px",
"borderRightColor": "red",
"borderRightWidth": "0px",
"borderTopColor": "red",
"borderTopWidth": "0px",
},
}
`;

exports[`apis/StyleSheet/registry resolve with stylesheet, resolves to mixed 3`] = `
Object {
"className": "
rn-left:50px
rn-pointerEvents:box-only
rn-position:absolute",
"style": Object {
"borderBottomColor": "red",
"borderBottomWidth": "0px",
"borderLeftColor": "red",
"borderLeftWidth": "0px",
"borderRightColor": "red",
"borderRightWidth": "0px",
"borderTopColor": "red",
"borderTopWidth": "0px",
"width": "100px",
},
}
`;

exports[`apis/StyleSheet/registry resolve without stylesheet, resolves to inline styles 1`] = `
Object {
"className": "
",
"style": Object {
"borderBottomColor": "red",
"borderBottomWidth": "0px",
"borderLeftColor": "red",
"borderLeftWidth": "0px",
"borderRightColor": "red",
"borderRightWidth": "0px",
"borderTopColor": "red",
"borderTopWidth": "0px",
"left": "50px",
"pointerEvents": "box-only",
"position": "absolute",
"width": "100px",
},
}
`;

exports[`apis/StyleSheet/registry resolve without stylesheet, resolves to inline styles 2`] = `
Object {
"className": "
",
"style": Object {
"borderBottomColor": "red",
"borderBottomWidth": "0px",
"borderLeftColor": "red",
"borderLeftWidth": "0px",
"borderRightColor": "red",
"borderRightWidth": "0px",
"borderTopColor": "red",
"borderTopWidth": "0px",
"left": "50px",
"pointerEvents": "box-only",
"position": "absolute",
"width": "200px",
},
}
`;

exports[`apis/StyleSheet/registry resolve without stylesheet, resolves to inline styles 3`] = `
Object {
"className": "
",
"style": Object {
"borderBottomColor": "red",
"borderBottomWidth": "0px",
"borderLeftColor": "red",
"borderLeftWidth": "0px",
"borderRightColor": "red",
"borderRightWidth": "0px",
"borderTopColor": "red",
"borderTopWidth": "0px",
"left": "50px",
"pointerEvents": "box-only",
"position": "absolute",
"width": "100px",
},
}
`;
22 changes: 19 additions & 3 deletions src/apis/StyleSheet/__tests__/createReactDOMStyle-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,27 @@

import createReactDOMStyle from '../createReactDOMStyle';

const reactNativeStyle = {
boxShadow: '1px 1px 1px 1px #000',
borderWidthLeft: 2,
borderWidth: 1,
borderWidthRight: 3,
display: 'flex',
marginVertical: 0,
opacity: 0,
shadowColor: 'red',
shadowOffset: { width: 1, height: 2 },
resizeMode: 'contain'
};

describe('apis/StyleSheet/createReactDOMStyle', () => {
test('converts ReactNative style to ReactDOM style', () => {
const reactNativeStyle = { display: 'flex', marginVertical: 0, opacity: 0 };
const expectedStyle = { display: 'flex', marginTop: '0px', marginBottom: '0px', opacity: 0 };
expect(createReactDOMStyle(reactNativeStyle)).toMatchSnapshot();
});

expect(createReactDOMStyle(reactNativeStyle)).toEqual(expectedStyle);
test('noop on DOM styles', () => {
const firstStyle = createReactDOMStyle(reactNativeStyle);
const secondStyle = createReactDOMStyle(firstStyle);
expect(firstStyle).toEqual(secondStyle);
});
});
16 changes: 16 additions & 0 deletions src/apis/StyleSheet/__tests__/generateCss-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/* eslint-env jasmine, jest */

import generateCss from '../generateCss';

describe('apis/StyleSheet/generateCss', () => {
test('generates correct css', () => {
const style = {
boxShadow: '1px 1px 1px 1px #000',
borderWidthLeft: 2,
borderWidthRight: 3,
position: 'absolute',
transitionDuration: '0.1s'
};
expect(generateCss(style)).toMatchSnapshot();
});
});
Loading

0 comments on commit d87f71e

Please sign in to comment.