A production-ready realtime SaaS with webpack

I’m a huge fan of Meteor. It socketizes everything, transpiles your styles, and gives you a big chunk of code to push to a server. Sometimes though, you need something more flexible.

Like any standard Saas, I needed a small landing page I could serve from a CDN, a portal that offered a realtime websocket experience, and an easy path to scale both vertically and horizontally.

The end result is something I call Meatier, which I open sourced on github: http://github.com/mattkrick/meatier. The package solves a lot of problems (JWT authentication & authorization, scaling sockets, using redux as a client-side cache, storing socket state in redux, optimistic & realtime database updates, etc.) but for this entry, I decided to focus on webpack. Why? Because there are 100 “webpack 101” guides, and not a single “webpack 201”.

For this project, I used webpack 2 (beta). It still has a bug or 2, but you won’t find them easily.

Building a production webpack config

I’ll assume you already have a development config built. If you don’t already know how to build a config for development, well…

Using your router to chunk your site

The first step is to go through your router & make all your synchronous components async. Let’s go through an example with react-router@1.0.2:

export default store => {
return {
onEnter: requireNoAuth(store),
path: 'signup',
getComponent: async (location, cb) => {
let component = await System.import('./Signup');
cb(null, component)
}
}
}

The first thing to notice is the route isn’t in JSX and it’s actually a function taking in your redux store. This makes things easier for code-splitting your redux reducer (more on that in the next blog… if you like this one). The magic happens in the System.import. In the spec, this returns a promise to deliver the module. In webpack 2, it’s statically assessed and ready to become its own separate chunk that can be dynamically required.

Writing the production config for the client

Great. Now webpack knows how to split your code in the best way possible so that you never send an extra byte down the wire. But… sometimes that’s not the best. For example, maybe saving an extra 5Kb isn’t worth the extra HTTP request. That’s why there’s AggressiveMergingPlugin. It balances your request to size ratio. There’s also MinChunkSizePlugin, which I keep set at 50000 to limit my tiny chunks.

The next step is to make the second visit better for the user (they will visit your site a second time, right?) To do so, we want to load as much code as possible from their browser cache. Fewer bytes sent = more money for you and a faster load for them. To do this, we’ll break out the vendor packages, because chances are you won’t update React as much as you’ll update your own bug-infested code. (example below, hang tight)

The next step is to address updates. When a client loads an asset, the browser looks at the filename. If it can resolve the filename locally, it won’t send a request. That means if your file is named something like app.js, you could push an update and the client will never download it until the cache is invalidated. To solve this, assign a chunkhash that changes when the contents of the file changes.

output: {
filename: '[name]_[chunkhash].js',
chunkFilename: '[name]_[chunkhash].js',
...
},

Now, instead of requesting app.js statically in our HTML, we’ll need to request a name that changes on every build. To do this, we use the AssetsPlugin to create a lookup table between the asset and chunkhashed name:

new AssetsPlugin({
path: path.join(root, 'build'),
filename: 'assets.json'
}),

Then, we just require assets.json before we make our HTML, and stick in the assets.app.js. Easy! https://github.com/mattkrick/meatier/blob/master/src/server/Html.js

There’s a problem though: if a chunk changes, that’ll change the hash for your other chunks as well, causing all your chunks to require a fresh download. To avoid this, there’s the NamedModulesPlugin that replaces webpack’s module numbers with the actual pathnames. Just call it with no options and on the next build all the chunks will have distinct hashes. Note: for the security minded, you may not want to share your pathnames with the client, which is why there’s HashedModuleIdsPlugin. It’ll cause a little extra bloat, but offers some peace of mind.

At this point, the only chunks that are updated are the ones where you changed code & the one with the webpack runtime in it. Unfortunately, that webpack runtime is bundled into the last common chunk, probably the vendor.js. To get around this, we’ll need to extract out the webpack runtime so instead of refreshing 2 files, it’ll refresh just the one you want:

new webpack.optimize.CommonsChunkPlugin({
names: ['vendor', 'manifest'],
minChunks: Infinity
}),

Since the last common chunk always has the runtime, we’ll extract it out in our CommonsChunkPlugin. Note that it doesn’t have a corresponding entry chunk and since minChunks is Infinity, nothing else can be added to it.

To get the manifest in our HTML, we could make another HTTP request, but requesting a couple bytes of data seems like a waste, so let’s just inline it. To do so, we’ll grab the name from the assets.json, then read out the contents into a string:

https://github.com/mattkrick/meatier/blob/master/src/server/createSSR.js

const assets = require('../../build/assets.json');
const readFile = promisify(fs.readFile);
assets.manifest.text = await readFile(path.join(root, 'build', path.basename(assets.manifest.js)), 'utf-8');

Writing the production config for the server

The last step is the worst. CSS. You have 4 choices:

  1. Extract the CSS into a stylesheet
  2. Inline your CSS in each component
  3. Extract the CSS into a series of <style> tags in your <head>
  4. A combination

I personally like external stylesheets. At the expense of another HTTP request, I get something that loads very quickly, doesn’t need JavaScript, and can be cached on the client. While in a perfect world we’d have 1 stylesheet per chunk, this isn’t possible yet for server side rendering (if I’m wrong, let me know!). Given that CSS repeats a lot of the same strings, it’ll compress very small, so let’s opt for 1 big style sheet.

Currently, there are a lot of hacky solutions to solve the problem that node doesn’t know how to require a css file. Maybe you’ve seen some:

  • Simply ignore css files on the server (AKA I don’t give a FOUC)
  • Stick a process.env.BROWSER in EVERY single component
  • Dig into the webpack stats.json, extract the referenced CSS files, & inject them into the requires when on the server
  • Give every component a this.styles which its parent reads, all the way up the tree until you can create your own <style> tag
  • Put the styles in a component’s context

I don’t like any of them. Rewriting your components just to achieve SSR seems wrong. So, I decided to make a webpack build of my router on the server. Since webpack knows what to do with the css files, it can build the entire router & then use that to render a page on the server side. Webpack makes this easy if you know what to do: just set your target to ‘node’ and your output.libraryTarget: “commonjs2”. https://github.com/mattkrick/meatier/blob/master/webpack/webpack.config.server.js

Next, use the ExtractTextPlugin to create your css file. With both webpack 1 & 2, I got some unexpected behavior where setting the plugin’s allChunks to true would create multiple CSS files, but also put all the contents in the main css file. To solve this, I limit the chunks to 1 with LimitChunkCountPlugin. The end result is 1 prerendered router that your server can use to render some HTML, and a CSS file that has all the styles that your site will use.

For faster server startup times (assuming you’re using babel on the server), you can exclude this prerendered bundle with the following options:

require('babel-register')({
only(filename) {
return (filename.indexOf('build') === -1 && filename.indexOf('node_modules') === -1);
}
});

Closing Remarks

Now you know how to write a production ready webpack config. It works using server side rendering. It doesn’t need javascript enabled on the client. Best of all, you don’t need to rewrite every component to handle styling. As an addd bonus, it’s really fast to develop on: anytime you’re working on the server, just run your production build so you don’t need to recompile your client side on every restart.

I hope this gave you the confidence to try more aggressive webpack configs. Go ahead and check out the Meatier repo and if you have any questions or suggestions, don’t be afraid to reach out.