Optimizing Asset Files with Compression

In this article we're going to look at a couple of options for compressing static text assets, like .css, .html and .js files. This is vitally important, especially over mobile and low bandwidth connections. If you didn't think we could squeeze anymore kB out of these files, just you wait 'til we're done here.

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 asset-compression
npm install

GZip Compression

Gzip is the standard and has been for many years. It's understood by all browsers and computation costs aside, if you're not at least gzipping your production bundles, you're leaving a lot of kB on the table. In webpack it's easy to get it done. Let's jump in.

In the terminal:

npm install compression-webpack-plugin

At the top of webpack.prod.js:

const CompressionPlugin = require("compression-webpack-plugin")

Then down in the plugins section, let's use the gzip algorithm:

new CompressionPlugin({
  algorithm: "gzip"
})

Now when we run npm run build, we get a new file.

Compression Output

Start the production server with heroku local and goto https://localhost:5000.

Non Compression Output

But we're still not serving the gzipped file. We'd see the Content-Type in Response Headers as gzip if we were. Instead it's application/javascript. What's wrong?

Seems we need a better server configuration. Heroku still doesn't support HTTP/2 or Gzip at the server level, so for now, we'll do so at the Express level. Luckily there's a package for that.

npm install express-static-gzip

In express.js, instead of the usual static middleware, we're going to replace that with express-static-gzip. So where it's commented out below, feel free to delete those lines.

// const staticMiddleware = express.static("dist")
// server.use(staticMiddleware)
const expressStaticGzip = require("express-static-gzip")
server.use(expressStaticGzip("dist"))

Now if we run heroku local or npm run prod and open it in our browser, we can see the gzip files are now being served with the correct Content Encoding. The Content Length is the right size, which is what's important.

Compression Output

Reverse Proxies

Now, I wouldn't be a good nerd if I didn't mention that it's generally best practice to use Ngnix or HAProxy. That's beyond the scope of this article, but also, if you're deploying to Heroku, as I am, and you don't have access to a Reverse Proxy layer, (in Heroku, you don't), then you're going to have to do it this way.

Personally, I prefer the compile once and serve method described above, rather than the compile at request time strategy of a Reverse Proxy. No matter how many times they tell me it's all good, cause it's written in C, I just see a expensive computational task that could be done once, being done on every single request.

Brotli

Google, of course, came up with something even better than gzip and it's worth talking about now that it's basically cross browser.

To add this kind of compression to our project, we're going to do much the same this as every other plugin. In terminal:

npm install brotli-webpack-plugin

In the webpack.prod.js at the top:

const BrotliPlugin = require("brotli-webpack-plugin")

And at the end of the plugins array:

new BrotliPlugin()

In express.js in the expressStaticGzip config, add an object as a 2nd argument after dist:

server.use(
  expressStaticGzip("dist", {
    enableBrotli: true
  })
)

When we run npm run build we see, in addition to .gz files we have a bunch of .br files as well. And they're 1/3 smaller than the gzips in some cases. Wow!

Compression Output

Development Considerations

So, what happens when you've got your express server, looking for compressed files, but your dev server serving unoptimized files. Does it 404? Let's find out. In the terminal run:

npm run dev

Everything looks okay in the browser. Seems that the express static middleware is smart enough to know when it's needed and all files serve as they should, working from the webpack.dev.js and the Dev Server we love so much.

There is an option in the command line devServer to serve gzipped files in development. In terminal you would run:

webpack-dev-server --config=config/webpack.dev.js --compress

And you'd have the main-bundle.js served with a Content-Type of gzip. Whoo hoo! I don't use it since I think the dev server middleware approach is the way to go and this kind of compression is not compatible with the middleware, but maybe it'll matter to you.

In Sum

We went over a few compression options for webpack production bundles. Both were pretty close to the way we add any plugin. Paired with a simple express middleware for serving .br or .gz files when they're right next to the uncompressed files in dist. We then looked into any implications compression has in the development environment.

git checkout asset-compression-final

Up Next

So our bundle is relatively smaller. Finally! But for good reason. It's got very little in it. Next we're going to fill out our hero's blog with real markdown articles, and get into the wild and wholly world or Server side rendering. Hold onto your butts.