Everything I Know About The Script Tag

As you probably know, the script tag is used for specifying JavaScript which should be run in a webpage. A script tag can either include the JavaScript directly, or it can point to a URL where the script should be loaded from.

Script tags are executed in the order they appear

This idea should be intuitive when you read this code:

<script>
  var x = 3;
</script>
<script>
  alert(x);
  // Will alert '3';
</script>

It is less intuitive (but no less true) when working with external resources.

<script src="//typekit.com/fj3j1j2.js"></script>

<!-- This second script won’t execute until typekit has executed, or timed out -->
<script src="//my.site/script.js"></script>

It similarly works for combinations of local and remote scripts.

Functionally this means you can significantly slow down your site if you have slow scripts loading early in the page. It also means scripts which appear later on the page can depend on things scripts which appear earlier have done.

Elements on the page won’t render until all the script tags preceding them have loaded and executed. This means you can do all sorts of crazy things where you tweak the page before it loads, if you’re willing to accept the performance hit.

This doesn’t apply however if you add script tags to the DOM after the page has begun to load using document.appendChild or the like. Those tags will load whenever the browser sees fit, and in no particular order.

When a script tag executes, everything above it in the DOM is available (but not everything below).

<html>
  <head>
    <script>
      // document.head is available
      // document.body is not!
    </script>
  </head>
  <body>
    <script>
      // document.head is available
      // document.body is available
    </script>
  </body>
</html>

You can think of the HTML parser as traveling through the document tag by tag, executing any JavaScript as it approaches it. This means DOM nodes are available to your JavaScript (through querySelectorAll, jQuery, etc.) only if their opening tag appears earlier in the document than your script tag.

One useful corollary of this is that document.head is virtually always available in any JavaScript you might write (on a webpage). document.body is only available if your script tag appears inside or after the opening <body> tag.

async and defer

HTML5 has added a couple tools for controlling when scripts execute.

  • async means “execute this whenever”. More specifically that means: I don’t care if you execute this after the whole page has loaded, and every other script has executed. It’s very useful for analytics tracking codes, for example, where no other code on the page depends on their execution. Defining a variable or function in async code which the page needs is bad news, as you have no way of knowing when the async code will actually run.
  • defer means “wait for the parser to finish to execute this”. It’s roughly equivalent to binding your script to the DOMContentLoaded event, or using jQuery.ready. When the code does run, everything in the DOM will be available for you to use. Unlike async, defer’d code will run in the order it appears in the HTML of the page, it is just deferred until after the HTML is fully parsed.

The type attribute

Historically (since Netscape 2), it hasn’t mattered much if you specified type="text/javascript" in your script tags, or just left it blank. If you set any MIME type which isn’t a variant of JavaScript as the type though, the browser won’t execute it. This can be cool when you want to define your own language:

<script type="text/emerald">
  make a social network
    but for cats
</script>

The actual execution of that code is then up to you, i.e.:

<script>
  var codez = document.querySelectorAll('script[type="text/emerald"]');
  for (var i=0; i < codez.length; i++)
    runEmeraldCode(codez[i].innerHTML);
</script>

Defining the runEmeraldCode function is left as an exercise for the reader.

If you have a perverse desire to, you can also override the default type for every script tag on the page using a meta tag:

<meta http-equiv="Content-Script-Type" content="text/vbscript">

Or the Content-Script-Type header.

Take a look at A Brief History of Weird Scripting Languages on the Web for more details on what types were valid.

There’s an integrity attribute?

The integrity attribute is a part of the new Subresource Integrity spec. It allows you to provide a hash for the contents which a script file should contain. It’s meant to prevent a nefarious actor from messing with the contents of a script tag over the wire. In a world with SSL, this is only really valuable if you’re loading a script from some external source you don’t control like code.jquery.com.

If you do choose to use this, you include which hash you are using, and the hash’s value, separated by a hyphen. It looks like this:

<script
  src="//code.jquery.com/jquery.js"
  integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC">
</script>

I have yet to see it in the wild, but if you know of a site using it, comment below.

crossorigin is a thing too!

