Express + Brotli + Webpack 🚀

Let’s compress together and learn how Brotli can help us to increase the performance of our websites. I have implemented it in one of my work projects. So, I just thought to share my experience with everyone.

Express the ❤️ in the form of Brolti with the help of Webpack to save some customer ⏰ and 💵

Basic definitions 📖

  1. Express: Fast, unopinionated, minimalist web framework for Node.js.
  2. Brotli: It is an open source data compression library developed by Jyrki Alakuijala and Zoltán Szabadka. It is based on a modern variant of the LZ77 algorithm, Huffman coding and 2nd order context modeling.
  3. Webpack: It is a module bundler. It takes modules with dependencies and generates static assets representing those modules.

Let’s begin with the real shit 💩 !!!

There were two ways to implement the compression in Express directly (without any web server i.e: nginx etc ):

  1. Static building of compressed files with help of Webpack (any other frontend task runner or builder) and serve them on demand as needed by client.
  2. Dynamic building of compressed files on the run time (You can use require(‘compression’) ) in express to dynamically compresse files and serve to the client on the fly.

I have only implemented the static building of files. So let’s talk about that in more detail.

Static compression with Express

In the first step, which is building your bundles, you can include these two plugins `compression-webpack-plugin` and `brotli-webpack-plugin`.

const CompressionPlugin = require(‘compression-webpack-plugin’);
const BrotliPlugin = require(‘brotli-webpack-plugin’);
module.exports = {
plugins: [
new CompressionPlugin({
asset: ‘[path].gz[query]’,
algorithm: ‘gzip’,
test: /\.js$|\.css$|\.html$/,
threshold: 10240,
minRatio: 0.8
}),
new BrotliPlugin({
asset: ‘[path].br[query]’,
test: /\.js$|\.css$|\.html$/,
threshold: 10240,
minRatio: 0.8
})
]
}

These plugins will generate both gzip and brotli file for all of your bundles i.e if bundle name is ‘vendor_d0cfe49e718c1366c661.js’ you will get the vendor_d0cfe49e718c1366c661.js.gzip and vendor_d0cfe49e718c1366c661.js.br in the same directory (let’s assume it is /dist/static/vendor_d0cfe49e718c1366c661.js.* as of now).

PS: The above code will only generate the .gzip and .br if the minRatio 0.8 is achieved while compressing files. So in case of very small files gzip and br files will not be generated. The reason is that time in compressing and decompressing is costlier than the actual file served without compression.

You may also need to set the public path in webpack output config to ‘/static’. It will specifies the public URL address of the output files when referenced in a browser. Which will help us to filter the URL of the request and serve the the files by express-static-gzip fonly static if URL consist of ‘/static’.

output: {
path: '/dist/static',
filename: ‘[name]_[chunkhash].js’,
chunkFilename: ‘[id].[chunkhash].js’,
publicPath: ‘/static/’,
},

In the second step, which is to serve the right file based on input headers from the client browser. We will use the express-static-gzip

var express = require(“express”);
var expressStaticGzip = require(“express-static-gzip”);
var app = express();
// app.use(express.static(path.join(__dirname))); This was used previously with express.
app.use(“/”, expressStaticGzip(path.join(__dirname), {
enableBrotli: true
}));

The above code is the default code setting from the ‘express-static-gzip’ but I wanted to serve only the static files from this library. If file doesn’t exist, I wanted to throw error and my code should not go further into other routes. So, I just hacked a bit in the source code and created a new middleware file compression.js.

Below is the hacked code:

var express = require(“express”);
const expressStaticGzip = require(‘compression’); // compression.js gist is available on the github.
var app = express();

app.get('*', expressStaticGzip(path.join(__dirname), {
urlContains: ‘static/’,
fallthrough: false,
enableBrotli: true,
}));

There are three parameters that I have used here

  1. urlContains: It will check if the request original url contains the ‘static/’ in it. Then only serve files through this plugin else ignore the URL.
  2. fallthrough: It should be false to throw the error if file you are looking for doesn’t exist in the path.join(__dirname) directory and URL has ‘urlContains’.
  3. enableBrotli: It will check if Brotli file is available in the path.join(__dirname) and the appropriate headers is being requested by client then serve the .br file.

Below is the gist of the compression.js. I have hacked from the line 59-65.

Analysis of the results:

Let’s compare the performance of the website with Brotli or GZip or just uncompressed minified files.

In God we Trust, all others bring data. -W. Edwards Deming

Let’s dig ⛏ into the analysis and find the real numbers. Since this is my work place website which we used for internal purpose. I can’t share the real screenshot here but below are the real numbers that I have collected while testing the website on different type of compressions.

Select fast 3G from chrome dev tools under Network tab
  1. Brotli is ~8% ( ( 7.24–6.67 ) / 7.24) efficient than GZIP and 65.7% ( ( 19.48–6.67 ) / 19.48) efficient than Uncompressed file. If browser will not be able to serve the Brotli we have a fallback for gzip which is 62% ( ( 19.48–7.24 ) / 19.48) efficient than uncompressed file. So here we have Win Win situation.
  2. Now let’s analyse the size. Brotli is (( 586–458)/586)~21.85% efficient than GZIP and it is (( 2.5*1024–458)/2.5*1024)~82.1% efficient than uncompressed files. So lot of bandwidth can be saved by using Brotli compression.

Some data for Slow 3G network:

Select slow 3G from chrome dev tools under Network tab

Thank you all for reading so far. If you like it and want me to write more, please click on the 💚 to give me some motivation.

PS: Thanks Kuldeep for helping me out with some gaps and for guiding me through glitches in the process.

Show your support

Clapping shows how much you appreciated rachit gulati’s story.