WHAT’S IN THIS CHAPTER?
➤➤ Using the <script> element
➤➤ Comparing inline and external scripts
➤➤ Examining how document modes affect JavaScript
➤➤ Preparing for JavaScript-disabled experiences
The introduction of JavaScript into web pages immediately ran into the web’s predominant language, HTML. As part of its original work on JavaScript, Netscape tried to figure out how to make JavaScript coexist in HTML pages without breaking those pages’ rendering in other browsers. Through trial, error, and controversy, several decisions were finally made and agreed upon to bring universal scripting support to the web. Much of the work done in these early days of the web has survived and become formalized in the HTML specification.
THE <SCRIPT> ELEMENT
The primary method of inserting JavaScript into an HTML page is via the <script> element.This element was created by Netscape and first implemented in Netscape Navigator 2. It was later added to the formal HTML specification. There are six attributes for the <script> element:
➤➤ async—Optional. Indicates that the script should begin downloading immediately but should not prevent other actions on the page such as downloading resources or waiting for other scripts to load. Valid only for external script files.
➤➤ charset—Optional. The character set of the code specified using the src attribute. This attribute is rarely used because most browsers don’t honor its value.
➤➤ crossorigin—Optional. Configures the CORS settings for the associated request; by default, CORS is not used at all. crossorigin="anonymous" will configure the request for the file to not have the credentials flag set. crossorigin="use-credentials" will set the credentials flag, meaning the outgoing request will include credentials.
➤➤ defer—Optional. Indicates that the execution of the script can safely be deferred until after the document’s content has been completely parsed and displayed. Valid only for external scripts. Internet Explorer 7 and earlier also allow for inline scripts.
➤➤ integrity—Optional. Allows for verification of Subresource Integrity (SRI) by checking the retrieved resource against a provided cryptographic signature. If the signature of the retrieved resource does not match that specified by this attribute, the page will error and the script will not execute. This is useful for ensuring that a Content Delivery Network (CDN) is not serving malicious payloads.
➤➤ language—Deprecated. Originally indicated the scripting language being used by the code block (such as "JavaScript", "JavaScript1.2", or "VBScript"). Most browsers ignore this attribute; it should not be used.
➤➤ src—Optional. Indicates an external file that contains code to be executed.
➤➤ type—Optional. Replaces language; indicates the content type (also called MIME type) of the scripting language being used by the code block. Traditionally, this value has always been "text/javascript", though both "text/javascript" and "text/ecmascript" are deprecated. JavaScript files are typically served with the "application/x-javascript" MIME type even though setting this in the type attribute may cause the script to be ignored. Other values that work in non–Internet Explorer browsers are "application/ javascript" and "application/ecmascript". If the value is module, the code is treated as an ES6 module and only then is eligible to use the import and export keywords.
There are two ways to use the <script> element: embed JavaScript code directly into the page or include JavaScript from an external file.
To include inline JavaScript code, place JavaScript code inside the <script> element directly, as follows:
<script>
function sayHi() {
console.log("Hi!");
}
</script>
The JavaScript code contained inside a <script> element is interpreted from top to bottom. In the case of this example, a function definition is interpreted and stored inside the interpreter environment. The rest of the page content is not loaded and/or displayed until after all of the code inside the <script> element has been evaluated.
When using inline JavaScript code, keep in mind that you cannot have the string "</script>" anywhere in your code. For example, the following code causes an error when loaded into a browser:
<script>
function sayScript() {
console.log("</script>");
}
</script>
Because of the way that inline scripts are parsed, the browser sees the string "</script>" as if it were the closing </script> tag. This problem can be avoided easily by escaping the "/" character, as in this example:
script>
function sayScript() {
console.log("<\/script>");
}
</script>
The changes to this code make it acceptable to browsers and won’t cause any errors. To include JavaScript from an external file, the src attribute is required. The value of src is a URL linked to a file containing JavaScript code, like this:
<script src="example.js"></script>
In this example, an external file named example.js is loaded into the page. The file itself need only contain the JavaScript code that would occur between the opening <script> and closing </script> tags. As with inline JavaScript code, processing of the page is halted while the external file is interpreted. (There is also some time taken to download the file.) In XHTML documents, you can omit the closing tag, as in this example:
<script src="example.js"/>
This syntax should not be used in HTML documents because it is invalid HTML and won’t be handled properly by some browsers, most notably Internet Explorer
NOTE By convention, external JavaScript files have a .js extension. This is not a requirement because browsers do not check the file extension of included JavaScript files. This leaves open the possibility of dynamically generating JavaScript code using a server-side scripting language, or for in-browser transpilation into JavaScript from a JavaScript extension language such as
TypeScript or React’s JSX. Keep in mind, though, that servers often use the file extension to determine the correct MIME type to apply to the response. If you don’t use a .js extension, double-check that your server is returning the correct MIME type.
It’s important to note that a <script> element using the src attribute should not include additional JavaScript code between the <script> and </script> tags. If both are provided, the script file is downloaded and executed while the inline code is ignored.
One of the most powerful and most controversial parts of the <script> element is its ability to include JavaScript files from outside domains. Much like an <img> element, the <script> element’s src attribute may be set to a full URL that exists outside the domain on which the HTML page exists, as in this example:
<script src="http://www.somewhere.com/afile.js"></script>
When the browser goes to resolve this resource, it will send a GET request to the path specified in the src attribute to retrieve the resource—presumably a JavaScript file. This initial request is not subject to the browser’s cross-origin restrictions, but any JavaScript returned and executed will be. Of course, this request is still subject to the HTTP/HTTPS protocol of the parent page.
Code from an external domain will be loaded and interpreted as if it were part of the page that is loading it. This capability allows you to serve up JavaScript from various domains, if necessary. Be careful, however, if you are referencing JavaScript files located on a server that you don’t control. A malicious programmer could, at any time, replace the file. When including JavaScript files from a different domain, make sure you are the domain owner or the domain is owned by a trusted source.
The <script> tag’s integrity attribute gives you a tool to defend against this; however, it has limited browser support.
Regardless of how the code is included, the <script> elements are interpreted in the order in which they appear in the page so long as the defer and async attributes are not present. The first <script> element’s code must be completely interpreted before thesecond <script> element begins interpretation, the second must be completed before the third, and so on.
Tag Placement
Traditionally, all <script> elements were placed within the <head> element on a page, as in this example:
<!DOCTYPE html>
<html>
<head>
<title>Example HTML Page</title>
<script src="example1.js"></script>
<script src="example2.js"></script>
</head>
<body>
<!-- content here -->
</body>
</html>
The main purpose of this format was to keep external file references, both CSS files and JavaScript files, in the same area. However, including all JavaScript files in the <head> of a document means that all of the JavaScript code must be downloaded, parsed, and interpreted before the page begins rendering (rendering begins when the browser receives the opening <body> tag). For pages that require a lot of JavaScript code, this can cause a noticeable delay in page rendering, during which time the browser will be completely blank. For this reason, modern web applications typically include all JavaScript references in the <body> element, after the page content, as shown in this example:
<!DOCTYPE html>
<html>
<head>
<title>Example HTML Page</title>
</head>
<body>
<!-- content here -->
<script src="example1.js"></script>
<script src="example2.js"></script>
</body>
</html>
Using this approach, the page is completely rendered in the browser before the JavaScript code is processed. The resulting user experience is perceived as faster because the amount of time spent on a blank browser window is reduced.
Deferred Scripts
HTML 4.01 defines an attribute named defer for the <script> element. The purpose of defer is to indicate that a script won’t be changing the structure of the page as it executes. As such, the script can be run safely after the entire page has been parsed. Setting the defer attribute on a <script> element signals to the browser that download should begin immediately but execution should be deferred:
<!DOCTYPE html>
<html>
<head>
<title>Example HTML Page</title>
<script defer src="example1.js"></script>
<script defer src="example2.js"></script>
</head>
<body>
<!-- content here -->
</body>
</html>
Even though the <script> elements in this example are included in the document <head>, they will not be executed until after the browser has received the closing </html> tag. The HTML5 specification indicates that scripts will be executed in the order in which they appear, so the first deferred script executes before the second deferred script, and both will execute before the DOMContentLoaded event (see the chapter “Events” for more information). In reality, though, deferred scripts don’t always
execute in order or before the DOMContentLoaded event, so it’s best to include just one when possible.
As mentioned previously, the defer attribute is supported only for external script files. This was a clarification made in HTML5, so browsers that support the HTML5 implementation will ignore defer when set on an inline script. Internet Explorer 4–7 all exhibit the old behavior, while Internet Explorer 8 and above support the HTML5 behavior.
Support for the defer attribute was added beginning with Internet Explorer 4, Firefox 3.5, Safari 5, and Chrome 7. All other browsers simply ignore this attribute and treat the script as it normally would. For this reason, it’s still best to put deferred scripts at the bottom of the page.
NOTE For XHTML documents, specify the defer attribute as defer="defer".
Asynchronous Scripts
HTML5 introduces the async attribute for <script> elements. The async attribute is similar to defer in that it changes the way the script is processed. Also similar to defer, async applies only to external scripts and signals the browser to begin downloading the file immediately. Unlike defer, scripts marked as async are not guaranteed to execute in the order in which they are specified. For example:
<!DOCTYPE html>
<html>
<head>
<title>Example HTML Page</title>
<script async src="example1.js"></script>
<script async src="example2.js"></script>
</head>
<body>
<!-- content here -->
</body>
</html>
In this code, the second script file might execute before the first, so it’s important that there are no dependencies between the two. The purpose of specifying an async script is to indicate that the page need not wait for the script to be downloaded and executed before continuing to load, and it also need not wait for another script to load and execute before it can do the same. Because of this, it’s recommended that asynchronous scripts not modify the DOM as they are loading.
Asynchronous scripts are guaranteed to execute before the page’s load event and may execute before or after DOMContentLoaded (see the “Events” chapter for details). Firefox 3.6, Safari 5, and Chrome 7 support asynchronous scripts. Using async scripts also confers to your page the implicit assumption that you do not intend to use document.write—but good web development practices dictate that you shouldn't be using it anyway.
NOTE For XHTML documents, specify the async attribute as async="async"
Dynamic Script Loading
You are not limited to using static <script> tags to retrieve resources. Because JavaScript is able to use the DOM API, you are more than welcome to add script elements, which will, in turn, load the resources they specify. This can be done by creating script elements and attaching them to the DOM:
let script = document.createElement('script');
script.src = 'gibberish.js';
document.head.appendChild(script);
Of course, this request will not be generated until the HTMLElement is attached to the DOM, and therefore not until this script itself runs. By default, scripts that are created in this fashion are async. This can be problematic, however, as all browsers support createElement but not all support async script requests. Therefore, to unify the dynamic script loading behavior, you can explicitly mark the tag as synchronous:
let script = document.createElement('script');
script.src = 'gibberish.js';
script.async = false;
document.head.appendChild(script);
Resources fetched in this fashion will be hidden from browser preloaders. This will severely injure their priority in the resource fetching queue. Depending on how your application works and how it is used, this can severely damage performance. To inform preloaders of the existence of these dynamically requested files, you can explicitly declare them in the document head:
<link rel="subresource" href="gibberish.js">
Changes in XHTML
Extensible HyperText Markup Language, or XHTML, is a reformulation of HTML as an application of XML. Unlike in HTML, where the type attribute is unneeded when using JavaScript, in XHTML, the <script> element requires that you specify the type attribute as text/javascript.
The rules for writing code in XHTML are stricter than those for HTML, which affects the <script> element when using embedded JavaScript code. Although valid in HTML, the following code block is invalid in XHTML:
<script type="text/javascript">
function compare(a, b) {
if (a < b) {
console.log("A is less than B");
} else if (a > b) {
console.log("A is greater than B");
} else {
console.log("A is equal to B");
}
}
</script>