Skip to content

Commit

Permalink
Merge pull request #3009 from oliviertassinari/server-side-rendering
Browse files Browse the repository at this point in the history
[Style] Address server sider rendering issues
  • Loading branch information
oliviertassinari committed Jan 22, 2016
2 parents c523100 + a10b76b commit f1d4b97
Show file tree
Hide file tree
Showing 22 changed files with 198 additions and 73 deletions.
2 changes: 2 additions & 0 deletions docs/src/app/app-routes.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import Prerequisites from './components/pages/get-started/Prerequisites';
import Installation from './components/pages/get-started/Installation';
import Usage from './components/pages/get-started/Usage';
import Examples from './components/pages/get-started/Examples';
import ServerRendering from './components/pages/get-started/ServerRendering';

import Colors from './components/pages/customization/colors';
import Themes from './components/pages/customization/themes';
Expand Down Expand Up @@ -77,6 +78,7 @@ const AppRoutes = (
<Route path="installation" component={Installation} />
<Route path="usage" component={Usage} />
<Route path="examples" component={Examples} />
<Route path="server-rendering" component={ServerRendering} />
</Route>
<Redirect from="customization" to="/customization/themes" />
<Route path="customization">
Expand Down
1 change: 1 addition & 0 deletions docs/src/app/components/app-left-nav.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ const AppLeftNav = React.createClass({
<ListItem primaryText="Installation" value="/get-started/installation" />,
<ListItem primaryText="Usage" value="/get-started/usage" />,
<ListItem primaryText="Examples" value="/get-started/examples" />,
<ListItem primaryText="Server Rendering" value="/get-started/server-rendering" />,
]}
/>
<ListItem
Expand Down
9 changes: 9 additions & 0 deletions docs/src/app/components/pages/get-started/ServerRendering.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from 'react';
import MarkdownElement from '../../MarkdownElement';
import serverRenderingText from './serverRendering.md';

const ServerRendering = () => (
<MarkdownElement text={serverRenderingText} />
);

export default ServerRendering;
55 changes: 55 additions & 0 deletions docs/src/app/components/pages/get-started/serverRendering.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
## Server Rendering

When using Material UI with server rendering, we must use the same environment for the server and the client.
This has two technical implications.

### Autoprefixer

First, Material UI has to use the same user agent for the auto prefixer.
On the client side, the default value is `navigator.userAgent`.
But on the server side, the `navigator` is `undefined`. You need to provide it to Material UI.

The `userAgent` can take one of the following values:
- a regular user agent like
`Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.82 Safari/537.36`
- `'all'` to prefix for all user agents
- `false` to disable the prefixer

We rely on the [muiTheme](/#/customization/themes) context to spread the user agent to all of our component.
For instance, you can provide it like this:

```js
import getMuiTheme from 'material-ui/lib/styles/getMuiTheme';
import themeDecorator from 'material-ui/lib/styles/theme-decorator';
import colors from 'material-ui/lib/styles/colors';

const muiTheme = getMuiTheme({
palette: {
primary1Color: colors.green500,
primary2Color: colors.green700,
primary3Color: colors.green100,
},
}, {
avatar: {
borderColor: null,
},
userAgent: req.headers['user-agent'],
});

class Main extends React.Component {
render() {
return (
<div>Hello world</div>
);
}
}

export default themeDecorator(muiTheme)(Main)
```

### process.env.NODE_ENV

You also need to use the same `process.env.NODE_ENV` value for the client side and server side.
Otherwise, the checksums won't match.
In order to make sure our style transformations are only applied once,
we add an additional property to each style when `process.env.NODE_ENV !== 'production'`.
2 changes: 1 addition & 1 deletion src/auto-complete.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,7 @@ const AutoComplete = React.createClass({
useLayerForClickAway={false}
onRequestClose={this._close}
>
{menu}
{menu}
</Popover>
</div>
);
Expand Down
10 changes: 7 additions & 3 deletions src/before-after-wrapper.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from 'react';
import StylePropable from './mixins/style-propable';
import AutoPrefix from './styles/auto-prefix';
import DefaultRawTheme from './styles/raw-themes/light-raw-theme';
import ThemeManager from './styles/theme-manager';

Expand Down Expand Up @@ -105,8 +104,13 @@ const BeforeAfterWrapper = React.createClass({
let beforeElement;
let afterElement;

beforeStyle = AutoPrefix.all({boxSizing: 'border-box'});
afterStyle = AutoPrefix.all({boxSizing: 'border-box'});
beforeStyle = {
boxSizing: 'border-box',
};

afterStyle = {
boxSizing: 'border-box',
};

if (this.props.beforeStyle) beforeElement =
React.createElement(this.props.beforeElementType,
Expand Down
14 changes: 7 additions & 7 deletions src/circular-progress.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import ReactDOM from 'react-dom';
import StylePropable from './mixins/style-propable';
import AutoPrefix from './styles/auto-prefix';
import autoPrefix from './styles/auto-prefix';
import Transitions from './styles/transitions';
import DefaultRawTheme from './styles/raw-themes/light-raw-theme';
import ThemeManager from './styles/theme-manager';
Expand Down Expand Up @@ -144,13 +144,13 @@ const CircularProgress = React.createClass({
_rotateWrapper(wrapper) {
if (this.props.mode !== 'indeterminate') return;

AutoPrefix.set(wrapper.style, 'transform', 'rotate(0deg)');
AutoPrefix.set(wrapper.style, 'transitionDuration', '0ms');
autoPrefix.set(wrapper.style, 'transform', 'rotate(0deg)', this.state.muiTheme);
autoPrefix.set(wrapper.style, 'transitionDuration', '0ms', this.state.muiTheme);

setTimeout(() => {
AutoPrefix.set(wrapper.style, 'transform', 'rotate(1800deg)');
AutoPrefix.set(wrapper.style, 'transitionDuration', '10s');
AutoPrefix.set(wrapper.style, 'transitionTimingFunction', 'linear');
autoPrefix.set(wrapper.style, 'transform', 'rotate(1800deg)', this.state.muiTheme);
autoPrefix.set(wrapper.style, 'transitionDuration', '10s', this.state.muiTheme);
autoPrefix.set(wrapper.style, 'transitionTimingFunction', 'linear', this.state.muiTheme);
}, 50);

this.rotateWrapperTimer = setTimeout(() => this._rotateWrapper(wrapper), 10050);
Expand Down Expand Up @@ -197,7 +197,7 @@ const CircularProgress = React.createClass({
},
};

AutoPrefix.set(styles.wrapper, 'transitionTimingFunction', 'linear');
autoPrefix.set(styles.wrapper, 'transitionTimingFunction', 'linear', this.state.muiTheme);

if (this.props.mode === 'determinate') {
let relVal = this._getRelativeValue();
Expand Down
2 changes: 2 additions & 0 deletions src/enhanced-button.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ const EnhancedButton = React.createClass({
const focusRipple = isKeyboardFocused && !disabled && !disableFocusRipple && !disableKeyboardFocus ? (
<FocusRipple
color={focusRippleColor}
muiTheme={this.state.muiTheme}
opacity={focusRippleOpacity}
show={isKeyboardFocused}
/>
Expand All @@ -186,6 +187,7 @@ const EnhancedButton = React.createClass({
<TouchRipple
centerRipple={centerRipple}
color={touchRippleColor}
muiTheme={this.state.muiTheme}
opacity={touchRippleOpacity}
>
{children}
Expand Down
2 changes: 2 additions & 0 deletions src/enhanced-switch.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,7 @@ const EnhancedSwitch = React.createClass({
key="touchRipple"
style={rippleStyle}
color={rippleColor}
muiTheme={this.state.muiTheme}
centerRipple={true}
/>
);
Expand All @@ -406,6 +407,7 @@ const EnhancedSwitch = React.createClass({
key="focusRipple"
innerStyle={rippleStyle}
color={rippleColor}
muiTheme={this.state.muiTheme}
show={this.state.isKeyboardFocused}
/>
);
Expand Down
4 changes: 2 additions & 2 deletions src/left-nav.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import ReactDOM from 'react-dom';
import KeyCode from './utils/key-code';
import StylePropable from './mixins/style-propable';
import AutoPrefix from './styles/auto-prefix';
import autoPrefix from './styles/auto-prefix';
import Transitions from './styles/transitions';
import WindowListenable from './mixins/window-listenable';
import Overlay from './overlay';
Expand Down Expand Up @@ -413,7 +413,7 @@ const LeftNav = React.createClass({
const leftNav = ReactDOM.findDOMNode(this.refs.clickAwayableElement);
const transformCSS = 'translate3d(' + (this._getTranslateMultiplier() * translateX) + 'px, 0, 0)';
this.refs.overlay.setOpacity(1 - translateX / this._getMaxTranslateX());
AutoPrefix.set(leftNav.style, 'transform', transformCSS);
autoPrefix.set(leftNav.style, 'transform', transformCSS, this.state.muiTheme);
},

_getTranslateX(currentX) {
Expand Down
21 changes: 3 additions & 18 deletions src/menus/menu.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import update from 'react-addons-update';
import Controllable from '../mixins/controllable';
import StylePropable from '../mixins/style-propable';
import ClickAwayable from '../mixins/click-awayable';
import AutoPrefix from '../styles/auto-prefix';
import autoPrefix from '../styles/auto-prefix';
import Transitions from '../styles/transitions';
import KeyCode from '../utils/key-code';
import PropTypes from '../utils/prop-types';
Expand Down Expand Up @@ -197,21 +197,6 @@ const Menu = React.createClass({
if (this.props.autoWidth) this._setWidth();
},

componentDidEnter() {
this._animateOpen();
},

componentWillLeave(callback) {
let rootStyle = ReactDOM.findDOMNode(this).style;
rootStyle.transition = Transitions.easeOut('250ms', ['opacity', 'transform']);
rootStyle.transform = 'translate3d(0,-8px,0)';
rootStyle.opacity = 0;
rootStyle = AutoPrefix.all(rootStyle);
setTimeout(() => {
if (this.isMounted()) callback();
}, 250);
},

componentClickAway(e) {
if (e.defaultPrevented)
return;
Expand Down Expand Up @@ -240,8 +225,8 @@ const Menu = React.createClass({
let scrollContainerStyle = ReactDOM.findDOMNode(this.refs.scrollContainer).style;
let menuContainers = ReactDOM.findDOMNode(this.refs.list).childNodes;

AutoPrefix.set(rootStyle, 'transform', 'scaleX(1)');
AutoPrefix.set(scrollContainerStyle, 'transform', 'scaleY(1)');
autoPrefix.set(rootStyle, 'transform', 'scaleX(1)', this.state.muiTheme);
autoPrefix.set(scrollContainerStyle, 'transform', 'scaleY(1)', this.state.muiTheme);
scrollContainerStyle.opacity = 1;

for (let i = 0; i < menuContainers.length; ++i) {
Expand Down
4 changes: 3 additions & 1 deletion src/mixins/style-propable.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export default {
mergeAndPrefix,

prepareStyles(...args) {
return prepare((this.state && this.state.muiTheme) || this.context.muiTheme, ...args);
return prepare((this.state && this.state.muiTheme) ||
this.context.muiTheme ||
(this.props && this.props.muiTheme), ...args);
},
};
24 changes: 10 additions & 14 deletions src/refresh-indicator.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import ReactDOM from 'react-dom';
import StylePropable from './mixins/style-propable';
import AutoPrefix from './styles/auto-prefix';
import autoPrefix from './styles/auto-prefix';
import Transitions from './styles/transitions';
import Paper from './paper';
import DefaultRawTheme from './styles/raw-themes/light-raw-theme';
Expand Down Expand Up @@ -309,33 +309,29 @@ const RefreshIndicator = React.createClass({
transitionDuration = '850ms';
}

AutoPrefix.set(path.style, 'strokeDasharray', strokeDasharray);
AutoPrefix.set(path.style, 'strokeDashoffset', strokeDashoffset);
AutoPrefix.set(path.style, 'transitionDuration', transitionDuration);
autoPrefix.set(path.style, 'strokeDasharray', strokeDasharray, this.state.muiTheme);
autoPrefix.set(path.style, 'strokeDashoffset', strokeDashoffset, this.state.muiTheme);
autoPrefix.set(path.style, 'transitionDuration', transitionDuration, this.state.muiTheme);

this.scalePathTimer = setTimeout(() => this._scalePath(path, currStep + 1), currStep ? 750 : 250);
},

_rotateWrapper(wrapper) {
if (this.props.status !== 'loading') return;

AutoPrefix.set(wrapper.style, 'transform', null);
AutoPrefix.set(wrapper.style, 'transform', 'rotate(0deg)');
AutoPrefix.set(wrapper.style, 'transitionDuration', '0ms');
autoPrefix.set(wrapper.style, 'transform', null, this.state.muiTheme);
autoPrefix.set(wrapper.style, 'transform', 'rotate(0deg)', this.state.muiTheme);
autoPrefix.set(wrapper.style, 'transitionDuration', '0ms', this.state.muiTheme);

this.rotateWrapperSecondTimer = setTimeout(() => {
AutoPrefix.set(wrapper.style, 'transform', 'rotate(1800deg)');
AutoPrefix.set(wrapper.style, 'transitionDuration', '10s');
AutoPrefix.set(wrapper.style, 'transitionTimingFunction', 'linear');
autoPrefix.set(wrapper.style, 'transform', 'rotate(1800deg)', this.state.muiTheme);
autoPrefix.set(wrapper.style, 'transitionDuration', '10s', this.state.muiTheme);
autoPrefix.set(wrapper.style, 'transitionTimingFunction', 'linear', this.state.muiTheme);
}, 50);

this.rotateWrapperTimer = setTimeout(() => this._rotateWrapper(wrapper), 10050);
},

prefixed(key) {
return AutoPrefix.single(key);
},

render() {
const rootStyle = this._getRootStyle();
return (
Expand Down
14 changes: 10 additions & 4 deletions src/ripples/circle-ripple.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,20 @@ import React from 'react';
import ReactDOM from 'react-dom';
import PureRenderMixin from 'react-addons-pure-render-mixin';
import StylePropable from '../mixins/style-propable';
import AutoPrefix from '../styles/auto-prefix';
import autoPrefix from '../styles/auto-prefix';
import Transitions from '../styles/transitions';
import Colors from '../styles/colors';

const CircleRipple = React.createClass({

propTypes: {
color: React.PropTypes.string,

/**
* The material-ui theme applied to this component.
*/
muiTheme: React.PropTypes.object.isRequired,

opacity: React.PropTypes.number,

/**
Expand Down Expand Up @@ -60,14 +66,14 @@ const CircleRipple = React.createClass({
Transitions.easeOut('2s', 'opacity') + ',' +
Transitions.easeOut('1s', 'transform')
);
AutoPrefix.set(style, 'transition', transitionValue);
AutoPrefix.set(style, 'transform', 'scale(1)');
autoPrefix.set(style, 'transition', transitionValue, this.props.muiTheme);
autoPrefix.set(style, 'transform', 'scale(1)', this.props.muiTheme);
},

_initializeAnimation(callback) {
let style = ReactDOM.findDOMNode(this).style;
style.opacity = this.props.opacity;
AutoPrefix.set(style, 'transform', 'scale(0)');
autoPrefix.set(style, 'transform', 'scale(0)', this.props.muiTheme);
setTimeout(() => {
if (this.isMounted()) callback();
}, 0);
Expand Down
10 changes: 8 additions & 2 deletions src/ripples/focus-ripple.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import ReactDOM from 'react-dom';
import PureRenderMixin from 'react-addons-pure-render-mixin';
import StylePropable from '../mixins/style-propable';
import AutoPrefix from '../styles/auto-prefix';
import autoPrefix from '../styles/auto-prefix';
import Colors from '../styles/colors';
import Transitions from '../styles/transitions';
import ScaleInTransitionGroup from '../transition-groups/scale-in';
Expand All @@ -14,6 +14,12 @@ const FocusRipple = React.createClass({
propTypes: {
color: React.PropTypes.string,
innerStyle: React.PropTypes.object,

/**
* The material-ui theme applied to this component.
*/
muiTheme: React.PropTypes.object.isRequired,

opacity: React.PropTypes.number,
show: React.PropTypes.bool,

Expand Down Expand Up @@ -85,7 +91,7 @@ const FocusRipple = React.createClass({
nextScale = currentScale === startScale ?
endScale : startScale;

AutoPrefix.set(innerCircle.style, 'transform', nextScale);
autoPrefix.set(innerCircle.style, 'transform', nextScale, this.props.muiTheme);
this._timeout = setTimeout(this._pulsate, pulsateDuration);
},

Expand Down
Loading

0 comments on commit f1d4b97

Please sign in to comment.