Dynamic CSS Chunk Loading
In this article we're going to look at doing for CSS what we just did for JS. Splitting and dynamically delivering component sized chunks for each page.
We're going to start where we left off in the last article.
If you need to catch up:
git clone https://github.com/lawwantsin/webpack-course.git
cd webpack-course
git checkout css-extract
npm install
So we've done most of the work to get us there. The only thing left to do is hook up two packages, one by the same engineer who gave us the react-universal-component
package and the other made 4 days ago by the creator of webpack. We'll be serving CSS along with our dynamic JS imports.
In terminal:
npm i webpack-flush-chunks mini-css-extract-plugin
We're going to deliver the component CSS in tandem with the component's JS. To illustrate, let's create a new file in src/components
:
mkdir src/css
touch src/css/About.css
And let's copy the .profile
stuff from main.css
into that new file:
Into About.css
:
.profile {
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
flex-flow: column;
}
.profile > img {
border-radius: 100%;
bseriesOrder: 5px;
width: 300px;
box-shadow: 0 0 20px black;
}
h1 {
font-size: 5em;
font-family: sans-serif;
color: white;
text-shadow: 0 0 20px black;
text-align: left;
}
Now let's import that file into our About.js
.
const imagePath = require("../images/link.jpg") // Under this line
import "../css/About.css"
Now, in webpack.dev-client.js
and webpack.prod-client.js
:
// At the top
const MiniCSSExtractPlugin = require('mini-css-extract-plugin')
// In the loaders.
{
test: /\.css$/,
use: [MiniCSSExtractPlugin.loader, "css-loader"]
}
// In the Plugins.
new MiniCSSExtractPlugin()
We're going to replace the ExtractTextPlugin
with the MiniCSSExtractPlugin
plugin in production. In development, we should return to the good old style-loader
to do HMR.
In the plugins array let's add that without arguments.
new MiniCSSExtractPlugin({
filename: "[name].css",
chunkFilename: "[name].css"
}),
We can adjust the filename of our css, chunks, though, in production, it will default to using a content hash.
Okay, last let's put some blank css in the other components import
them in their js
files, and run the server.
In terminal:
cd src/css
touch Article.css Gallery.css
In Gallery.js:
import "../css/Gallery.css"
In Article.js:
import "../css/Article.css"
It's important we place these css files in a separate (non-child) folder from the components involved in the import()
. If you remember in Routes.js
we're pulling dynamic pages from src/components. So I place the css in src/css
.
Let' see where we're at. In terminal:
npm run dev
Passing Chunks
Inside the production side of express.js
let's grab the client build stats object out of the stats
object which is one of the arguments passed to the function we're defining to run after the production compile is done.
We can use that to restore logging to our production builds.
webpack([clientConfigProd, serverConfigProd]).run((err, stats) => {
const clientStats = stats.toJson().children[0]
const render = require("../../build/prod-server-bundle.js").default
// Put the output back in the console.
console.log(
stats.toString({
colors: true
})
)
server.use(
expressStaticGzip("dist", {
enableBrotli: true
})
)
server.use(render({ clientStats }))
done()
})
And let's pass that as an object to the render function inside render.js
:
Now, let's import them into our middleware file.
In server/render.js
:
import { flushChunkNames } from "react-universal-component/server"
import flushChunks from "webpack-flush-chunks"
He'll be the first to admit that flushChunks is a pretty weird, gross name for a package that give us now, what's been thought incredibly complicated for quite some time.
Out of the flushChunks
function we have an object, which we can pull out the js
styles
.
Those are all ready to be embedded in a template or sting as is. They come out as nice script tags or link tags. In render.js
export default ({ clientStats }) => (req, res) => {
const app = renderToString(
<StaticRouter location={req.url} context={{}}>
<Routes />
</StaticRouter>
)
const { js, styles, cssHash } = flushChunks(clientStats, {
chunkNames: flushChunkNames()
})
res.send(`
<html>
<head>
${styles}
</head>
<body>
<div id="react-root">${app}</div>
${js}
${cssHash}
</body>
</html>
`)
}
Finally we notice a Loading...
where there should be html. In webpack.dev-server.js
let's add the LimitChunkCountPlugin
to our plugins array:
plugins: [
new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 1
}),
Awesome and it's working.
In Sum
When we rerun our server and go back to our browser, we see we have what we were promised, the mythical double chunk, coming at you. You'll notice that About holds the h1 statement, so h1
is un-styled until those rules come down. Really cool stuff.
git checkout css-chunks-final
Up Next
Now that we've got an optimal setup, let's build this blog out a bit in preparation for the next section. Where we expand our blog into a multi-domain super site. We've come a long way from humble beginnings, but we've added a lot of power to our setup. Let's use that power, in the next section.