Uglify vs. Babel-minify vs. Terser: A battle for code compression

Ugliffe -vs- Babel-minify -vs- Terser – A -mini- Battle – Royale

What is shrinking?

Minification (also minimization) is the process of removing all unnecessary characters from the source code of an interpreted programming language or markup language without changing its functionality. These unnecessary characters are usually included

  • White space characters
  • A newline
  • comments
  • Block delimiter

Let’s try to understand this with an example. The following code shows a sample JavaScript code that creates an array and initializes it with the first 20 integer values:

var array = []; forVar I = 0; i < 20 ; I ++) {array [I] = I; }Copy the code

Now, let’s try to shrink this code manually. The following example shows how we can achieve the same functionality with just one line of code:

forVar a = [I = 0]; ++ i < 20 ; A [I] = I;Copy the code

First, we reduce the name of the array variable (Array to A), and then we move it to the loop initialization construct. We also move the array initialization in line 3 into the loop. As a result, the number of characters and file sizes are significantly reduced.

Why shrink?

Now that we know what shrinking is, it’s easy to guess why we do it. Because shrinking reduces the size of the source code, it becomes more efficient to transfer over the network.

This is especially useful for Web and mobile applications, where the front end makes HTTP requests to the back end to retrieve resources such as files, user data, and so on. For fairly large applications, such as Instagram or Facebook, the front end is typically installed on the user’s device, and the back end and database exist as local servers or multiple instances in the cloud.

In a typical user operation such as loading photos, the front end makes an HTTP request to the back end, which in turn makes a request to the database instance for the requested resource. This involves transferring data over a network, and the efficiency of the process is proportional to the size of the data being transferred. This is where miniaturization comes in handy.

So how do you zoom out?

In the previous section, we saw how to manually shrink a simple code. But this is not actually a scalable solution for a large code base. Various build tools have been available for years to shrink JavaScript code. Here’s a look at some of the most popular solutions:

UglifyJS

The goal of UglifyJS is to shrink and compress code. Let’s go ahead and install it using the following command:

npm install uglify - js - g

Copy the code

Now let’s try running Uglify on a JavaScript module. To do this, I wrote a sample module with the following code: sample.js

var print = "Hello world! Let's minify everything because, less is more"Var apple = [1, 2, 3, 4, 5]for(let element in apple) {
    console.log(print)
    console.log(element)
}

for(let element in apple) {
    console.log(print)
    console.log(element)
}

for(let element in apple) {
    console.log(print)
    console.log(element)
}

for(let element in apple) {
    console.log(print)
    console.log(element)
}

for(let element in apple) {
    console.log(print)
    console.log(element)
}

for(let element in apple) {
    console.log(print)
    console.log(element)
}

for(let element in apple) {
    console.log(print)
    console.log(element)
}

for(let element in apple) {
    console.log(print)
    console.log(element)
}

for(let element in apple) {
    console.log(print)
    console.log(element)
}

for(let element in apple) {
    console.log(print)
    console.log(element)
}

for(let element in apple) {
    console.log(print)
    console.log(element)
}

for(let element in apple) {
    console.log(print)
    console.log(element)
}

for(let element in apple) {
    console.log(print)
    console.log(element)
}

for(let element in apple) {
    console.log(print)
    console.log(element)
}

    
Copy the code

This code basically prints strings and arrays in a loop. We copied the for() loop several times to increase the size of the file so that we could see Uglify’s effects better.

The file size is 1.2KB

UglifyJS options

As you can see, Uglify has a lot of options, most of which are self-explanatory. So let’s go ahead and try a few of them:

The file size is 944B and is executed by repeatedly printing the string and array values

We used and modified the -c (compress) and -m (mangle) options on the file. The file size is reduced to 944B, a reduction of about 22%. Now, let’s look at the file content and see how it is changed via uglification: -c -m sample.js

var print="Hello world! Let's minify everything because, less is more"Apple = [1, 2, 3, 4, 5];for(i=0; i<5; i++)console.log(print),console.log(apple[i]);for(i=0; i<5; i++)console.log(print),console.log(apple[i]);for(i=0; i<5; i++)console.log(print),console.log(apple[i]);for(i=0; i<5; i++)console.log(print),console.log(apple[i]);for(i=0; i<5; i++)console.log(print),console.log(apple[i]);for(i=0; i<5; i++)console.log(print),console.log(apple[i]);for(i=0; i<5; i++)console.log(print),console.log(apple[i]);for(i=0; i<5; i++)console.log(print),console.log(apple[i]);for(i=0; i<5; i++)console.log(print),console.log(apple[i]);for(i=0; i<5; i++)console.log(print),console.log(apple[i]);for(i=0; i<5; i++)console.log(print),console.log(apple[i]);for(i=0; i<5; i++)console.log(print),console.log(apple[i]);for(i=0; i<5; i++)console.log (print),console.log(apple[i]);for(i=0; i<5; i++)console.log(print),console.log(apple[i]);for(i=0; i<5; i++)console.log(print),console.log(apple[i]);

Copy the code

From the example above, we can see that the output has the same code, without any Spaces or newlines.

To further understand the effects of Uglify, let’s write a sample JS code using a prototype function:

// comments 

function sample(helloworld) {
  this.hello = helloworld
}
sample.prototype.printvar = function()
{
  console.log(this.hello)
}


var hello = "hello world"
var s = new sample(hello)
s.printvar()
 
 
Copy the code

Now, let’s compile this code:

Uglified code is 131B.

Note that I used the Uglify (included) option. This option embeds everything in a large function with configurable parameters and values. To understand what this means, let’s look at the output: -e

!function() {function o(o){this.hello=o}o.prototype.printvar=function(){console.log(this.hello)}; new o("hello world").printvar()}();


Copy the code

In the uglified output we can see that the function name sample has disappeared and has been replaced with O. All code is contained in one large function, which further reduces the size of the code at the expense of readability.

Now, let’s take a look at another popular minify: Babel-Minify.

Babel-minify (also known as Babili)

Babel-minify, formerly known as Babili, is an experimental project that attempts to use Babel’s toolchain (for compilation) to do something similar: shrink. It is currently 0.x and is not recommended for production use in official repositories.

Why do we need Uglify when we already have it? If you noticed the previous example, I didn’t use the latest ECMAScript syntax. That’s because Uglify doesn’t support it yet – but Babel-Minify does.

This is because it is just a set of Babel plug-ins, and Babel has learned the new syntax of the parser Babylon. Also, when you can locate only browsers that support newer ES functionality, your code size can be smaller because you don’t have to transform and then shrink it.

Before babel-Minify, we’ll run Babel to transform ES6, and then run Uglify to shrink the code. With Babel-Minify, this two-step process basically becomes one step.

Babel-minify is known to ES2015 + because it is built using the Babel toolchain. It is written as a set of Babel plug-ins with babel-Preset – Minify consumables. Let’s look at an example.

Let’s use the following command to install Babel and Babel presets locally to convert ES6:

npm install - save - dev @babel / core @babel / cli
 npm install - save - dev babel - plugin - transform - es2015 - classes

Copy the code

Now, let’s write one in ES6 syntax: sample.js

//ES6 Syntax

class sample {
  constructor(helloworld) {
      this.hello = helloworld
  }
  printvar() {
    console.log(this.hello)
  }
}

var hello = "hello world"
var s = new sample(hello)
s.printvar()

Copy the code

Let’s convert this code using the following command:

function _classCallCheck(instance, Constructor) { if(! (instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); }}function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); }}function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }

//ES6 Syntax
let sample = function () {
  function sample(helloworld) {
    _classCallCheck(this, sample);

    this.hello = helloworld;
  }

  _createClass(sample, [{
    key: "printvar",
    value: function printvar() { console.log(this.hello); }}]);returnsample; } (); var hello ="hello world";
var s = new sample(hello);
s.printvar();

Copy the code

The converted code looks like this:

As you can see, ES6 class syntax has been converted to regular function syntax. Now, let’s run Uglify on this content to zoom it out:

uglifyjs sample-transpiled.js -c -m -e -o sample-transpiled-uglified.js

Copy the code

Now, the compressed content looks like this:

function _classCallCheck(e,l){if(! (e instanceof l))throw new TypeError("Cannot call a class as a function")}function _defineProperties(e,l){for(var n=0; n<l.length; n++){var r=l[n]; r.enumerable=r.enumerable||! 1,r.configurable=! 0,"value"inr&&(r.writable=! 0),Object.defineProperty(e,r.key,r)}}function _createClass(e,l,n){return l&&_defineProperties(e.prototype,l),n&&_defineProperties(e,n),e}let sample=function() {function e(l){_classCallCheck(this,e),this.hello=l}return _createClass(e,[{key:"printvar",value:function(){console.log(this.hello)}}]),e}(); var hello="hello world",s=new sample(hello); s.printvar();Copy the code

If we compare these file sizes, sample.js is 227B, sample-transpiled. Js is 1KB, and sample-transpiled-uglified. It is obvious that this is not the best process because it leads to an increase in file size. To solve this problem, Babel-Minify was introduced. Now, let’s install Babel-Minify and try to convert and shrink the code.

npm install babel-minify --save-dev
Copy the code

Next we use the following command to compress the same sample.js

minify sample.js > sample-babili.js
Copy the code

We can look at the output after execution:

class sample{constructor(l){this.hello=l}printvar(){console.log(this.hello)}}var hello="hello world",s=new sample(hello); s.printvar();Copy the code

This file is 135B in size and is nearly 40% compressed, which is a better way to compress the code. It directly improves the efficiency of sending it over the network and runs in many browsers because Babel can transform code. There are also various plug-ins available.

Terser

Terser, the ES6+ JavaScript parser and Mangler/Compressor toolkit, is probably the most efficient. Terser recommends that you package it with Rollup, which results in smaller code.

Rollup is a WebPack-like module wrapper that was created to build flat distributable versions of JavaScript libraries as efficiently as possible, leveraging the clever design of ES2015 modules. ES6 modules will eventually be implemented natively in the browser, but the current Rollup gives you an early taste.

While Rollup is a good choice, if you are using WebPack > V4, Terser will be used by default. Terser can be enabled by toggling Boolean variables, as shown below:

module.exports = {
  //...
  optimization: {
    minimize: false}};Copy the code

Install Terser

npm install terser -g
Copy the code

The Terser command line has the following syntax:

terser [input files] [options]
Copy the code

Terser can take multiple input files. It is recommended that you pass the input file first and then the options. Terser parses the input files in sequence and applies any compression options.

These files are parsed in the same global scope – that is, references from a file to a variable/function declared in another file will be correctly matched. If no input file is specified, Terser will read from STDIN.

If you want to pass the option before entering the file, separate the two with a double dash to prevent the input file from being used as an option parameter:

terser --compress --mangle -- input.js
Copy the code

Now let’s try running our sample.js code:

terser -c toplevel,sequences=false --mangle -- sample.js > sample-terser.js
Copy the code

Here’s what this output looks like

new class{constructor(l){this.hello=l}printvar(){console.log(this.hello)}}("hello world").printvar();
Copy the code

We can see that the output from all the tools we’ve seen so far is by far the best. The file size is 102B, nearly 55% smaller than the original sample.js size. You can use the –help option to find additional command-line options for Terser.

Terser command line option. Of all the options we are most interested in are –compress and –mangle, each of which has its own set of options. The –compress and –mangle options give you control over how the source code is processed to produce reduced output.

If you noticed, we already used the — COMPRESS top-level and sequence options in the first Terser example. For example, you can pass true to the — COMPRESS drop_console option to remove all console.* functions from the source code, or use the keep_classnames option if you don’t want to break classnames.

Sometimes it can be useful to beautify the generated output. You can do this using the — Beautify option. Many build tools use Terser – find them here.

Let’s try using the drop_console option in the source file to see if the console.log() function has been deleted:

terser --compress drop_console=true -- sample.js > sample-drop-console.js
Copy the code

Now, let’s look at the source code, sample.js:

//ES6 Syntax

class sample {
  constructor(helloworld) {
      this.hello = helloworld
  }
  printvar() {
    console.log(this.hello)
  }
}

var hello = "hello world"
var s = new sample(hello)
s.printvar()
Copy the code

Now output sample-drop-console.js:

new class{constructor(r){this.hello=r}printvar() {}},"hello world").printvar();
Copy the code

As we can see, the code is further broken and compressed, and the new file is only 79B in size. This is a 65% reduction compared to the 55% you would see without the Drop_console option. This way, we can use options to balance readability and performance depending on the requirements of the project.

Performance comparison

So far, we’ve looked at three of the most popular tools for compressing JS and CSS code. The Babel repository comparison benchmark results provide these statistics to help you choose the right compression tool for your project.

From the diagram above, we can see that Terser performs best in React based projects. You can also view the results of other Web frameworks.

Configure miniFIERS for React projects

In this section, we’ll show you how to configure minifier for the React application. Let’s use Terser to shrink the React application in this example. To achieve this in a simplified way, we use WebPack. Webpack is a tool chain for bundling all files into a file called bundle.js that can be loaded efficiently. Let’s install webPack using the following command:

npm install webpack --save-dev
Copy the code

We also need to install some Babel plug-ins to convert the code:

npm install babel-core babel-loader babel-preset-env babel-preset-react\babel-preset-stage-0 --save-dev
Copy the code

Next, let’s install the Terser plugin:

npm install terser-webpack-plugin --save-dev
Copy the code

We also need loaders for.svg and.css, so we’ll install them as well:

npm install svg-inline-loader --save-dev
npm install css-loader --save-dev
Copy the code

At this stage, we just need to configure the webpack.config.js file. For details, see the following:

const path = require('path');
const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
  entry: "./src/index.js",
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  mode: 'development',
  module: {
    rules: [
    {
      test: /\.js$/,
      exclude: /(node_modules)/,
      use: {
        loader: 'babel-loader',
        options:{
          presets: ['@babel/preset-react'}}}, {test: /\.svg$/,
      loader: 'svg-inline-loader'
    },
    {
      test: /\.css$/,
      use: ['style-loader'.'css-loader'],
    }]
  },
  optimization: {
    minimizer: [new TerserPlugin()],
  },
}

Copy the code

From the code above, we can see that the entry file for webpack is index.js in SRC /, and the final output will be stored as bundle.js in the dist/directory. The optimization field pulls the TerserPlugin into the narrowing process. Now, let’s run WebPack to statically build our application for production.

Output from Webpack.

We can see that WebPack runs the Loader and plug-ins on all the files and builds a bundle.js with a size of 938KB, and our entire application is much larger than that. This is the real power of WebPack and its associated loaders and plug-ins.

A number of new bundles have been launched recently. Of these, Rollup and Parcel are increasingly popular. The basic configuration and setup of any bundled tool is similar to WebPack. You can find performance comparisons between WebPack, Rollup, and Parcel here, linked here.

conclusion

Finally, let me close by showing a snippet of NPM trends. We can see that Terser has gained tremendous popularity over the past six months. This can be directly attributed to its better performance compared to other miniaturization tools.

Thank you for reading this article. I hope you’ve learned a little bit about compressing code and that some long-standing puzzles have been clearly resolved.