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);

40 Responses to “The ridiculous case of adding a script element”

  1. Andrea Giammarchi Says:

    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? :-)

  2. stoyan Says:

    nice, thanks! in general makes a lot of sense about top vs bottom of the stack

    so your example makes something like:

    <html>
      <head>
      <script>
      <body>
    
  3. Adam Bankin Says:

    Is there a reason why you don’t create and append a node as the firstChild of documentElement if a node doesn’t exist?

  4. Adam Bankin Says:

    That was meant to say “head” but I used greater and less thans

  5. John-David Dalton Says:

    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.

  6. Mathias Bynens Says:

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

  7. Link-urile săptămânii 5-11 septembrie | Staicu Ionuţ-Bogdan Says:

    [...] The ridiculous case of adding a script element. [...]

  8. Andrea Giammarchi Says:

    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

  9. Andrea Giammarchi Says:

    <!doctype html>
    <script src=”whatever.js”></script>

  10. Diego Perini Says:

    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.

  11. satellite TV Says:

    satellite TV…

    [...]JSPatterns.com » Blog Archive » The ridiculous case of adding a script element[...]…

  12. page Says:

    godd u really suck

  13. Sam Says:

    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

  14. Acheter Vimax Says:

    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.

  15. 黄色的鸭子 | Spencer Says:

    [...] 不仅采用异步的方式加载analytics代码,并且细致考虑了代码加载后的位置。最大化优化了网站的性能(why?答案在这里)。 [...]

  16. The Riviera Times Says:

    The Riviera Times…

    [...]JSPatterns.com » Blog Archive » The ridiculous case of adding a script element[...]…

  17. Michael Says:

    Pardon the newbie question, but when using Stoyan’s “first script tag” solution, where and how should the jQuery CDN be loaded?

  18. Keegan Street Says:

    Wouldn’t the third approach and Andrea’s approach generate invalid HTML? The permitted content of an element is “one element, followed by one element.” – https://developer.mozilla.org/en/HTML/Element/html

  19. Keegan Street Says:

    Correction: Wouldn’t the third approach and Andrea’s approach generate invalid HTML? The permitted content of an <html> element is “one <head> element, followed by one <body> element.” – https://developer.mozilla.org/en/HTML/Element/html

  20. Enyo 2.0 教程 | MiDuo – Life View Says:

    [...] logic around adding a <script> tag at runtime, see the // discussion at the URL below: // http://www.jspatterns.com/the-ridiculous-case-of-adding-a-script-element/ addScriptElement: function(src) { var script = document.createElement('script'); script.src = src; [...]

  21. Blog sharingHM » Insertar etiqueta script al final del documento Says:

    [...] de investigar recursos en la web me tope con esta solucion gracias al ejemplo que esta en la web http://www.jspatterns.com en el cual tome de ejemplo y solo agregue una linea para que me quedara de la siguiente manera. [...]

  22. fono chat Says:

    We have been a number of volunteers plus launching a new program in this neighborhood. Your internet site provided us practical facts to work with. You will have completed your strong action in addition to our own total community are going to be thankful to your account.

  23. vimax stores Says:

    Thank you for any other informative site. The place else may just I am getting that kind of info written in such a perfect approach? I’ve a undertaking that I’m simply now running on, and I have been at the glance

  24. opciones binarias Says:

    Hey very nice site!! Man .. Beautiful .. Amazing .
    . I will bookmark your site and take the feeds additionally?
    I’m satisfied to seek out so many useful information right here within the post, we need develop extra strategies in this regard, thank you for sharing. . . . . .

  25. feee3 Says:

    You really make it appear so easy with your presentation however I to find this topic to be really something which I feel I might never understand. It sort of feels too complex and very broad for me. I’m taking a look ahead on your next put up, I will try to get the cling of it!

  26. warz cheats Says:

    Thank you for every other fantastic article.
    The place else may just anyone get that kind of information in such an ideal means of writing?
    I’ve a presentation subsequent week, and I am at the search for such info.

  27. mandetech.com Says:

    Greetings! Very useful advice within this article!

    It’s the little changes which will make the greatest changes. Many thanks for sharing!

  28. web hosting Says:

    My brother suggested I might like this web site. He was once entirely right. This publish actually made my day. You can not imagine just how much time I had spent for this info! Thanks!

  29. gaetane Says:

    Hi :) I too found this intensely interesting. And I’m not even a spammer! Us PHP coders just bash stuff together until it works. As a result, I find javascript… what’s the word?… “fiddly”? I had other words but I probably can’t say them here. Anyway, I ran into this exact problem – trying to attach a script when there’s no head or body.

    You just saved me a whole bunch of annoyance and frustration! Thanks ;)

  30. emergency dentist san antonio Says:

    Hello There. I found your blog using msn. This is an extremely well
    written article. I will make sure to bookmark it and return to read more of your useful info.
    Thanks for the post. I’ll definitely return.

    my blog: emergency dentist san antonio

  31. Jakub Łopuszański Says:

    Big thanks from developers at nk.pl :)

  32. Tracy J. Prather Says:

    Good day! Do you know if they make any plugins to protect against hackers?
    I’m kinda paranoid about losing everything I’ve worked hard on.
    Any tips?

  33. parajumpers men Says:

    The campground is located within walking distance of the beach, where anglers can surf fish. I support home schooling for those who are able and willing. The usage and distribution of files is allowed if credit for the work is attributed to me by means of my name and the full URL of the file location. The bare minimum with this is to learn the first four frets on the first six strings of the guitar. Try to find a good site with a message board; you can find more deals at the message board or fin

  34. Amit Anand Says:

    Yuppieeeeeeee. !!! Loved this article ! Superb Man :) great explanation :) Looking Forward to read all of your BLOG POST! :D

  35. bambuser.com Says:

    My spouse and I stumbled over here by a different web
    address and thought I should check things out. I like what I see so i am just following you.
    Look forward to finding out about your web page again.

  36. Michael D. Prather Says:

    I am really impressed along with your writing talents and also with the layout on your weblog.
    Is that this a paid subject or did you customize it your
    self? Anyway keep up the nice high quality writing, it is uncommon to peer a
    great weblog like this one nowadays..

  37. Parajumpers Kodiak Says:

    Air mattresses, futons and even a mattress from an unused guest bedroom can all be used to add extra sleeping space. I have searched for a week so you don’t have to, saving you time and money. Decorate in green, have plenty of Parajumpers Jackets: Chieftains, U2,Parajumpers Kodiak, Sinead O the Corrs,Parajumpers Long Bear, whatever your taste, plus throw in all the other usual party stuff and invite friends to celebrate with you. But the “Fake Handbag Award” goes to Salinas CA, where the City Co

  38. Michael Kors Outlet Says:

    Fits underneath cabinets. Preparation Times 1 espresso(60ml) 30 sec. 2 espressos 45 sec. 2. Giving is a pattern. Decor and food can all be designed around gifting, presents, or wrapping. But Platt and also other Florida dairy farmers typically are not directly triggering the extra. Florida residents and tourists drink milk faster compared to state’s own dairies can produce it. Wisconsin as well as other Midwestern dairy states produce far more than his or her residents can consume, and far from

  39. StevenCes Says:

    http://www.acehomeinspection.com/default.asp?MKB=Michael-Kors-Discount-Handbags.html

    [url=http://www.oaklandaaup.org/default.asp?rab=Discount-Ray-Ban-Sunglasses.html]Ray Ban Sunglasses On Sale[/url]
    itw2Hplaysu5Ds0Et4C[url=http://www.usagency.com/?mek=Michael-Kor-Handbags.html]Michael Kors Outlet Handbags[/url]

  40. xbox live gold gratuit 48h Says:

    This post gives clear idea designed for the new visitors of blogging, that truly how to do running
    a blog.

    My weblog … xbox live gold gratuit 48h

Leave a Reply