Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Style] Address server sider rendering issues #3009

Merged
merged 5 commits into from
Jan 22, 2016
Merged
Show file tree
Hide file tree
Changes from all 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: 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);
Copy link
Contributor

Choose a reason for hiding this comment

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

Any reason why we can't call muiTheme.prefix() rather than calling it on the imported auto-prefix from here?

Copy link
Member Author

Choose a reason for hiding this comment

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

I had some issue when trying to do so. That's definitely something we should address.

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