The ridiculous case of adding a script element
Adding a script element to a page should be a no-brainer. Yet, it's ridiculously unreliable in the wild - when you don't have any idea of the surrounding markup.
You know the drill - create a script element, point its src to a URL and add it to the page so that script file can be downloaded in a non-blocking manner.
Creating the script is nice and easy:
var js = document.createElement('script'); js.src = 'myscript.js';
The problem is how to add it to the page.
1. to the head
Probably the most common approach is to append to the head of the document:
document.getElementsByTagName('head')[0].appendChild(js);
You get all head elements (there should be only one) and you add the script there so the result is
<html> <head> <script> ...
But what if there's no head in the document? Remember, we want solid robust code that always works.
Well turns out that most browsers will create the head element even if the
tag is not there.Most, but not all as Steve Souders' browserscope test shows. Exceptions include browsers like Opera 8 but also Android 1.6 and one iPhone 3 - hardly old and negligible.

So head is out.
2. add to the body
This is even shorter:
document.body.appendChild(js);
what if there's no <body> tag? Well my test shows that all tested browsers will create a body element. Even one that has a working appendChild() method. Now, while some of those that don't create head, do create body, I'm still a little uncomfortable that I couldn't find a single browser that doesn't create body. Makes me feel a little worried about the testing and data collecting.
But even if we assume that in all browsers, document.body always exist, there's still a problem. IE7 and the dreaded "Operation aborted" error.
If you modify BODY while loading the page and from a script that is not a direct child of body, but nested in another element, you get Operation aborted and nothing works.
Surprisingly simple page fails miserably:
<html> <body> <div> <script> var js = document.createElement('script'); js.async = true; js.src = "http://tools.w3clubs.com/pagr/1.sleep-2.js"; document.body.appendChild(js); </script> </div> </body> </html>
See it live in IE7 and be amazed
Now operation aborted may be solved with a defer, but maybe not in IE9.
Another reason not to attempt anything with body is if the script is included in the head. At execution time we have not reached <body> yet, so document.body doesn't exist. Demo.
3. use documentElement
document.documentElement is the html doc itself. Now that's got to exist no matter what.
So you go like
var html = document.documentElement; html.insertBefore(js, html.firstChild);
And it works!
But what if the firstChild is a comment before the head? This makes something kinda weird:
<html> <script> <!-- comment --> <head> ..
Script obviously has no place there, but my test page worked in ie6789, and recent versions of FF, Chrome, Safari, Opera.
But looks like there are other browsers where this comment thing fails as reported(btw, good to read the whole post and all the comments) by Google Analytics folks who say they have received complaints when they used to do that. There might be other cases or browsers I didn't try. So reluctantly, we move on.
4. hook to the first script
Ahaa, if you're running a script, then this script must either be inline <script>bla</script> or external <script src="meh.js">. Either way, there's gotta be at least one script tag! Wo-hoo!
So the final solution is:
var first = document.getElementsByTagName('script')[0]; first.parentNode.insertBefore(js, first);
No matter where the first script might be, we glue out new one right above it.
Drawback is that looking for script nodes might be a little more expensive than looking for document.documentElement or document.body or the single match of getElemenetsByTagName('head')
There's still a case when there might not be a script element at all. Ah, impossible, since we're running a script there must be a script element! Well (and thanks to @kangax who pointed this to me while he was reviewing JavaScript Patterns!) here's one example:
<body onload="alert('Look ma, executing script without a script tag!')">
Overall while not completely foolproof, this is as close as you can get to being able to achieve the simple task - add a new script node. Isn't web development just magnificent?
(Funny thing if you use this only to load a script asynchronously: in order to load a script (file), you need a script that refers to a script, possibly itself. JavaScript all around.)
Once again the whole snippet:
var js = document.createElement('script'); js.src = 'myscript.js'; var first = document.getElementsByTagName('script')[0]; first.parentNode.insertBefore(js, first);

September 10th, 2011 at 5:54 am
What I usually do is:
var html = document.documentElement;
html.insertBefore(js, html.lastChild); // NOTE: lastChild
The DOM is a stack of nested elements, and as is for every stuck, it’s better to move as less “indexes” as possible.
A script before script 0 moves all other elements, a script before last node moves last node only at the end.
Whenever this brings real benefits or not, it always worked for me and it’s more linear/logic.
As example, the script itself that is relying into document.getElementsByTagName(“script”)[0] could be outside the header but it will still work.
Does it really matter then since apparently there are no “official” or “unavailable places” for scripts?
September 10th, 2011 at 6:02 am
nice, thanks! in general makes a lot of sense about top vs bottom of the stack
so your example makes something like:
September 10th, 2011 at 7:24 am
Is there a reason why you don’t create and append a node as the firstChild of documentElement if a node doesn’t exist?
September 10th, 2011 at 7:24 am
That was meant to say “head” but I used greater and less thans
September 10th, 2011 at 9:05 am
The “Operation Aborted” problem affects IE 5.5 – 8 (I’ve not run into it with IE8 but MS’s docs say so).
For more info check out Microsoft’s knowledge base posts 961712 and 974322.
September 10th, 2011 at 11:49 am
Great overview!
You don’t actually need the .async = true for dynamically inserted scripts; see http://mathiasbynens.be/notes/async-analytics-snippet#async for more information. (That page explains the snippet I always use to dynamically load scripts.)
September 11th, 2011 at 5:23 am
[...] The ridiculous case of adding a script element. [...]
September 11th, 2011 at 5:42 am
yes Stoyan, something like that … but being this a valid layout for all browsers:
I really don’t think we should care that much about the position, as long as it works as expected
September 11th, 2011 at 5:43 am
<!doctype html>
<script src=”whatever.js”></script>
September 11th, 2011 at 2:17 pm
If the objective is just to avoid the IE bug, in your example change:
document.body.appendChild( js );
with:
document.body.insertBefore( js, document.body.lastChild );
I still prefer to have all my scripts in the head section when in control of the sources, if that is not the case prefer using the ‘document.documentElement’ as a reference for insertion.
The ‘root’ element is the only element that is always present in the DOM when Javascript execution starts, even for different document types (XHTML/XML).
In my tests, script tags works when added to the ‘documentElement’, even if its first child is a TextNode. Even if the above modification make your sample work I would still suggest not to do it so and use ‘documentElement’ instead, more so for IE.
September 17th, 2011 at 10:08 pm
satellite TV…
[...]JSPatterns.com » Blog Archive » The ridiculous case of adding a script element[...]…
October 14th, 2011 at 7:58 pm
godd u really suck
November 1st, 2011 at 11:26 am
don’t forget to get back your windows tax when buying a new computer (60 – 70$).
ex: acer.custhelp.com/app/answers/detail/a_id/280
December 15th, 2011 at 5:57 pm
This site leaves a constructive materials that may possibly ‘t be disregarded primarily it includes and exhibits us one other level of hugely amazing impression.
January 14th, 2012 at 12:12 pm
[...] 不仅采用异步的方式加载analytics代码,并且细致考虑了代码加载后的位置。最大化优化了网站的性能(why?答案在这里)。 [...]