I am working on an isomorphic React+Flux+express app and using webpack loaders for my sass (with sass-loader) and jsx files. I am not sure how to inject my stylesheets into a server side template. I took a look at the Extract Text Plugin for this purpose, but I really want to be able to use hot module replacement.
Right now, I am loading my main.scss
file in a React component like this:
if (typeof window !== 'undefined') {
require("!style!css!sass!../../../styles/main.scss");
}
This works well for loading an individual stylesheet in a component, but there is a flicker before the React part is mounted. I understand that this is because this is injecting the stylesheet after my client-side app has loaded, so the stylesheet is not immediately available. This leads to the actual question: is there a way to inject this style-sheet into my server-side template while still using the webpack loader, or does this call for a separate gulpfile or express middleware? I was previously using a gulpfile to build my stylesheets, but I will end up having a lot of stylesheets and don't want them all jammed into one file.
So the idea here is to have webpack compile with two separate configurations, one targeted for web
(the browser), the other targeted for node
(server-side). The server-side bundle can be required in other node/express code to build the pre-rendered HTML with the css.
There's a full example here, and I'll walk you through the relevant parts of it. https://github.com/webpack/react-starter
The prerender.html
in app
is the server-side template the author is using. Notice the following two lines of code:
<link rel="stylesheet" href="STYLE_URL">
<script src="SCRIPT_URL"></script>
See the config for webpack here https://github.com/webpack/react-starter/blob/master/make-webpack-config.js. The options being passed into here depends on whether you're doing a prod build or a dev build. Since we want to build the client bundle and the prerendering server bundle, let's take a look at https://github.com/webpack/react-starter/blob/master/webpack-production.config.js. It's creating two bundles, specifically the first one with separate stylesheets targetted for browser, and the second config is for prerendering.
For the first compilation, it uses this:
plugins.push(new ExtractTextPlugin("[name].css" + (options.longTermCaching ? "?[contenthash]" : "")));
to create a separate css file alongside your bundle. During the second compilation (for prerendering), it uses null-loader
to load the styles (because we already have the styles we needed in a css file, we can just use that).
Now here's where we inject the path to css into your server template.
Take a look here at a simplified server.js
: https://github.com/webpack/react-starter/blob/8e6971d8fc9d18eeef7818bd6e9be45f6b8643e6/lib/server.js
var STYLE_URL = "main.css?" + stats.hash;
var SCRIPT_URL = [].concat(stats.assetsByChunkName.main)[0];
app.get("/*", function(req, res) {
res.contentType = "text/html; charset=utf8";
res.end(prerenderApplication(SCRIPT_URL, STYLE_URL, COMMONS_URL));
});
Assuming your output path for your bundle is the same as server.js (otherwise you can get the publicPath using require("../build/stats.json").publicPath
and prepend it to your STYLE_URL
and SCRIPT_URL
above.
Then in your prerender.jsx
: https://github.com/webpack/react-starter/blob/8e6971d8fc9d18eeef7818bd6e9be45f6b8643e6/config/prerender.jsx
Grab your server-side template prerender.html
and replace the URLs:
var html = require("../app/prerender.html");
module.exports = function(scriptUrl, styleUrl, commonsUrl) {
var application = React.renderComponentToString(<Application />);
return html.replace("STYLE_URL", styleUrl).replace("SCRIPT_URL", scriptUrl).replace("COMMONS_URL", commonsUrl).replace("CONTENT", application);
};
I admit this can be complicated and confusing, and if it's easier to use a separate gulpfile go for it. But do play around with this. If you need more clarification and help, you can post a comment and I'll get to it as soon as I can or you can use the webpack chatroom here (https://gitter.im/webpack/webpack), I'm sure one of the developers there can probably give you a better explanation than I did.
Hope this is somewhat(?) helpful!
The initial flash appears because "style-loader" hasn't yet downloaded your CSS styles.
To resolve this issue, isomorphic (universal) rendering is required (markup generation on server, and not just in production mode - in development mode too).
You can achieve isomorphic rendering either by following the "react-starter" project path (mentioned in the comment above), or by using webpack-isomorphic-tools
https://github.com/halt-hammerzeit/webpack-isomorphic-tools
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With