Script tag in an HTML document

Script tag in an HTML document

This post will introduce the specs of a script tag's properties, which might help if you want to optimize your page load time.

Script tag loading order

The following image explains the loading time of a script tag based on its property. There are several notes:

Source: https://html.spec.whatwg.org/images/asyncdefer.svg
  • defer should not be combined with async. By the specs definition, async has the higher priority, which means defer is ignored.
For classic scripts, if the async attribute is present, then the classic script will be fetched in parallel to parsing and evaluated as soon as it is available (potentially before parsing completes). If the async attribute is not present but the defer attribute is present, then the classic script will be fetched in parallel and evaluated when the page has finished parsing. If neither attribute is present, then the script is fetched and evaluated immediately, blocking parsing until these are both complete.
  • If there are several script tags, defer scripts are executed in the order they appear in the document, while async scripts are executed asynchronously whenever they are finished parsing. That is to say, async scripts execution order is not preserved.
For classic scripts/module scripts, if the async attribute is present, then the classic/module script will be fetched in parallel to parsing and evaluated as soon as it is available (potentially before parsing completes)
  • Notice the red color. When a script is executed, the document stops parsing because the script can contain a DOM modification such as document.write().
  • module type scripts are defer by default. Indeed, if defer attribute is provided on a module type script, it has no effect. When async is provided to a module type script, it becomes async.

From whatwg specs:

The defer attribute has no effect on module scripts.
  • With inline scripts (i.e src is not provided), for module type scripts async can be used, while for classic scripts both async and defer cannot be used.
Classic scripts may specify defer or async, but must not specify either unless the src attribute is present. Module scripts may specify the async attribute, but must not specify the defer attribute.

Is inline content of a script with src executed?

In other words, if there is a script tag <script src='./app.js'>window.start()</script>, what will happen? (earlier spoiled answer: NO. And this is never recommended).

In the HTML 4.01 specification of w3c, if both src and inline content are set, the script retrieved via src is executed, and the inline content is ignored.

The script may be defined within the contents of the SCRIPT element or in an external file. If the src attribute is not set, user agents must interpret the contents of the element as the script. If the src has a URI value, user agents must ignore the element's contents and retrieve the script via the URI

From the latest HTML (5) specification from whatwg (Last Updated 22 April 2021), when src is provided, the content must be empty. Otherwise, browser can throw error, leading to unexpected behavior. However, in practical, the browsers just ignore the inline content.

If there is no src attribute, depends on the value of the type attribute, but must match script content restrictions.If there is a src attribute, the element must be either empty or contain only script documentation that also matches script content restrictions.

While it is not recommended, several libraries might use the inline content of the script tag to provide a text information to the script. In that case, if the script is loaded as a module, use import.meta.currentScript, otherwise, use  Document.currentScript to return the <script> element whose script is currently being processed.
Note that: these APIs are only able to reference the <script> element if they are called synchronously from the top level of the script (codepen).

How to execute an inline code after a remote script is loaded?

The solution: dynamically load the script and listen for its load event to execute the inline code.

const script = document.createElement('script')
script.src = 'https://code.jquery.com/jquery-3.6.0.min.js'
script.async = true

script.addEventListener('load', () => {
    script.parentNode?.removeChild(script)
    // now you can use jquery
    // inline code here
})

document.head.appendChild(script)

How to load a style sheet asynchronously?

At the end of your document, put the following html value:

<noscript id="deferred-style">
	<link rel="stylesheet" type="text/css" href="./deferred.css"/>
</noscript>
<script>
    window.addEventListener('load', function (){
        (requestAnimationFrame || setTimeout)(function() {
            var addStylesNode = document.getELementById('deferred-style')
            var replacement = document.createElement('div')
            replacement.innerHTML = addStylesNode.textContent
            document.body.appendChild(replacement)
            addStylesNode.parentElement.removeChild(addStylesNode)
        })
    })
</script>
idea from https://developers.google.com/speed/docs/insights/OptimizeCSSDelivery

Full list of a script tag's attributes

From whatwg specs:

  • src: string.
  • type: string.
  • nomodule: boolean.
  • async: boolean.
  • defer: boolean.
  • crossorigin: string.
  • integrity: string.
  • referrerpolicy: string.

And global attributes which can be applied on any HTML element.