Open rvboris opened 8 years ago
Same here. @rvboris Have you found a solution?
@michaseel I use isomorphic-style-loader only on server build for class loading and extract-text-webpack on client build to generate css files
Also looking for a solution to this
Maybe something like this?
client.js
import React from 'react';
import ReactDOM from 'react-dom';
import WithStylesContext from './WithStylesContext';
import { Router, browserHistory } from 'react-router';
import routes from './routes';
ReactDOM.render(
<WithStylesContext onInsertCss={styles => styles._insertCss()}>
<Router history={browserHistory} routes={routes} />
</WithStylesContext>,
document.getElementById('root')
);
server.js
import express from 'express';
import React from 'react';
import ReactDOM from 'react-dom';
import WithStylesContext from './WithStylesContext';
import { Router, createMemoryHistory } from 'react-router';
import routes from './routes';
const server = express();
const port = process.env.PORT || 3000;
server.get('*', (req, res) => {
const css = []; // CSS for all rendered React components
const body = ReactDOM.renderToString(
<WithStylesContext onInsertCss={styles => css.push(styles._getCss())}>
<Router history={createMemoryHistory} routes={routes} />
</WithStylesContext>
);
const html = `<!doctype html>
<html>
<head>
<title>React Router with Isomorphic CSS Example</title>
<style>${css.join('')}</style>
</head>
<body>
<div id="root">${body}</div>
<script src="/client.js"></script>
</body>
</html>`;
res.status(200).send(html);
});
server.listen(port, () => {
console.log(`Node.js app is running at http://localhost:${port}/`);
});
routes.js
import React from 'react';
import { Route, IndexRoute, Link } from 'react-router';
import withStyles from 'isomorphic-style-loader/lib/withStyles';
import s from './Layout.css';
function Layout({ children }) {
return (
<div className={s.root}>
<header className={s.header}>
<h1 className={s.title}>React Router with Isomorphic CSS Example</h1>
<ul className={s.nav}>
<li className={s.item}><Link to="/">Home</Link></li>
<li className={s.item}><Link to="/foo">Foo</Link></li>
<li className={s.item}><Link to="/bar">Bar</Link></li>
</ul>
</header>
{children}
</div>
);
}
const LayoutWithStyles = withStyles(s)(Layout);
const Home = () => (<div>Home!</div>);
const Foo = () => (<div>Foo!</div>);
const Bar = () => (<div>Bar!</div>);
const routes = (
<Route path="/" component={LayoutWithStyles}>
<IndexRoute component={Home} />
<Route path="foo" component={Foo} />
<Route path="bar" component={Bar} />
</Route>
);
export default routes;
WithStylesContext.js
import { Component, PropTypes, Children } from 'react';
export default class WithStylesContext extends Component {
static propTypes = {
children: PropTypes.element.isRequired,
onInsertCss: PropTypes.func.isRequired,
};
static childContextTypes = {
insertCss: PropTypes.func.isRequired,
};
getChildContext() {
return { insertCss: this.props.onInsertCss };
}
render() {
return Children.only(this.props.children);
}
}
export default WithStylesContext;
@frenzzy Thank you so much! As a fresher I have learned a lot from your resolution.
@frenzzy I tried your solution but I keep getting a syntax error [Your assistance will be greatly appreciated]
> 1 | .inp{
| ^
2 | padding:10px;
3 | }
4 |
my client.js is as follows:
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { Router, browserHistory } from 'react-router';
import { syncHistoryWithStore } from 'react-router-redux';
import WithStylesContext from '../shared/WithStylesContext';
import configureStore from '../shared/store/configureStore';
import { routes } from '../shared/routes';
const initialState = window.__INITIAL_STATE__;
const store = configureStore(browserHistory, initialState, window.devToolsExtension && window.devToolsExtension());
const history = syncHistoryWithStore(browserHistory, store);
ReactDOM.render(
<Provider store={store}>
<WithStylesContext onInsertCss={styles => styles._insertCss()}>
<Router routes={routes} history={history} />
</WithStylesContext>
</Provider>,
document.getElementById('app')
);
My server.js is as follows (I didn't include the imports here):
app.get('*', (req, res) => {
const memoryHistory = createMemoryHistory(req.path);
let store = configureStore(memoryHistory);
const history = syncHistoryWithStore(memoryHistory, store);
const css = []; // CSS for all rendered React components
// routes is our object of React routes defined above
match({ history, routes, location: req.url }, (err, redirectLocation, props) => {
if (err) {
// something went badly wrong, so 500 with a message
res.status(500).send(err.message);
} else if (redirectLocation) {
// we matched a ReactRouter redirect, so redirect from the server
res.redirect(302, redirectLocation.pathname + redirectLocation.search);
} else if (props) {
const finalState = store.getState();
store = configureStore(memoryHistory, finalState);
// if we got props, that means we found a valid component to render
// for the given route
const markup = renderToString(
<Provider store={store}>
<WithStylesContext onInsertCss={styles => css.push(styles._getCss())}>
<RouterContext {...props} />
</WithStylesContext>
</Provider>
);
// render `index.ejs`, but pass in the markup we want it to display
const styl = css.join('');
res.render('index', { markup, finalState, styl });
} else {
// no route match, so 404. In a real app you might render a custom
// 404 view here
res.sendStatus(404);
}
});
});
My index.ejs file has <style type="text/css"><%- styl %></style>
My webpack.config.js file has
{
test: /\.css$/,
loaders: [
'isomorphic-style-loader',
'css-loader?modules&localIdentName=[name]_[local]_[hash:base64:3]',
'postcss-loader',
],
},
My Login.js view which I want to style has:
import React from 'react';
import { Link } from 'react-router';
import withStyles from 'isomorphic-style-loader/lib/withStyles';
import s from './Login.css';
class Login extends React.Component {
constructor(props) {
super(props);
this.displayName = 'Login';
}
render() {
return (
<div className="container">
<h1>Welcome to VISA eLearning</h1>
<input type="email" placeholder="Email address" />
<input type="password" placeholder="Password" />
<p>
<Link to="/forgot" className="btn btn-primary">Forgot Password</Link>
{' '}
<Link to="/register" className="btn btn-primary">Create an account</Link>
</p>
</div>
);
}
}
export default withStyles(s)(Login);
@Jonrock23 you need to configure isomorphic-style-loader for the server bundle too (see example react-starter-kit/tools/webpack.config.js)
@frenzzy having some issue with your solution server side, i get styles._getCss is not a function, when i debug styles is an empty object {}, any idea what might be going wrong? Works fine on client side.
For me it works with this config but only in development, in production styles._getCss is not a function
@alex-shamshurin sounds like you have different configuration for release mode, just use isomorphic-style-loader for release configuration too.
On client
{
test : /\.scss$/,
loader: 'isomorphic-style-loader!css?modules&importLoaders=2&localIdentName=[path][name]__[local]___[hash:base64:5]!postcss-loader!sass?outputStyle=expanded&sourceMap'
},
On dev server:
{
test : /\.scss$/,
loader: 'isomorphic-style-loader!css?modules&importLoaders=2&localIdentName=[path][name]__[local]___[hash:base64:5]!postcss-loader!sass?outputStyle=expanded&sourceMap'
},
on prod server
{
test : /\.scss$/,
loader: ExtractTextPlugin.extract('css?locals&modules!postcss-loader!sass?outputStyle=expanded&sourceMap')
},
when I changed on prod server config to
loader: ExtractTextPlugin.extract('isomorphic-style-loader!css?locals&modules!postcss-loader!sass?outputStyle=expanded&sourceMap')
I got an error "Module build failed: TypeError: text.forEach is not a function"
And I feel that something is wrong here. On prod i'd like have separate css file (and may be only critical css inlined)
@alex-shamshurin you should not mix ExtractTextPlugin with IsomorphicStyleLoader, you need to choose one of them and use for all environments. If you want to use ExtractTextPlugin you don't need IsomorphicStyleLoader at all.
So it's not possible to have styles inlined on development and extracted to css file on production? Why? As far as I remember people used original style-loader for devs and extract plugin for prod, but style-loader has errors with 'window' object on server and not universal. Then what a use of all that?
For example this works:
{
test : /\.css$/,
loader: ExtractTextPlugin.extract('style-loader', 'css-loader?module!postcss-loader')
},
@alex-shamshurin there are a few issues with the original syle-loader
and ExtractTextPlugin - (1) harder to configure (different configs for development/production builds), (2) doesn't work well with code-splitting, (3) more files for the browser to load (e.g. just bundle.js
vs bundle.js
+ styles.css
), (4) style-loader injects all the stules into the DOM, which is not good for performance (5) there could be conflicting styles in different React components, style-loader
doesn't support that. For example, with isomoprhic-style-loader you have CheckoutPage component where you may have styles on body
, html
elements.. these styles are going to be injected into the dom, only right before the CheckoutPage component is mounted and removed from the dom right after the component is unmounted.
So I needn't separate css file at all, do you mean that? But if there are too many syles for many different pages it can increase page loading time, or isomorphic-style-loader includes only styles that is required by page? (during server rendering) and then ehat happens during client routing?
Correct, you don't need separate .css files at all and in order to optimize the initial page load AND your application is big enough, you may want to use code splitting, each chunk of your app, in this case, will include its own piece of CSS.
Should I care about replacing styles on client routing? Why react-starter-kit contains this in App component:
componentWillMount() {
const { insertCss } = this.props.context;
this.removeCss = insertCss(s);
}
componentWillUnmount() {
this.removeCss();
}
Usually, you just decorate your components with withStyles(styles1, styles2, MyComponent)
(import withStyles from 'isomorphic-style-loader'
). But in case with the App
component, it's a little bit different, because React's context is set inside this App component and withStyles will not work with it.
It would be great to split the existing App
component into two, the one that sets React's context and the other that has markup and CSS for the main layout (src/components/Layout
) decorated with withStyles(s, Layout)
higher-order component. A PR with this update is welcome!
@alex-shamshurin BTW, here is an example - the Layout component + the top-level component that sets React's context here.
https://github.com/thebigredgeek/isomorph-style-loader < There is an active fork here with the latest round of changes, including hoist-non-react-statics
integration on the decorator
@koistya What's the use of mount and unmount handlers there? And BTW why all styles are repeated twice? when component is mounting does it care about styles in head?
@koistya Hi, I also get similar error _getCss is not a function
.
Both webpack config of my client side and server side for css is :
loader: [
'isomorphic-style-loader',
'css?localIdentName=[hash:base64]&modules&importLoaders=1&sourceMap',
'postcss-loader',
'sass-loader'],
When I debug the style
object, it is object like:
{ header: '_1gojyfyWXhhfHNYDmYH54L',
logo: 'aH8URw6l9v-WpVuVTmyIw',
active: '_35I5aW8-Hdns_JUIos6gUp', }
It seems that help functions _getCss
and _insertCss
are not added to the style
object.
It seems that help functions _getCss and _insertCss are not added to the style object.
I am having the same issue.
any boilerplate with react-router-v4 ?
Same thing.
I've realized that I didn't bundle my server-side code with webpack (stupid me). Anyways, anyone who is interested in minmal, framework-agnostic example of isomorphic-style-loader
+ css-loader
+ webpack
can check this repo:samples-isomorphic-style-loader-server-side which I created for my mates at work.
So if you don't have _getCss
or _insertCss
functions - bundle your server side code with webpack.
@frenzzy Thanks for the proposed solution to get isomorphic style loader to work with react router. I had some initial issues with not being able to provide multiple styles to withStyle
and share a proposed fix, in case someone else comes here with similar problems...
In server.js
change
<WithStylesContext onInsertCss={styles => css.push(styles._getCss())}>
to
<WithStylesContext onInsertCss={(...styles) => { styles.forEach(style => inlineStyles.push(style._getCss())) }}>
in client.js
change
<WithStylesContext onInsertCss={styles => styles._insertCss()}>
to
<WithStylesContext onInsertCss={(...styles) => {styles.forEach(style => style._insertCss())}}>
@frenzzy i have used your solution as above but it is giving error as below Cannot read property 'apply' of undefined at WithStyles.componentWillMount
My code : `renderToString( <WithStylesContext onInsertCss={styles => css.push(styles._getCss())}>
</WithStylesContext>
)` Any help??
Hello, how to provide context to react-router?