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.
Start the production server with heroku local
and goto https://localhost:5000.
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.
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!
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.