Third Party JavaScript

|   Nov 16, 2011

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:

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.

  • 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.

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:

Comments

  • In Errorception – a JS error tracking service I’m building – I decided to up it a notch. The problem with both async and defer is that they still impact the window load events even though downloads are parallelized. So, if the third-party happened to have any delay in serving the JS file, $.ready (and similar such functions) would be delayed. Now, sometimes this is a good thing (if you want to guarantee the availability of the API), but in my case this is an annoying delay that messes up end-user experience.

    So, in Errorception’s case, the script is not included until page load is fired, which is far later in the page load cycle, ensuring that the page’s JS has had no performance impact by the inclusion of the snippet.

    I’ve written up a blog post about it here: http://blog.errorception.com/2011/08/reliable-high-performance-js-error.html and here’s the code if you want to jump to it straight away: https://gist.github.com/1147102#file_errorception_tracking_script_verbose.html I’d be interested in knowing what you think about it.

    Commented on November 18, 2011 at 2:07 pm
    • Thanks for sharing, Rakesh. Errorception is a great product idea – glad to see it raises visibility into errors on JS clients.

      With regard to your loading strategy comment:
      In our cross-browser testing with the dynamic script generation technique ( https://gist.github.com/1355790 ), timeouts, high latency, and 404 responses for the script did not negatively impact DOMready. For example:
      Setup:
      – Use the dynamic script generation technique and ensure the script to load is either a large file or has artificial latency applied.
      – Include a binding to ‘onload’ or ‘load’ near the top of the file that alerts a message.
      – Include an alert at the end of your large script file.

      Running this test in IE7 -> Chrome latest results in a DOMready call that is not blocked by timeouts or 404s for the script file (the script is executed seconds after DOMready). Chrome Introspector backs this smoke testing up with their timeline feature.

      I agree with you that waiting for DOMReady to download third party content has it’s benefits: you can guarantee that additional HTTP requests or self-initializing JS will not negatively impact page load (some snippets kick off asset downloads & other intensive operations immediately). This is why we’ve split our loading strategy out:
      – The core third party loader is downloaded asynchronously during page load (assuming a cold cache) and does not block onload.
      – When this small loader JS is executed, it waits for DOMready (by default) before initializing the main functionality (creating IFRAMEs, requesting our Integration content). We want to wait until after DOMready to kick off additional HTTPS requests or asset downloads.

      We feel that this is a good balance between performance and user experience, as waiting for DOMready to start downloading the loader script has the potential to delay the initial HTTP request by 1-10+ seconds (depending on the host page’s original DOMready timeline). This would shift the download out too far for our product’s intended use case without much impact (single HTTP request) to page load.

      Commented on November 21, 2011 at 2:11 pm
  • I wrote up a post a few months ago on whether it’s safe to use third party JavaScript or not: http://tech.bluesmoon.info/2011/04/how-much-do-you-trust-third-party.html

    In general, you should really know how much you have to lose before you go in for third party JavaScript. The more you have to lose, the better it is to build it yourself, or at least host it yourself.

    Commented on November 21, 2011 at 2:13 pm
    • Thanks for sharing your article Philip. I think those are very important points to consider before including any third party JS – especially the intentions & reliability of the provider. I do think that hosting a copy of third party libraries can be risky, as it introduces an intermediate step between new features/fixes and your users, but I can see where you are headed with the idea (balancing bleeding edge features w/ approval & QA for Third Party content). Personally, I think once a third party provider has proven to have good intentions and be reliable, it becomes easier to trust them and include their content. Your message is a good one though – you should not include content without careful review!

      Commented on November 21, 2011 at 2:15 pm

Leave a comment

Your email address will not be published. Required fields are marked *

Connect with Facebook

Sign up to receive email communications regarding events, webinars, and product news.

What is Socialcast?

Socialcast by VMware (NYSE: VMW) is a social network for business uniting people, information, and applications with its real-time enterprise activity stream engine. Behind the firewall or in the cloud, Socialcast enables instant collaboration in a secure environment. Socialcast is headquartered in San Francisco, California. www.socialcast.com