How I reduced my javascript library size from 140K to 56K

collar.js

I created collar.js last year, it is a reactive (functional) programming library to help you organise your software’s architecture, data flow, and business logic. It works both on browser and node.js. Before the version 1.0, the minified version is about 140k. Recently, I worked on an e-commerce wechat mini program, unfortunately, it has a size limitation of less than 1MB. Compare to this limit, 140k is too much. So I started to reduce the size of collar.js, and finally got a 56k version (11k for gzip version). Here is how I achieved it and some practises:

Identify 3rd party libraries

Generally, your application consist of two parts: your own code and 3rd party libraries (the dependencies). You need to pay attention to your usage of 3rd party libraries, most of the case, you don’t use all of their functionalities. That’s the part that we want to cut off from our code.

For example, I used Immutable.js in collar.js. After a search of the usage in the code, I found that I only used 7 functions of Immutable Map: fromJS(), toJSON(), get(), set(), getIn(), setIn(), and deleteIn(). The rest of the APIs is useless for collar.js. So I need to cut them off from the code base.

Another example is the event emitter. I used the eventemitter3 in collar.js as it claimed to be one of the fastest implementation in their readme file. But when I double check the usage in my code base, I found that I only use two APIs: emit() and on() . So I also have some spaces here to reduce.

I will not list all the dependencies here, but I think you got the idea.

Wrap the 3rd party libraries with a wrapper class

Once you have identified the usage of your 3rd party library, you can wrap the API/data structure with a wrapper class/object, delegate the implementation to your 3rd party library. And replace all the usage with your wrapper class. The idea here is to make the wrapper class the only entry point in your code to use 3rd party library.

Sometimes, it is not easy to wrap the API exactly as it is, you can always create a high level API and hide all the usage of 3rd party library inside of it. For example, some of the immutable APIs will return an immutable object, which requires that you create different wrappers for these immutable types. To avoid it, you can create an API which returns the plain javascript object.

const Immutable = require('immutable');
class ImmutableWrapper {
constructor(obj) {
this._immu = Immutable.fromJS(obj);
}

getIn(path) {
let value = this._immutable.getIn(path);
if (Immutable.Iterable.isIterable(value)) return value.toJS();
return value;
}

...
}

Now each of your 3rd party library has its own wrapper class, to better manage them, I suggest you create a dependencies.js file to host them. It becomes the entry point to all your 3rd party dependencies.

// dep.js
module.exports = {
Immutable: require('./ImmutableWrapper'),
Eventemitter: require('./EventEmitterWrapper')
...
}

It looks like an index of 3rd party library:

You can build your application and rerun all the tests to verify if every thing still works fine.

Replace your 3rd party code

Now, we have successfully decoupled our own code and the 3rd party code. Time to replace them with smaller implementations. There are two strategies here:

  1. Use a smaller 3rd party library. It could be a library that have smaller footprint implementation, or a library has less functions, but fulfils all your needs.
  2. For some libraries, it is not hard to reimplement them by your own hands

For collar.js case, I replaced Immutable.js with another library called freezer. And I reimplement the event emitter with the following code:

class EventEmitter {
constructor() {
this.listeners = {};
}

on(msg, listener) {
if (!Object.prototype.hasOwnProperty.call(this.listeners, msg)) {
this.listeners[msg] = [];
}
const list = this.listeners[msg];
list.push(listener);
}

emit(msg, value) {
if (!Object.prototype.hasOwnProperty.call(this.listeners, msg)) return;

const list = this.listeners[msg];
list.forEach(l => l(value));
}
}

module.exports = EventEmitter;

I did the similar things for other 3rd party dependencies. After replaced those 3rd party libraries, the size reduced from 140k to 85k (minified), 14.8k (gzipped)

Don’t forget to rerun the test to make sure your change does not broke anything.

Reduce the size of your own code

There are a lot of coding techniques to make your code less redundant or less verbose, but I found that doesn’t help a lot to reduce the size of code while keeping the readability of the code.

The method I used might not be suitable for all applications, but it is a very efficient one: cut the functionalities. As collar.js is a reactive library, most part of the code is operators, like map() , filter() . I listed all the operators and grouped them by categories, all these categories are referenced in one single file, so it is very easy to customise. This is pretty much like the boostrap customization.

The customised one for the e-commerce wechat mini program I worked on is 56k (minified version), 11k (gzipped). There are still spaces for further size reduction, but it is ok for the e-commerce wechat mini program, so I just stopped there.

Summary

The key of the reduction is to separate the 3rd party code from your own code by creating a dependency index with a dep.js index file and a series of wrapper classes. You can benefit a lot with this practice. For example, I applied the same technique in the latest version of collar.js-dev-client and collar-dev-server. I can easily change the websocket dependency from socket.io to websocket-node without touching one line of my own code.

I suggest to create a wrapper class for all the 3rd party dependencies, and create an index file for all those wrapper classes. This is a very good practise to help you understand the functionalities that your code really depends on. And it also gives you the flexibility to easily change your dependencies from one to another. Sometimes, you are introducing a very large library in your project but using a very small part of it. For example, jquery, I usually use the selector and event listener part of it, but it is very easy to replace them with vanilla javascript with a few lines of code.

If you like this post, please click the heart to share it :D Thanks!