Writing third-party Javascript

the integration part in a nutshell

All my latest articles can be found at the RisingStack blog.

What is third-party javascript?

Third-party JavaScript is a pattern of JavaScript programming that enables the creation of highly distributable web applications. Unlike regular web applications, which are accessed at a single web address, these applications can be arbitrarily loaded on any web page using simple JavaScript includes. — Ben Vinegar, Anton Kovalyov (Third-party Javascript)

Our goal

Our goal was to provide a library to our customers that is modular, lightweight and supports browsers from IE8.

The interface

Our solution was inspired by the new analytics.js by Google. Analytics.js provides the following interface:

ga('set', 'page', '/my-new-page');

As you can notice, Google is not using the following form:

ga.set('page', '/my-new-page')

This comes handy when our partners want to make calls to the library even before it is loaded. The other advantage of this approach is that “infinite” number of parameters can be passed to it. All you need is a little snippet that stores the arguments until the library is loaded. Speaking of which, this is how the snippet looks like:

<script>
(function (window, document, tag, url, name, a, m) {
window[name] = window[name] || function () {
(window[name].q = window[name].q || []).push(arguments)
}, window[name].l = 1 * new Date();
a = document.createElement(tag),
m = document.getElementsByTagName(tag)[0];
a.async = 1;
a.src = url;
m.parentNode.insertBefore(a, m)
})(window, document, 'script', '//www.your-awesome-lib.com/lib.js', 'awesomelib');
</script>

This creates an array (called q), and pushes the arguments of each call to this array. The job of our script will be to override this function and process the items from the queue.

Before the script is loaded

After the above snippet is loaded, you can do things like:

awesomelib('set', 'car', {
brand: 'Tesla',
name: 'Model S'
});

The queue created by the loaded script starts filling up with commands. As we push further arguments into it, it will become an array of argument arrays. This way, all of our past commands can be processed later on (as soon as our library is loaded).

Here we go

Our awesome new library is loaded. First things first, it has to process the items in the queue. After that, it overrides the awesomelib function, so it will point to a function inside our library. As javascript runs in a single thread, the swap will be synchronous and will not cause any problems.

Let’s take a look at the processing part! You can use something similar to process your queued items:

processQueue = function () {
var items = window.awesomelib.q;
// iterate over your items, and process them
};

After it is done, you have to swap the original function created by the include snippet and the just loaded one:

command = function () {
var args = [].slice.call(arguments);
}
window.awesomelib = command;

This way calls into your library stay the same, the users of your library won’t notice any change in behaviour. Now you are ready to deal with your business logic! ☺

Things to remember

1. Respect the global scope

Although in the snippets above was not indicated, you should always pay attention what variables you are leaking into the global namespace. In our case it is only the nameofyourlib function. To avoid this, I recommend using the revealing module pattern. With the pattern applied, our command library’s skeletons would look something like this:

(function(window){
var command, save;
save = function () {
// domething here
};
command = function () {
var args = [].slice.call(arguments);
//do something here...
//save can be called from here
};
window.awesomelib = command;
}(window));
//save cannot be called from here

2. Size matters

This is pretty simple: you want your library to load as fast as possible. Try to cut down the number of dependencies to the absolute minimum. For example if you don’t need all the functionality underscore provides, then go with lodash, and include only what you need. Also, use tools like minify/uglify and gzip.

3. Modularity is king

It is common sense. Your growing codebase will be harder and harder to maintain it, period. But if you have modules created for one purpose and one purpose only, and they are covered with tests your life will be happier.


Further reading:

More on design patterns in Javascript from Addy Osmani: http://addyosmani.com/resources/essentialjsdesignpatterns/book/

More on writing third-party javascript: http://manning.com/vinegar/