The First Rule
Understanding and assimilating this rule gives you two principle considerations when designing your script.
- The impact of adding your script should be minimal. Preferably none.
- You cannot make any assumptions about how the page is coded.
Let's start with the first point. How can you make the impact of your script minimal? A couple of considerations come to mind.
You should ideally have no global variables in your code. Making sure this happens is rather simple. Firstly, ensure that you enclose your code in a self-executing anonymous function. Secondly, pass your code through a good lint tool to ensure that you've not used any undeclared variables, since undeclared variables will cause implicit global variables. This is also regarded as a general best-practice for JS development, and there's absolutely no reason you shouldn't adhere to it.
A typical self-executing anonymous function looks as follows, in its most minimal form:
To do this slightly better, I recommend the following form instead:
In the pattern above, the
document objects — both rather frequently used — become local-scope variables. Local variables are usually reduced to one or two letter variable names when passed through the most popular JS minifiers, so the size of your code reduces somewhat by using this pattern. I'll come back to the
undefined variable in just a bit. Once minified, your code will look something like the following (using closure-compiler in this case). Notice how
document have been reduced to single letter variable names.
Wait, what? No globals?
Ok, there are some cases when a global variable is absolutely necessary. Since linkage in JS happens through global variables, it might be necessary to add a global variable just to provide an API namespace to your users. Here's what the most popular third-party snippets do:
- Google Analytics exposes a
_gaqvariable (docs) with one method on it —
- Facebook exposes a
FBvariable (docs), which is the namespace for their API. (Using FB also requires you to define a global
fbAsyncInitfunction, which could've been avoided. I guess they're doing what they do for ease of use, even though it's against best practice.)
- Twitter @Anywhere exposes a
twttrvariable (docs), which like FB is their API's container namespace.
- For completeness, Errorception exposes a
_errsvariable. We currently do not have an API (coming soon!), but this has been left as a placeholder.
Take a moment to think about those variable names. They are pretty unique to the service-provider, and will usually not conflict. The possibility of conflict cannot be completely avoided, but can be reduced significantly by picking one that's unique to your service. So, exporting a global of
_ is just a horrible idea, however easy-to-type it may seem.
No modifications to shared objects
Several objects are shared in the JS runtime — even more than you might imagine at first. For example, the DOM is shared, but then so are the
Number objects. Do not modify these objects either by altering their instances directly or by modifying their prototypes. That's simply not cool.
The only cautious exception to this rule might be if you need to polyfill browser methods. In all honesty, I would avoid using polyfills as much as possible in my third-party code and I don't recommend it at all, but you could do this if you are feeling adventurous. The reason I wouldn't recommend it is two-fold:
- The code on the page may make assumptions about browser capabilities based on browser detection rather than feature detection. (Remember, even jQuery removed browser detection only recently, and many popular libraries still do a good deal of browser detection.)
- The code on the page might do object enumeration
for...ininstead of array iteration
for(var i=0;i<len;i++)for iterating through arrays. Even when enumerating object properties (which is a legit case of
for...in), the code on the page might not use
Either of these will break the code on the host page. You don't want code on the host page to break just because they've added your script.
No DOM modifications
Just like you don't own the global namespace, you don't own the DOM either. Making any changes to the DOM is simply unacceptable. Do not add properties or attributes to the DOM, and do not add or remove elements in the DOM. Your widget is never important enough to add extra nodes or attributes to any element of the DOM. This is because code on the page might be too tightly dependent on the DOM being one way, and if you modify it their code is likely to break.
That said, there are two permissible cases when you can modify the DOM. The first is when you are expected to present a UI, and the second is when you need to communicate with your server and circumvent the same-origin policy of the browser. In the first case, make the contract explicit by asking for a DOM node within which you will make the modifications, so that the developer of the page knows what to expect. I'll address the second case in detail in the third post in this series.
Make no assumptions about the page
This is the most complex to ensure. Even Google Analytics has had issues with their tracking script because of assumptions they inadvertently made. Steve Souders has enumerated some on his blog. So, for example, you can't even assume that the DOM will have a
<head> node even after parsing the HTML! jQuery also had some bugs in their dynamic loader due to assumptions they inadvertently made. It seems that the only real thing you can rely on is that your script tag is on the page (and hence in the DOM). Nothing else should be taken for granted.
Unfortunately, I don't have a clean solution for this problem. The only solution seems to be that you should keep your dependencies on the DOM and to native object to a bare minimum, and test in every environment you can lay your hands on. In Errorception, I test on way more browsers than I'd care about, even including old browser versions and mobile phones. It's the only real way to be sure. I have to do this irrespective of whether I support the browser or not, because it might be perfectly supported by the developers of the page.
A slightly less scary but equally dangerous problem is that
undefined, and that can mess with your script. If you really need to use
undefined, you should ensure that you have a clean, unassigned
undefined for your use. There are several ways to make sure you are working with a clean
undefined. The anonymous function I've shown above implements one of these mechanisms, such that
undefined inside the function is the
undefined you expect.
One post in particular is relevant here: an excellent post by Philip Tellis titled "How much do you trust third-party widgets?". It's a must read to get a gist of the issues surrounding trust in third-party widgets, along with some high-level solutions.
In Part 2…
In the next installment of this article series, I'll talk about strategies to load and bootstrap your code in the page, without causing a performance hit. I'll also be touching upon how, at Errorception, we mitigate the risk of the service going down, if ever — an approach that's definitely not unique to Errorception, but isn't as abundantly used as you think.