Zadejte hledaný výraz...

LightningJS: safe, fast, and asynchronous third-party Javascript

petrx
verified
rating uzivatele
(8 hodnocení)
28. 10. 2011 12:34:52
Používáte to někdo?
http://www.olark.com/spw/2011/10/lightningjs-safe-fast-and-asynchronous-third-party-javascript/
LightningJS: safe, fast, and asynchronous third-party Javascript
Posted on October 25, 2011 by mjpizz
Why do we care about third-party Javascript embedding?
Over the last 5 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:
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 blocks document.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. Even better, get in touch with us here at Olark…we’re hiring!
https://github.com/olark/lightningjs#readme
LightningJS
LightningJS is a safe, fast, and asynchronous embed code intended for third-party Javascript providers.
Safe: ensures zero Javascript code conflicts
Globals, prototypes and mismatched library versions can cause lots of compatibility headaches for third-party code.
With LightningJS, all of that third-party code lives in its own separate window context. If the code needs to do DOM manipulation, it still has access to the original document via window.parent.
Fast: avoids blocking window.onload and document.ready
Slowdowns in embedded third-party code should never impact the original document. Traditional embed techniques can block window.onload if the server responds slowly, even when the embed itself is asynchronous.
With LightningJS, third-party server response time has zero impact on the original document. It should even be safe to embed the code at the top of the of the document for an added speed boost :)
Asynchronous: defers API calls with a simple interface
When customers are using your third-party API, asynchronicity can make usage a bit more complicated. Some libraries require manual creation of a callstack (e.g. Google Analytics var _gaq=[]; _gaq.push(...)), and others try to hook into script.onload events.
With LightningJS, the third-party namespace is immediately available as a callable function. All calls return objects that adhere to the CommonJS Promise API. Just a few modifications to the existing API will enable these deferred calls.
What does it look like?
Let's say that we are Pirates Incorporated, purveyors of all things piratey on the interwebs. When using LightningJS, our embed code will look something like this:
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 a 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!
}
})
Getting Started
The LightningJS API is pretty simple:
lightningjs.require imports third-party code
lightningjs.modules dictionary of all imported modules
lightningjs.provide exports third-party code as a LightningJS module
lightningjs.expensive designates specific exported methods to execute only after the original document is completely finished
This step-by-step should get you up and running pretty quickly:
Step 1: Create your embed code
To make your own embed code, just start with embed.min.js and then underneath that code you can simply use lightningjs.require to pull in your library. For example: if you named your library namespace "piratelib", then your final embed code might look something like this:
The extra "guards" around the code ensure that you avoid any issues with template engines (e.g. Drupal, Joomla, etc) and XHTML parsers...as a third-party provider it pays to be prepared for all kinds of document environments.
Step 2: Modify your codebase to use the parent window context
Keep in mind that your code will be loaded in its own window context. If you need to access the globals on the original document, you should grab the window.parent object. You should also use window.parent.document to manipulate the DOM.
Some older codebases may rely on implicit globals in the original document If this is the case with your library, you could try wrapping your code in a with context:
with (window.parent) {
// your existing codebase goes here
}
Step 3 (optional): Enable asynchronous calls to your library
If you want to utilize the asynchronous API that the embed code creates for your namespace, you can do so by pasting the code from lightningjs-bootstrap.min.js at the top of your codebase.
After doing this, you need to tell LightningJS which methods need to be made available for asynchronous calling using the lightningjs.provide method. For example, you could expose your fireWarningShot method like so:
/*** lightningjs-bootstrap.min.js ***/
/*** piratelib library code ***/
lightningjs.provide("piratelib", {
fireWarningShot: function(options) {
return piratelib.fireWarningShot(options);
}
});
Step 4 (optional): Force expensive methods to execute after the original document loads
Tools like Google PageSpeed and YSlow sometimes penalize pages for executing too much Javascript before the page loads. Therefore, you may want to avoid executing certain CPU or parsing intensive methods until after window.onload so that your library does not cause any execution penalty for the original document.
To avoid executing expensive methods too early, just decorate these methods using lightningjs.expensive. LightningJS will ensure that calls to these methods are delayed until after the original document completely loads:
lightningjs.provide("piratelib", {
loadToTheGunwalls: lightningjs.expensive(function() {
// this method instantiates lots of grog and iterates
// through each bottle in a loop
piratelib.stockTheGalley();
while (piratelib.bottlesOfGrogOnTheWall) {
piratelib.takeOneDownAndPassItAround();
}
}),
});
28. 10. 2011 12:34:52
https://webtrh.cz/diskuse/lightningjs-safe-fast-and-asynchronous-third-party-javascript/#reply692335
Pro odpověď se přihlašte.
Přihlásit