Third Party JavaScript powers many of the most popular integrations on the web today, like Facebook Connect, DISQUS Comments, and Google Analytics. These products enable developers to add new features and functionality to any website by including JavaScript from the provider’s domain. Integrations can be activated by individual users via Bookmarklets or deployed across an entire Intranet by an IT team, because at their core, they are JavaScript code snippets. In Socialcast’s case, Third Party JavaScript enables Reach, our embedded activity stream product, to create an interactive social layer on any website.

Think like a Diplomat

I find it helpful to frame the Third Party JS model in terms of a diplomatic mission. As an invited guest in a foreign place, you should be both respectful and cautious of the new environment. As an emissary, you should be excited to augment the page with your own unique functionality. JavaScript gives you the opportunity to radically change an application for the better, but only when executed carefully (there is no “diplomatic immunity” for Third Party JavaScript). This article outlines some of the lessons learned from developing and supporting Reach’s JavaScript library with the Socialcast Engineering team.

The Foundation

The first step in creating a Third Party JavaScript library is to identify the core functionality. Assume the JavaScript already exists on the page and ask: “What should it do?” When we set out to build Socialcast’s JavaScript platform, the common denominator between every integration was the creation of an IFRAME based on a set of user-supplied configuration options. These configuration options would vary from each implementation and would need to be easily accessible. With that knowledge, we built a basic Reach Extension component that would serve as the foundation for our Reach API and began iterating.

This library started laser-focused: it created dynamic and configurable IFRAME elements and initialized our different Reach Extensions, but as we iterated on the product, the scope eventually expanded to include cross-domain communication and host page metadata extraction. As the feature-set and dependency tree grew, so did the size and quantity of JavaScript source files. Because this growth is inevitable as you iterate on the initial library, you’ll need to balance growth and performance early on. Organizing your code into modules that can be minified and packaged together to reduce HTTP requests is a great way to support a performant loading strategy.

Jetsetting JavaScript

Packaging your JavaScript and thoughtfully designing an API is a solid foundation, but unless you have a delivery strategy, your product is going to be First Party JavaScript. The next goal should be to enable implementors to load your script file as fast as possible without blocking or disrupting their website’s native functionality. To accomplish this, you’ll need to use a “non-blocking” script loading technique. In our testing, the most performant way to load Reach’s initial JavaScript library was to use Dynamic Script Generation, a technique popularized by Nicholas Zakas in his excellent book: High Performance JavaScript [1]. The following code example demonstrates a script element being constructed and embedded into the HEAD of the document:

(function(){
  var e = document.createElement('script'); e.type='text/javascript'; e.async = true;
  e.src = document.location.protocol + '//yourdomain.com/packaged_third_party_javascript.js';
  var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(e, s);
})();
view raw gistfile1.js This Gist brought to you by GitHub.

You may look at this script and wonder why it’s worth the extra lines. After all, it seems much more straightforward to use a “defer” attribute on a basic script element. The main benefit of dynamically generating the script element is that the browser will begin to download your JavaScript file immediately and in parallel with any other script downloads. Without it, the default browser behavior would trigger; waiting for each individual JavaScript file to load and execute in order. Because the file is by definition on a different domain, this has the potential to be slower. The best way to illustrate the difference in techniques is to use Chrome’s Timeline Profiler.  Let’s first examine the page load timeline when loading an external “extension.js” file using a traditional script element.  Note: To better demonstrate the impact of the technique, the script download will have a very high latency.

Traditional Script Include (Blocking)

You’ll notice that the traditional script include (above) causes the page to wait for the entire ‘extension.js’ script to download and execute before continuing to render subsequent HTML or resources on the page. The next screenshot is the same page load, but the ‘extension.js’ file is included with the dynamic script generation technique. Notice that the page is able to render while the script is downloading and that ‘extension.js’ is evaluated after the ’DOMContentLoaded’ event fires.

Dynamic Script Generation (Non-Blocking)

  • Note: I was a bit surprised to see the new (and very well designed) SoundCloud JavaScript SDK using the traditional script include technique in all of it’s developer examples.  Their SDK’s ‘loadJavascript’ helper uses dynamic script generation to include subsequent scripts, but the initial ‘sdk.js’ load is blocking.  My thought is that they do not want to force developers to deal with script onload callbacks, but there is an alternative that I’ll present later in this article.

In addition to using the dynamic script generation technique, your initial JavaScript download can be made even more performant by serving the files with gzip encoding (to reduce file size) and setting an Expires header.  An Expires header will allow browsers that have downloaded the file already to fetch it from cache.  At Socialcast, we chose an Expires date that was one day in the future, but each application will have different needs.  If you are serving non-volatile content, a longer Expires window may be appropriate.

  • Warning: Be careful not to set an aggressively Far-Future Expires header for the loader JavaScript. The trick is to find a balance between performance and ensuring all users are on the latest version of the library. Renaming the JS file to break cache is usually not an option for the loader script once it has been deployed, so it’s very important to get this strategy right from the beginning.

It’s a jungle out there

Now that your script has landed safely on the host page (hopefully in one piece), it’s time to explore and adapt to your new environment. Each host page that you are included on will have a unique landscape due to the fact that every script has access to write to a shared global namespace. You should treat the host page as a potentially hostile environment and, like any good traveler, rely on the tools & dependencies you packaged in advance. Any assumptions that you make about the expected behavior of seemingly “native” methods can have dire consequences if the host application has modified or overridden them.

In Socialcast’s case, one of our first prototype integrations suffered from a particularly nasty bug that our team spent a hours debugging. In the end, we discovered that the host application had over-written the browser’s “JSON” object, which resulted in malformed JSON being generated when one of our dependencies called it. After this discovery, we immediately packaged our own JSON parser/serializer methods with the library and came away with a great lesson learned about trusting “native” browser methods in the wild.

Being cautious will help protect your code, but you should also be conscious of your own impact on the host page. Any API that you “export” into the global namespace should have a single access point. In Reach’s example, it is the “_reach” global variable. Any methods on this object should expect to be publicly accessible to any other JS on the page. Utility methods and other code that doesn’t belong in your API should be kept in a private scope. Not only will this prevent namespace collisions, but it will also make your API easier to use and understand.

(function(){
  
  // Private (not accessible in global scope)
  function UtilityFunction(){}
  function ThirdPartyAPI(){}
  
  // Export a single global access point the ThirdPartyAPI
  window.thirdPartyAPI = window.thirdPartyAPI || new ThirdPartyAPI();

})();
view raw gistfile1.js This Gist brought to you by GitHub.
  • Note: Adding a “noConflict” option to your API is a good way to allow implementors to control naming collisions. Example: jQuery.noConflict

Initialization

One of the more interesting problems to solve with Third Party JavaScript is the initialization flow. Most applications require user-defined configuration options in order to initialize (for example: an access token or target element). Because we chose to load the library asynchronously, the challenge is determining when the newly executed JavaScript is available for use. In Socialcast’s case, we decided that every Reach snippet should be responsible for:

  • Finding the global “_reach” namespace if it exists already.
  • Creating the global “_reach” namespace if it did not exist and setting it as an empty Array.
  • Calling _reach.push() with an object of Reach Extension options.
var _reach = _reach || [];
_reach.push({
  container: 'corp_stream',
  domain: 'http://socialcast.com/',
  token: '12345'
});
view raw gistfile1.js This Gist brought to you by GitHub.

If our JavaScript library loads after this configuration snippet is executed, it will read and store the Array of options and re-define the global “_reach” variable to be an instance of our Reach Class. This class also has a “push” method that initializes Extensions, allowing us to have a consistent API without complicated callbacks and, most importantly, embracing the asynchronous loading strategy.

If your initialization process includes creating IFRAME elements, consider exposing a configuration option for waiting for the DOMReady event. Any IFRAME creation will block page load and spawn additional requests for content & assets, so it’s best to wait until the host page has been fully parsed and downloaded. In Reach’s case, we expose a “DOMReady” option that is true by default, but also allow immediate initialization.

Once you’ve loaded your JavaScript library into the host page and kicked off initialization, it’s time for your application to shine. There are many challenges related to co-existing with other JavaScript in the host page after initialization, so I encourage anyone interested in Third Party JS to follow some of the more popular projects and check out the upcoming Third Party JavaScript book.

References: