A Detailed Breakdown of the <script> Tag

Colin Ihrig
Share

When the <script> tag was originally introduced, it was used to add only the most basic level of interactivity to web pages. But the web has changed a lot, and the <script> tag has evolved. The proliferation of JavaScript has made it one of the most important HTML tags. This article explores the <script> tag in detail, including best practices for using it in modern web pages.

Specifying the Scripting Language

While JavaScript is the default scripting language of the web, browsers can support any number of additional languages. For example, Internet Explorer also supports a scripting language derived from Visual Basic named VBScript. In order to specify the scripting language of choice, the <script> tag’s “type” attribute is used. Technically, “type” specifies the MIME type of the script. The following example uses “type” to specify JavaScript’s MIME type, “text/javascript”.

<script type="text/javascript">
  // JavaScript code here
</script>

In older versions of HTML it was necessary to specify a value for “type”. However, starting in HTML5, “type” defaults to “text/javascript”. This decision was made because the W3C realized that JavaScript is the only universally supported scripting language. Therefore, HTML5 <script> tags can be shortened in the following fashion.

<script>
  // JavaScript code here
</script>

The “language” Attribute

There is a second attribute, “language”, which can be used to specify the scripting language. Unlike “type”, the possible values for “language” were never standardized. This attribute has been deprecated for a long time now, but some people still use it. It should not be used under any circumstances.

Inline and External Scripts

The <script> tag allows code to either be embedded directly into HTML, or included from external script files. Inline scripts are created by placing code between <script> and </script> tags in an HTML file. The following code shows an example inline <script>.

<script>
  // Inline JavaScript code here
</script>

Inline scripts are a quick and easy way to add code to web pages. However, large scripts can also lead to cluttered HTML files. The alternative approach is to include code in external script files. The external files are referenced via a URL specified by the “src” attribute. The following example shows how external script files are included. In this example, the external script file is named external.js, and is located in the same directory as the HTML file. Also, note that the closing </script> tag is still required.

<script src="external.js"></script>

XHTML Compatibility

XHTML rules are much stricter than those of HTML. When special XML characters (such as & and <) are used in scripts in XHTML files, they cause errors. The simplest workaround is to use external script files. However, if you simply must write inline scripts, then you will need to include CDATA (character data) sections in your file. Within CDATA sections the special XML characters can be used freely. The following example uses a CDATA section that is compatible with both XHTML and HTML syntax. Note that XHTML also requires the use of the “type” attribute.

<script type="text/javascript">
//<![CDATA[
  alert((1 < 2) && (3 > 2));
//]]>
</script>

Dynamic Script Tag Injection

Dynamic script tag injection is a technique in which new <script> elements are created at runtime. When the new <script> is added to the page, its “src” URL is automatically downloaded and executed. The following code shows an example dynamic script tag injection. In the example, a new <script> element is created using the document.createElement() function. Next, the “src” attribute is set to the URL of the script file. Finally, the new element is added to the document’s head, causing the script to be downloaded and executed.

var script = document.createElement("script");

script.setAttribute("src", url);
document.head.appendChild(script);

Application

The <script> tag is not subject to the same origin policy. Web pages can exploit this freedom to request cross-site data. JSONP, a technique for making cross-site AJAX-like requests, makes extensive use of dynamic script tag injection. Under the JSONP model, each AJAX request is replaced by a script tag injection.

The “async” Attribute

When a <script> tag is encountered, the browser stops what it is doing and begins downloading/executing the script. This default behavior is known as synchronous blocking. During this period of blocking, the page may seem unresponsive or slow to the user. To mitigate this problem, HTML5 introduced the “async” attribute for <script> tags. “async” is a Boolean attribute which, when specified, indicates that a script should unblock the rest of the page, and execute asynchronously. According to the specification, “async” should only be used with external script files. Unfortunately, “async” is not yet supported in Opera.  The following example shows how the “async” attribute is used.

<script src="file.js" async="async"></script>

Normally, “async” defaults to false. However, when dealing with dynamically injected scripts, the default value becomes true. This can lead to problems if scripts with interdependencies are inserted at the same time. For example, assume that a page injects a third-party library originating from a slow server. At (almost) the same time, the page also injects a script that makes calls to the library. Since the scripts are asynchronous, the second file could potentially get downloaded and executed before the library is available. The solution is to preserve the program order by setting “async” to false for both scripts. This concept is illustrated in the following example. In the example, the page will wait for the first script to load before moving on to the second script.

var library = document.createElement("script");
var local = document.createElement("script");

library.async = false;
local.async = false;
library.setAttribute("src", "remote-library-code.js");
local.setAttribute("src", "local-file.js");
document.head.appendChild(library);
document.head.appendChild(local);
// use the library code

The “defer” Attribute

As previously stated, <script> tags cause the browser to block the rest of the page while the script is processed. This can lead to problems if the script references DOM elements which haven’t finished loading yet. In this scenario, the DOM-related code is typically placed in an event handler such as window load or DOMContentLoaded. Another option is to postpone execution until the document has been parsed using the “defer” attribute. Like “async”, “defer” is a Boolean attribute that should only be used with external script files. The following example shows how “defer” works. This example requires an HTML file and a separate JavaScript file named defer.js. The HTML source is shown below.

<!DOCTYPE html>
<html lang="en">
<head>
  <title>defer Example</title>
  <meta charset="UTF-8" />
  <script src="defer.js" defer="defer"></script>
</head>
<body>
  <span id="message"></span>
</body>
</html>

The code for defer.js is shown below. If the “defer” attribute were not present, this code would fail because the DOM is not ready when the <span> element is accessed.

var span = document.getElementById("message");

span.textContent = "The DOM is ready for scripting!";

It is possible to specify both “async” and “defer” on the same <script> element. This is possible so that browsers that do not support “async” can fall back on the “defer” behavior instead of synchronous blocking. According to the specification, “defer” is overridden by “async”. Unfortunately, “defer” is also unsupported in Opera.

Performance Considerations

All of the examples on this page have shown <script> tags placed in the document’s head. This can actually degrade performance because the rest of the page is blocked while the scripts are processed. Obviously, the “defer” and “async” attributes offer one solution. Unfortunately, they are not supported everywhere yet. A second option is to move <script> tags to the bottom of the <body> tag whenever possible ― this is usually fine as long as the script does not call document.write().

Using external script files instead of inline scripts is another technique that often improves performance. This leads to smaller HTML files, at the expense of creating additional HTTP requests. However, script files are often shared among multiple HTML pages, and can be cached by the browser. The overall result is a smaller HTML download without the additional server requests. Also remember that scripts can be downloaded dynamically after the page is loaded.

Things to Remember

  • The <script> tag always requires a matching </script> tag.
  • The scripting language is specified by the “type” attribute. Do not use the “language” attribute. HTML5 defaults to “text/javascript”.
  • XHTML files should use external script files or CDATA sections to allow special characters.
  • Dynamic script tag injections allow cross-site resource requests.
  • The “async” attribute causes a script to execute asynchronously from the rest of the page. It can also preserve order for dynamically inserted scripts.
  • The “defer” attribute can postpone script execution until the document is ready.
  • Move <script> tags to the end of the <body> when possible.
  • Use external script files which can be cached and shared among pages.