Skip to content

Instantly share code, notes, and snippets.

@DahmaniAdame
Forked from jakub-g/async-defer-module.md
Created May 21, 2024 20:22
Show Gist options
  • Save DahmaniAdame/3e442f9db4696eb7ea066fdda89e991a to your computer and use it in GitHub Desktop.
Save DahmaniAdame/3e442f9db4696eb7ea066fdda89e991a to your computer and use it in GitHub Desktop.
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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment