Why care about third-party Javascript embedding?
Over the last five years or so, embedding Javascript code has become the norm. Much of this code is delivered by third-party services like Google Analytics and others. In fact, I just checked this morning and the Olark website embeds at least six separate third-party Javascript tools ranging from website analytics, to A/B testing frameworks, to commenting systems…and of course our very own chat box.
The advantages are obvious: we didn’t have to write a single line of code. Nor did we have the operational headache of spinning up those services on our own machines. By simply dropping in a bit of embedded Javascript, the third-party code connects to its own services and “just works”. The process is easy enough that even non-technical people can usually add embed code easily via their CMS admin panels (e.g. WordPress).
The disadvantage is that each of these embedded Javascript libraries can add overhead to the original website. Slowdowns can (and do) happen if the third-party servers are slow to deliver the code to the browser. Even asynchronous embed techniques will still block the window.onload
event until the third-party code finishes downloading.
Additionally, as a third-party Javascript provider, you need to worry about whether your customers have embedded other libraries that might conflict with yours. These conflicts can range from changing globals to adding prototypes, and even overriding native functions - at Olark we have seen websites that override both window.escape
and the native JSON decoders.
What can we do?
We need a way to embed Javascript code that gives us the following benefits:
- Safe: gives our code a context that is safe from Javascript conflicts
- Fast: does not affect the loading speed of the parent page (including
window.onload
) - Asynchronous: still allows our Javascript functions to be called easily. Fortunately, Meebo already blogged in detail about their solution nearly a year ago. Awesome! There were a few things missing from the example code though:
- relied on the Meebo build system
- missing public tests and benchmarks
- left out the “other half” of the system (the bootstrapping portion for the actual library)
We even built upon this embed code internally at Olark, adding a few minor fixes and the bootstrapping necessary for our asynchronous API calls. Last week we spent some time extracting the core concepts and distilling it down into a single reusable codebase…and we are pumped to finally to release it to the community :)
Introducing LightningJS
LightningJS allows third-party providers to deliver their Javascript in a way that is safe (each library gets its own window
context while still having access to the original document), fast (does not block window.onload
), and asynchronous (exposes an easy way to asynchronously call methods). You can get more detailed information and source code from lightningjs.com. Here is a brief look at LightningJS in action…
What about an example?
Let’s say that we are Pirates Incorporated, purveyors of all things piratey on the interwebs. When using LightningJS, we can tell our customers to paste code like this into their HTML page:
<script type="text/javascript">
/*** the code from lightningjs-embed.min.js goes here ***/
window.piratelib = lightningjs.require("piratelib", "//static.piratelib.com/piratelib.js");
</script>
Our customers can call methods on piratelib
immediately, even though none of our code has actually loaded yet:
piratelib("fireWarningShot", {direction: "starboard"})
This calls the fireWarningShot
method on our API. At some point, we decide to return a value to our customers that indicates whether the warning shot was seen. We also decide to throw exceptions in cases where the warning shot failed. Since LightningJS already implements the CommonJS Promise API, we can use the .then(fulfillmentCallback, errorCallback)
method to handle return values and exceptions:
piratelib("fireWarningShot", {direction: "starboard"}).then(function(didSee) {
if (!didSee) {
// arrr, those landlubbers didn't see our warning shot...we're no
// scallywags, so run another shot across the bow
piratelib("fireWarningShot", {direction: "starboard"});
}
}, function(error) {
if (error.toString() == "crew refused") {
// blimey! it's mutiny!
}
})
What about the hard data?
Exhaustive browser support is probably the most important in terms of our measurement. To that end, the included test cases pass in every browser we could get our hands on:
- Firefox 2+ (tested in 2.0, 3.0, 3.6, 4.0, 5.0, 6.0, 7.0)
- Chrome 12+ (tested in 12, 13, 14, 15)
- Internet Explorer 6+ (tested in 6, 7, 8, 9)
- Safari 4+ (tested in 4.0, 5.0, 5.1)
- Opera 10+ (tested in 10, 11.5)
- Mobile Safari 5+ (tested in 5.0, 5.1)
...and for all you practical folks out there, it should help knowing that embed techniques used in LightningJS have been battle-tested in the wild by both Olark and Meebo across thousands of websites and browsers. We also attempted to benchmark the performance of the LightningJS embed code under the worst-case scenario where third-party server performance is the bottleneck. To achieve this, we contrived a page with built-in delays that would ideally:
- fire
document.ready
after ~1s - fire
window.onload
after ~2s - finish downloading the third-party code after ~5s
Timing this benchmark was a bit difficult over a tunneled connection (we used the otherwise excellent BrowserStack to run them), but the results demonstrated that LightningJS always had better or equal behavior to traditional embed codes. In the modern browsers we tested (all versions of Firefox, Chrome, Safari, and Mobile Safari), LightningJS always bested the traditional asynchronous embed code by not blocking the window.onload
event:
Event | Traditional Synchronous | Traditional Asynchronous | LightningJS |
---|---|---|---|
document.ready | ~5s | ~1s | ~1s |
window.onload | ~5s | ~5s | ~2s |
third-party loaded | ~5s | ~5s | ~5s |
We saw similar improvements in Internet Explorer, though due to caching we could not measure whether LightningJS was better or equal to the traditional asynchronous approach. In Opera, the results were even better - it appears that traditional asynchronous code actually blocksdocument.ready
as well. LightningJS never blocked document.ready
, though it seems that none of the embed codes can avoid blocking window.onload
in Opera.
What’s next?
There are a lot of third-party services out there. We certainly hope that they will take some notice of LightningJS and start taking advantage of the benefits it provides. As a customer, it probably wouldn’t hurt to try asking them :) If you have some ideas on how to tighten up the compressed embed code and any other improvements, don’t forget to fork LightningJS on GitHub.