Decision Load JavaScript as external resource with async and defer attributes
deprecatedLoading 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 theasync
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 thedefer
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.