Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
async scripts, defer scripts, module scripts: explainer, comparison, and gotchas

<script> async, defer, async defer, module, nomodule, src, inline - the cheat sheet

With the addition of ES modules, there's now no fewer than 24 ways to load your JS code: (inline|not inline) x (defer|no defer) x (async|no async) x (type=text/javascript | type=module | nomodule) -- and each of them is subtly different.

This document is a comparison of various ways the <script> tags in HTML are processed depending on the attributes set.

If you ever wondered when to use inline <script async type="module"> and when <script nomodule defer src="...">, you're in the good place!

Note that this article is about <script>s inserted in the HTML; the behavior of <script>s inserted at runtime is slightly different - see Deep dive into the murky waters of script loading by Jake Archibald (2013)

Visual representation

Image replaces 1000 words, so here it is, for the good start: (credit: https://developers.google.com/web/fundamentals/primers/modules#module-vs-script) image

As you can see in the picture, async in particular might be a bit tricky: with legacy scripts, you generally use it to make things happen later, whereas with modules, you generally use it to make things happen earlier (this is because module scripts are deferred by default).

Standard vs async vs defer vs async defer

defer is older, async is just slightly newer. Both are well supported.

The intuitive difference between async and defer is that async tend to execute earlier (they don't have to wait for HTML to be parsed and DOM to be constructed, and for other scripts to be fetched).

  • "standard" non-module <script> (implied type=text/javascript, no async, no defer)

    • blocks the HTML parser
    • ℹ️ immediately fetched, parsed and executed, before any following <script>s in the HTML
    • ✔️ in-order execution guaranteed
    • blocks DOMContentLoaded event
    • given the above, not suitable for non-critical code, as it can lead to "single point of failure" rendering bottleneck and to delayed startup of the dynamic web apps
  • defer scripts:

    • ℹ️ for inline (non-module) scripts, defer is ignored and has no effect (they are executed immediately)
    • ℹ️ for inline (module) scripts, defer is implied (automatic)
    • ✔️ downloaded without blocking HTML parser
    • ✔️ relative order between multiple defer scripts execution is guaranteed (if they all have src)
    • ℹ️ executed after DOM is parsed (but just before raising DOMContentLoaded)
    • blocks DOMContentLoaded event (unless the script is async defer)
  • async scripts:

    • ℹ️ for inline (non-module) scripts, async is ignored and has no effect
    • ℹ️ for inline (module) scripts, async is supported source - to allow out-of-order, as-soon-as-possible execution
    • ✔️ downloaded without blocking HTML parser
    • ⚠️ out of order execution; executed as soon as available
    • ⚠️ relative order between async scripts execution is not guaranteed (also applies to async, type=module scripts)
    • ⚠️ doesn't wait for HTML parsing to finish; may interrupt DOM building (particularly when it gets served from browser's cache)
    • ⚠️ blocks load event (but not DOMContentLoaded event)
    • ⚠️ not supported in IE9-
  • async defer scripts:

type=module vs non-module (type=text/javascript) vs <script nomodule>

  • type=module scripts - differences wrt type=text/javascript scripts:

    • ℹ️ implies defer
    • ℹ️ for inline scripts, also implies defer (contrary to non-module scripts!)
    • ✔️ hence, guaranteed relative order of execution for all non-async module scripts (both inline and src)
    • ✔️ executes only once, even if script with same src is loaded multiple times
    • ℹ️ may use import to declare a dependency on another module script (that's one of the reasons why modules are deferred)
    • ℹ️ subject to CORS check (cross-origin modules need Access-Control-Allow-Origin: *)
    • ✔️ not executed by the browsers who don't support it
      • ⚠️ however they are still fetched apparently by IE11, Firefox 52 ESR etc.
  • <script nomodule>

Inline vs src

  • inline scripts (without src):

    • ℹ️ non-module inline scripts: async and defer are both ignored; script is blocking HTML parsers and DOM construction and is executed immediately
    • ℹ️ module inline scripts: implied defer; supporting async
    • not cacheable by the browser
  • src scripts:

    • ✔️ cacheable by the browser (given proper response headers), hence can be reused on future navigations without fetching from network

Summary of use cases for various kinds of scripts

type example use case
script src a legacy library that is needed by subsequent inline scripts
script src defer deferred execution, maintaining order; e.g. a lib that is needed by other defer scripts; progressive enhancement code
script src async deferred execution, without order (independent scripts); e.g. self-instantiating analytics lib
script src async defer like above, but with IE9 support
script inline 1) small piece of code that must be executed immediately, before some subsequent code (inlined polyfills, timers, server-generated configuration), or to register certain event listeners as soon as possible; 2) non-cacheable (generated, often changing etc.) code; 3) experience critical code that is small and the round-trip latency to download it separately would be too much
script src module library/app, for modern browsers only
script src module async progressive enhancement code, for modern browsers only
script inline module small piece of and/or non-cacheable code, for modern browsers only; perhaps inline config that is necessary for another non-async module declared after it
script inline module async small piece of and/or non-cacheable progressive enhancement code (independent script), for modern browsers only; it may imports a well-cacheable library
script nomodule ... a fallback script for legacy browsers, when shipping ES modules to modern browsers
script src async defer module nomodule left as an exercise for the reader

More reading

@GrosSacASac

This comment has been minimized.

Copy link

GrosSacASac commented Dec 3, 2018

The illustration and description of async is miss-leading.
It loads and executes with lower priority than all the other images, scripts, css. It most likely executes after everything else, after DOMContentLoaded. It was made a standard because the industry used it already in hand-made setTimeout + eval techniques to load ads and decorative features.
The problem with setTimeout is that you have to guess the time it takes for the page to load. If the guess is too long it is not optimal, if it is too low, it will execute before more important scripts... async solves this problem: With async it is possible to execute ads or decorative features as soon as possible, but it has low priority, so the meaning of as soon as possible should not be taken in a stand alone manner.

@GrosSacASac

This comment has been minimized.

Copy link

GrosSacASac commented Dec 3, 2018

async on script module will not execute earlier than script module without

@GrosSacASac

This comment has been minimized.

Copy link

GrosSacASac commented Dec 3, 2018

Putting script at the end of the body has the same effect as script defer, but script defer gives the browser opportunity to download and parse earlier

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.