Optimizing Javascript with Minification and Mangling

It still amazes me how many startups and blogs I see serving javascript that hasn't been minified. If you don't use a mangler in your production build process, you're literally sending your IP across a series of tubes and into the laps of your competitors. In this article we're going to discuss what to use, to obfuscate and otherwise ready your code for production, while making it smaller in the meantime.

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 prod-js2
npm install

TLDR

We could use this article to quickly go over how to add UglifyJSPlugin to your webpack config. But what would that serve? What are we doing with Uglify? If your purpose is to get a tight production bundle without learning how it happens. Then here you go.

In your webpack.prod.js add these 2 lines like we've done before with the definition at the top of the file:

const UglifyJSPlugin = require("uglifyjs-webpack-plugin")

And the plugin with the rest of the plugins:

plugins: [...new UglifyJSPlugin()]

And of course add the package in your terminal:

npm install uglifyjs-webpack-plugin

And presto! Better bundles. But how? Well, there are a few parts to what a tool like Uglify is doing.

Concatenation

Historically, Javascript was delivered to the client in a production bundle, Webpack serves one of the purposes for that bundle. Concatenating the files, or joining them into one big file that's faster to download in an HTTP 1.1 world. With HTTP2, concatenation might be an anti-pattern as HTTP2 prefers multiple small files. We'll look at that later.

Minify & Mangling

Then we have Minify. Whereby we replace long variable names with shorter, one letter variables. This serves both the purpose of making the file size smaller, but also, obfuscating the purpose of the code from anyone looking trying to read the prod bundle. To illustrate, let's minify something via the command line.

Let's install the babel minifier:

npm install babel-minify

Copy the following code to the bottom of src/main.js for illustrative purposes.

const globalVar = true
const something = function(someArgument) {
  const longVariableName = someArgument
  const result = function(longVariableName) {
    return longVariableName * longVariableName + globalVar
  }
  console.log(result)
}

Now in the terminal run:

minify src/main.js -d dist/

You'll see dist/main.js appears and looks like this:

require("./main.css"),
  require("./content.css"),
  require("./images/link.jpg"),
  require("react"),
  console.log(`Environment is ${process.env.NODE_ENV}`)
const globalVar = !0,
  something = function(a) {
    console.log(function(a) {
      return a * a + globalVar
    })
  }

A few things happened with the minify:

  1. You'll notice that global variables, stuff in the global scope like globalVar aren't mangled, but the longVariableName are turned into a. This is possible because they are in a function scope, so as long as the name is consistent, it shouldn't matter what the variable is named. This is called variable Mangling and is why Uglify is called that. It obfuscates the code's purpose, though that's no security and can be reverse engineered with enough time and patience.
  2. Since the function longVariableName is assigned the value of someArgument minify creates a shorthand and just uses the argument variable, thereby eliminating code.
  3. Notice that true has become !0 which is 2 characters shorter and means, not false (since 0 evaluates to false). Kinda cool, kinda weird.
  4. All whitespace is removed and semi-colons are added so the Javascript parser can use it. This is where minify gets it's name, but it's worth noting the semi-colon addition, since most people don't want to have to manage semi-colons. And with Babel, you don't have to anymore.

To UglifyJS or Babel?

The art of getting the smallest file size for your particular use case means learning the options and experimenting with them. Babel-Minify works with Babel.

npm install babel-minify-webpack-plugin

At the top of webpack.prod.js:

const MinifyPlugin = require("babel-minify-webpack-plugin")

In the plugins add:

plugins: [...new MinifyPlugin()]

When we run npm run build we get a main-bundle.js that's only 816 bytes. And when we switch back to UglifyJSPlugin, we see it beats it at 811 bytes. Not much, but we have very little JS in our project so far. We can keep both plugins in the project and comment them out for now, to see the difference. But the short of it is, Babel uses Babel to parse and transform while, Uglify uses it's own parsing & mangling code seperate from Babel.

Additional Considerations

Javascript optimization is an expanding field and can turn into a bit of an art form. In some cases you'll want to do a straight up find and replace of certain code. For instance process.env.NODE_ENV can be replaced at compile time with the value (ie. "production"), since it never changes. You can see that in action in the code of the output dist/main-bundle.js.

Lots more substitutions can happen, if we're using immutable variables that don't change and precompute them.

This isn't as simple as a text Find & Replace. Since Babel is turning the code into an AST so as to fully understand how it would be compiled, it's able to ensure it's not breaking the logic while making complex optimizations.

This is some of the cutting edge work Facebook is doing with Prepack. Pre-evaluating code and scope hoisting will be covered later.

In Sum

While optimizations in Javascript can get a bit hairy, I wanted to show you the parts of the puzzle that tools like Uglify and Google Closure Compiler are using to reduce code size. Variable Mangling, Whitespace removal and code replacement are a part of that puzzle.

Next Up

There is one final point I want to make about optimization and that's compression. Compression is another subject that can get weedy pretty quick and is a very important field in Computer Science. We'll stick to what's relevant, for our purposes to make our file sizes even smaller.