It’s not fully standardized yet, but there is a crossorigin attribute which is supported by some browsers. The general idea is that the browser doesn’t like to let you do much with resources loaded from a different ‘origin’ than the current page. (The origin is defined as the combination of the page’s protocol, hostname, and port. I.e. http://google.com:80).

This is to prevent you from, for example, making requests to your competitors website which cancels any accounts the current user might have (not nice!). Its connection to script tags is somewhat incidental though. The idea is if you apply a handler to the window.onerror event, that handler is provided with some information about the page and script which you perhaps shouldn’t have if you’re loading code from an external site. In secure browsers this info is not included unless you specify crossorigin.

crossorigin isn’t a magical security hack though, what it does is instruct the browser to undergo the normal CORS access check of making an OPTIONS request and checking for Access-Control headers.

document.currentScript

It has zero IE support, making it something of a novelty, but there is a property called document.currentScript which points to the current script being executed. It could be super helpful if you want to pull attributes out of the script tag your embed is included using. Personally I’m pretty glad it isn’t fully supported as it would make some of the work we do at Eager to install embed codes somewhat harder.

onafterscriptexecute?!

This is super useless, because it’s only supported on Firefox. It, along with onbeforescriptexecute allows you to bind an event which will be executed before and after every script on the page runs, which is pretty cool.

If you’re curious, the event objects include a reference to the script being executed, and the before event can cancel the execution by calling preventDefault().

for / event

To this day the HTML5 spec includes a rarely seen, previously IE-specific, method of binding code to an event. You are supposed to be able to do this to have a script tag not run until the page’s load event:

<script for="window" event="onload">
  alert("Hi!")
</script>

I can’t actually make this work on Chrome or Firefox (making them not standards compliant), but there’s a good chance it still works in IE.

NOSCRIPT

Like your parents, it’s hard to believe there was a time when JavaScript was young. There was a day however when you couldn’t be sure if a given browser would support JavaScript or not. Even worse, you couldn’t be sure that the browser would even know what a script tag was. And if a browser doesn’t recognize a tag, it is supposed to render it as a generic inline element, meaning all your secret JavaScripts would be rendered to the page as text!

Fortunately the spec was helpful enough to provide the solution, wrapping your code in something which the unsupporting browser would interpret as an HTML comment:

<script>
<!--  to hide script contents from old browsers

  // You would probably be pasting a ‘rollover’ script
  // you got from hotscripts.net here

// end hiding contents from old browsers  -->
</script>

Of course, like most things, XHTML made this much worse. XML has a very special method of escaping content which might contain closing tags and such, CDATA was born:

<script>
//<![CDATA[

    // Is this the right incantation to get this to pass
    // the XHTML validator?

//]]>
</script>

With that, your code would be valid XHTML. This wouldn’t have any impact on its functionality, but was incredibly important to your self worth as a web developer.

Browsers also included a helpful method to allow you to tell people who didn’t have JavaScript enabled to go away, the noscript tag. <noscript> wraps the content which should only be rendered if the browser doesn’t support script execution:

<noscript>
  Please use Internet Explorer 5.5 or above.
</noscript>
<script>
  exploitInternetExplorer0Day();
</script>

If you’re observant you’ll realize that noscript doesn’t accept the type argument, making its interaction with pages which use multiple script types somewhat ambiguous. The actual behavior varied from browser to browser, but included showing noscript blocks if any script tag used earlier in the document used a not-supported type. This means that it was very possible to have early noscripts not appear, while ones lower on the page did.

Script Tags and innerHTML

Script tags which are dynamically added to the page via the DOM are executed by the browser:

var myScript = document.createElement('script');
myScript.textContent = 'alert("✋")';
document.head.appendChild(myScript);

Script tags which are dynamically added to the page via innerHTML are not:

document.head.innerHTML += '<script>alert("✋")</script>';

Why this is is not clear, but it is a fun answer to the trivia question “is it possible to have a script tag show up in the Chrome inspector which has not actually ran?” Which, in turn, makes it a great way to prank your coworkers.


Thanks for tuning in! Our next post is on document.write and how weird it is. Subscribe below to get notified when our new blog posts are released.

Like this post? Share it with your followers.

Sign up to get our next post delivered to your inbox.

Follow us to get our latest updates.