Decision Load JavaScript as external resource with async and defer attributes

deprecated

Loading JavaScript as async & deferred has the best default performance

Decision

This ADR has been replaced by this new version.

When loading external JavaScript files in HTML, they will have the async and defer attributes.

  <script src="/path/to/file.js" async defer>

When adding JavaScript files with Drupal Libraries, this will require adding the async and defer attributes on a per-file basis:

my_library:
  version: 1.0.0
  js:
    js/my-script.js:
      attributes:
        async: true
        defer: true

Context

Adding async and defer to <script src="..."> tags will create the most performant page-loading experience:

  • The async attribute tells the browser to continue parsing HTML while the JavaScript file is downloading. Without the async attribute, the initial rendering of the page could be significantly delayed on devices with a slower connection speed.

  • The defer attribute tells the browser to avoid executing the JavaScript until the entire document has been parsed. Without the defer attribute, the initial rendering of the page could be significantly delayed on devices with older or slower processors.

By adding both the async and defer attributes on <script src="..."> tags, we minimize the impact of external JavaScript files as much as possible.

Exceptions

There are rare instances when JavaScript should be added into the body of the HTML document, such as removing a no-js class from the <html> element. In this case, inline blocking JavaScript is preferable to avoid cumulative layout shifts and perceived reflows of the document. For example, in the code below, the HTML document is sent to the browser with a no-js class to prevent displaying portions of the page that require JavaScript. By adding an inline style early in the document, we can prevent the browser from rendering the page once with the no-js class and then reflowing when no-js is removed.

<html class="no-js">
  <head>
    <script>
      document.documentElement.classList.remove('no-js');
    </script>
    <style>
      .no-js #app {
        display:none;
      }
    </style>
  </head>
  <body>
  </body>
</html>

Consequences

Requesting & executing JavaScript in this manner should lead to more performant page loading and the most consistent script execution since scripts will not run until the entire DOM is in place. This, in turn, will create a better experience for end-users and a better score on objective performance metrics.

Additional Resources