Webpack’s import() will soon fetch JS + CSS— Here’s how you do it today

the future

UPDATE (AUGUST 11th 2017): You want to use babel-plugin-universal-import now.

A month ago Webpack’s creator, Tobias Koppers, unleashed “The big plan” for CSS in Webpack in his article “The new CSS workflow (step1).”

The primary takeaway — mine at least — was that code-splitting for CSS would become a first class priority. You would be able to get css files generated for each of your dynamic “code splitting” chunks, and calls to import() would get you 2 files: JS + CSS. Here’s a quote from the article:

“The big plan

In the long term we want to make it possible to add first-class module support for CSS to webpack. This will work the following way:

  • We add a new module type to webpack: Stylesheet (next to Javascript)
  • We adjust the Chunk Templates to write two files. One for the javascript and one of the stylesheets (in a .css file).
  • We adjust the chunk loading logic to allow loading of stylesheets. We need to wait for CSS applied or at least loaded, before executing the JS.
  • When we generate a chunk load we may load the js chunk and the stylesheet chunk in parallel (combined by Promise.all).”

The foundation of course is being able to have CSS files generated for each dynamic chunk. Currently, the extract-text-webpack-plugin does not support this. The Beta 3.0 version circulating today won’t support it. My extract-css-chunks-webpack-plugin which has been around for a while does. But that’s still not good enough.

It’s not good enough because compilation is slow and doesn’t maximize caching within the browser. The reason is because it creates 2 different JS chunks for what should be just one chunk. Those chunks are:

  • one without CSS injection via style-loader
  • one with CSS injection via style-loader

The reason we do all this is so so your initial serving/rendering get the most minimal set of bytes + cacheable stylesheets AND so future calls to import() aren’t missing css.

BACKSTORY

For this all to make sense, and if this is the first time you’re hearing about any of this, you probably want to check out:

The summary is via react-universal-component and webpack-flush-chunks you can easily universally render your app while simultaneously code-splitting. For the first time as a generally available NPM package (no framework required). For those not in the know, the takeaway is this: SSR is a solved problem, splitting is a solved problem, doing both together hasn’t been.

You no longer have to trade SEO/SSR for code-splitting or vice versa.

That also means your primary mechanism for conserving bytes delivered to clients is code-splitting. And to get the full benefit of that you don’t want to be sending your private member panel’s CSS to your public visitors or vice versa. If you have multiple areas to your app, the problem just compounds.

In fact, through statically split CSS you’re sending less bytes to clients than the popular “render path” solutions du jour (since those have to send the definitions of your CSS in JS chunks in addition to css). And perhaps more importantly, that also saves you many wasted cycles generating CSS during render on the server and client.

Being able to control what static CSS files you send to clients really is a nice way to handle this problem. It’s why @sokra came to this conclusion. Being able to automatically do it is even better.

Introducing Babel-Plugin-Dual-Import + Extract CSS Chunks Webpack Plugin 2.o

babel-plugin-dual-import transforms your request to a Promise.all just as Sokra talked about, and as bonus it automatically comes up with a webpackChunkName for you. “Magic comments” are so magical they’ve disappeared. Under the hood of course I use them to generate your chunk names.

As for extract-css-chunks-webpack-plugin 2.0, well we’ve circumvented a major performance issue in terms of build time (no more double JS chunks), and now all you’re dealing with is stylesheets your users’ browsers can cache. And yes, HMR still works (better than ever in fact).

Also note: all 4 of these packages work together. I’m tentatively calling it the “Universal” family of packages. They can all be individually used — because that’s what flexible frameworkless development is about. But they work best together. Enjoy.

INSTALLATION

Since today we are primarily introducing the babel plugin, that’s what we’ll focus on:

yarn add --dev babel-plugin-dual-import

.babelrc:

{
"presets": [whatever you usually have],
"plugins": ["dual-import"]
}

App.js:

import('./Foo.js')

↓ ↓ ↓ ↓ ↓ ↓

import { importCss } from 'babel-plugin-dual-import/importCss.js'

Promise.all([
import( /* webpackChunkName: 'Foo' */ './Foo'),
importCss('Foo')
]).then(promises => promises[0]);

And if you’re using dynamic requires:

import(`../base/${page}`)

↓ ↓ ↓ ↓ ↓ ↓

import { importCss } from 'babel-plugin-dual-import/importCss.js'

Promise.all([
import( /* webpackChunkName: 'base/[request]' */ `./base/${page}`),
importCss(`base/${page}`)]
).then(promises => promises[0]);

You of course need to use extract-css-chunks-webpack-plugin to generate the dynamic CSS “chunks” and webpack-flush-chunks to seamlessly serve it. We’ll skip the webpack plugin config, but here’s how you serve the assets corresponding to rendered components:

import flushChunks from 'webpack-flush-chunks'
import * as Universal from 'react-universal-component/server'
const appString = ReactDOM.renderToString(<App />)

const { js, styles, cssHash } = flushChunks(webpackStats, {
chunkNames: Universal.flushChunkNames(),
})
res.send(`
<html>
<head>
${styles}
</head>
<body>
<div id='root'>${appString}</div>
${js}
${cssHash}
</body>
</html>
`)

If you’ve looked at how webpack-flush-chunks works before, the new addition is the cssHash which is similar to what Webpack puts in its bootstrap script mapping javascript chunks to their IDs. The cssHash maps css chunk names to their stylesheet files. babel-plugin-dynamic-import requests stylesheets from there in parallel with javascript imports.

CONCLUSION

There’s not much left to say today. So I won’t waste your time. Git clone the demo to see all 4 packages in action:

If you haven’t checked out React Universal Component + Webpack Flush Chunks, do so. They’re currently the only solution bringing it all together for React developers apprehensive about frameworks. For developers that have had these problems, you know who you are. Those days are over.

All 4 links in one place: