Skip to content

Commit

Permalink
[core] Benchmark Material-UI (#13233)
Browse files Browse the repository at this point in the history
  • Loading branch information
oliviertassinari committed Oct 13, 2018
1 parent 77da649 commit 7d6ac7f
Show file tree
Hide file tree
Showing 13 changed files with 283 additions and 38 deletions.
2 changes: 1 addition & 1 deletion .size-limit.js
Expand Up @@ -22,7 +22,7 @@ module.exports = [
name: 'The size of all the material-ui modules.',
webpack: true,
path: 'packages/material-ui/build/index.js',
limit: '92.7 KB',
limit: '93.2 KB',
},
{
name: 'The main docs bundle',
Expand Down
14 changes: 14 additions & 0 deletions packages/material-ui-benchmark/README.md
@@ -0,0 +1,14 @@
# Material-UI Benchmarking

```sh
yarn benchmark

ButtonBase x 78,875 ops/sec ±11.38% (80 runs sampled)
Grid x 84,284 ops/sec ±2.36% (87 runs sampled)
JssButton x 253,054 ops/sec ±4.50% (83 runs sampled)
StyledComponents x 110,824 ops/sec ±0.79% (92 runs sampled)
Emotion x 119,230 ops/sec ±1.81% (89 runs sampled)
Button x 27,616 ops/sec ±3.85% (85 runs sampled)
ButtonBase disableRipple x 85,133 ops/sec ±2.25% (84 runs sampled)
button x 993,210 ops/sec ±4.82% (81 runs sampled)
```
29 changes: 29 additions & 0 deletions packages/material-ui-benchmark/package.json
@@ -0,0 +1,29 @@
{
"name": "@material-ui/benchmark",
"private": true,
"version": "3.0.0",
"description": "Material-UI Benchmark.",
"repository": {
"type": "git",
"url": "https://github.com/mui-org/material-ui.git"
},
"bugs": {
"url": "https://github.com/mui-org/material-ui/issues"
},
"homepage": "https://github.com/mui-org/material-ui/tree/master/packages/material-ui-benchmark",
"scripts": {
"benchmark": "cd ../../ && NODE_ENV=production BABEL_ENV=docs-production babel-node packages/material-ui-benchmark/src/benchmark.js"
},
"devDependencies": {},
"engines": {
"node": ">=6.0.0"
},
"license": "MIT",
"dependencies": {
"benchmark": "^2.1.4",
"emotion": "^9.2.12",
"nodemod": "^1.5.19",
"react-emotion": "^9.2.12",
"styled-components": "^3.4.10"
}
}
98 changes: 98 additions & 0 deletions packages/material-ui-benchmark/src/benchmark.js
@@ -0,0 +1,98 @@
/* eslint-disable no-console, no-underscore-dangle */

import Benchmark from 'benchmark';
import React from 'react';
import styled from 'styled-components';
import ReactDOMServer from 'react-dom/server';
import styled2 from 'react-emotion';
import Button from '@material-ui/core/Button';
import Grid from '@material-ui/core/Grid';
import { withStyles } from '@material-ui/core/styles';
import ButtonBase from '@material-ui/core/ButtonBase';

const suite = new Benchmark.Suite({
async: true,
minSamples: 100,
minTime: 5,
});

global.__MUI_USE_NEXT_TYPOGRAPHY_VARIANTS__ = true;

function CustomButton() {
return <button type="button">Material-UI</button>;
}

const JssButton = withStyles({
root: {
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
position: 'relative',
// Remove grey highlight
WebkitTapHighlightColor: 'transparent',
backgroundColor: 'transparent', // Reset default value
// We disable the focus ring for mouse, touch and keyboard users.
outline: 'none',
border: 0,
margin: 0, // Remove the margin in Safari
borderRadius: 0,
padding: 0, // Remove the padding in Firefox
cursor: 'pointer',
userSelect: 'none',
verticalAlign: 'middle',
'-moz-appearance': 'none', // Reset
'-webkit-appearance': 'none', // Reset
textDecoration: 'none',
// So we take precedent over the style of a native <a /> element.
color: 'inherit',
'&::-moz-focus-inner': {
borderStyle: 'none', // Remove Firefox dotted outline.
},
'&$disabled': {
pointerEvents: 'none', // Disable link interactions
cursor: 'default',
},
},
})(({ classes, ...other }) => <button type="button" {...other} />);

const StyledComponents = styled.button`
font-size: 1.5em;
text-align: center;
color: palevioletred;
`;

const Emotion = styled2('button')`
font-size: 1.5em;
text-align: center;
color: palevioletred;
`;

suite
.add('ButtonBase', () => {
ReactDOMServer.renderToString(<ButtonBase>Material-UI</ButtonBase>);
})
.add('Grid', () => {
ReactDOMServer.renderToString(<Grid>Material-UI</Grid>);
})
.add('JssButton', () => {
ReactDOMServer.renderToString(<JssButton>Material-UI</JssButton>);
})
.add('StyledComponents', () => {
ReactDOMServer.renderToString(<StyledComponents>Material-UI</StyledComponents>);
})
.add('Emotion', () => {
ReactDOMServer.renderToString(<Emotion>Material-UI</Emotion>);
})
.add('Button', () => {
ReactDOMServer.renderToString(<Button>Material-UI</Button>);
})
.add('ButtonBase disableRipple', () => {
ReactDOMServer.renderToString(<ButtonBase disableRipple>Material-UI</ButtonBase>);
})
.add('button', () => {
ReactDOMServer.renderToString(<CustomButton />);
})
.on('cycle', event => {
console.log(String(event.target));
})
.run();
2 changes: 1 addition & 1 deletion packages/material-ui/src/Button/Button.js
Expand Up @@ -218,8 +218,8 @@ function Button(props) {
color,
disabled,
disableFocusRipple,
fullWidth,
focusVisibleClassName,
fullWidth,
mini,
size,
variant,
Expand Down
13 changes: 8 additions & 5 deletions packages/material-ui/src/ButtonBase/ButtonBase.js
Expand Up @@ -5,6 +5,7 @@ import classNames from 'classnames';
import keycode from 'keycode';
import ownerWindow from '../utils/ownerWindow';
import withStyles from '../styles/withStyles';
import NoSsr from '../NoSsr';
import { listenForFocusKeys, detectFocusVisible } from './focusVisible';
import TouchRipple from './TouchRipple';
import createRippleHandler from './createRippleHandler';
Expand Down Expand Up @@ -269,14 +270,13 @@ class ButtonBase extends React.Component {
classNameProp,
);

const buttonProps = {};

let ComponentProp = component;

if (ComponentProp === 'button' && other.href) {
ComponentProp = 'a';
}

const buttonProps = {};
if (ComponentProp === 'button') {
buttonProps.type = type || 'button';
buttonProps.disabled = disabled;
Expand All @@ -286,6 +286,7 @@ class ButtonBase extends React.Component {

return (
<ComponentProp
className={className}
onBlur={this.handleBlur}
onFocus={this.handleFocus}
onKeyDown={this.handleKeyDown}
Expand All @@ -296,15 +297,17 @@ class ButtonBase extends React.Component {
onTouchEnd={this.handleTouchEnd}
onTouchMove={this.handleTouchMove}
onTouchStart={this.handleTouchStart}
tabIndex={disabled ? '-1' : tabIndex}
className={className}
ref={buttonRef}
tabIndex={disabled ? '-1' : tabIndex}
{...buttonProps}
{...other}
>
{children}
{!disableRipple && !disabled ? (
<TouchRipple innerRef={this.onRippleRef} center={centerRipple} {...TouchRippleProps} />
<NoSsr>
{/* TouchRipple is only needed client side, x2 boost on the server. */}
<TouchRipple innerRef={this.onRippleRef} center={centerRipple} {...TouchRippleProps} />
</NoSsr>
) : null}
</ComponentProp>
);
Expand Down
5 changes: 4 additions & 1 deletion packages/material-ui/src/ButtonBase/ButtonBase.test.js
Expand Up @@ -633,7 +633,10 @@ describe('<ButtonBase />', () => {
wrapper.setProps({
children: 'bar',
});
assert.strictEqual(rerender.updates.length, 1);
assert.strictEqual(
rerender.updates.filter(update => update.displayName !== 'NoSsr').length,
1,
);
});
});
});
47 changes: 26 additions & 21 deletions packages/material-ui/src/ButtonBase/createRippleHandler.js
@@ -1,30 +1,35 @@
function createRippleHandler(instance, eventName, action, cb) {
return function handleEvent(event) {
if (cb) {
cb.call(instance, event);
}
/* eslint-disable import/no-mutable-exports */

let ignore = false;
let createRippleHandler = (instance, eventName, action, cb) => event => {
if (cb) {
cb.call(instance, event);
}

// Ignore events that have been `event.preventDefault()` marked.
if (event.defaultPrevented) {
ignore = true;
}
let ignore = false;

if (instance.props.disableTouchRipple && eventName !== 'Blur') {
ignore = true;
}
// Ignore events that have been `event.preventDefault()` marked.
if (event.defaultPrevented) {
ignore = true;
}

if (!ignore && instance.ripple) {
instance.ripple[action](event);
}
if (instance.props.disableTouchRipple && eventName !== 'Blur') {
ignore = true;
}

if (typeof instance.props[`on${eventName}`] === 'function') {
instance.props[`on${eventName}`](event);
}
if (!ignore && instance.ripple) {
instance.ripple[action](event);
}

return true;
};
if (typeof instance.props[`on${eventName}`] === 'function') {
instance.props[`on${eventName}`](event);
}

return true;
};

/* istanbul ignore if */
if (!process.browser) {
createRippleHandler = () => () => {};
}

export default createRippleHandler;
2 changes: 1 addition & 1 deletion packages/material-ui/src/NoSsr/NoSsr.js
Expand Up @@ -22,7 +22,7 @@ class NoSsr extends React.Component {
this.mounted = true;

if (this.props.defer) {
// Wondering why we use two raf? Check this video out:
// Wondering why we use two RAFs? Check this video out:
// https://www.youtube.com/watch?v=cCOL7MC4Pl0
requestAnimationFrame(() => {
// The browser should be about to render the DOM that React commited at this point.
Expand Down
8 changes: 4 additions & 4 deletions packages/material-ui/src/styles/MuiThemeProvider.test.js
Expand Up @@ -82,19 +82,19 @@ describe('<MuiThemeProvider />', () => {
);

assert.notStrictEqual(markup.match('Hello World'), null);
assert.strictEqual(sheetsRegistry.registry.length, 3);
assert.strictEqual(sheetsRegistry.registry.length, 2);
assert.strictEqual(sheetsRegistry.toString().length > 4000, true);
assert.strictEqual(sheetsRegistry.registry[0].classes.root, 'MuiTouchRipple-root-30');
assert.strictEqual(sheetsRegistry.registry[0].classes.root, 'MuiButtonBase-root-27');
assert.deepEqual(
sheetsRegistry.registry[1].classes,
sheetsRegistry.registry[0].classes,
{
disabled: 'MuiButtonBase-disabled-28',
focusVisible: 'MuiButtonBase-focusVisible-29',
root: 'MuiButtonBase-root-27',
},
'the class names should be deterministic',
);
assert.strictEqual(sheetsRegistry.registry[2].classes.root, 'MuiButton-root-1');
assert.strictEqual(sheetsRegistry.registry[1].classes.root, 'MuiButton-root-1');
});
});

Expand Down
4 changes: 3 additions & 1 deletion test/utils/rerender.js
@@ -1,9 +1,11 @@
/* eslint-disable no-underscore-dangle */

import React from 'react';
import getDisplayName from '../../packages/material-ui/src/utils/getDisplayName';

function createComponentDidUpdate(instance) {
return function componentDidUpdate() {
const displayName = getDisplayName(this);
const displayName = getDisplayName(this._reactInternalFiber.type);
instance.updates.push({
displayName,
});
Expand Down
2 changes: 2 additions & 0 deletions test/utils/setup.js
@@ -1,5 +1,7 @@
const createDOM = require('./createDOM');

process.browser = true;

// eslint-disable-next-line no-underscore-dangle
global.__MUI_USE_NEXT_TYPOGRAPHY_VARIANTS__ = true;
createDOM();
Expand Down

0 comments on commit 7d6ac7f

Please sign in to comment.