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
asyncattribute tells the browser to continue parsing HTML while the JavaScript file is downloading. Without theasyncattribute, the initial rendering of the page could be significantly delayed on devices with a slower connection speed. -
The
deferattribute tells the browser to avoid executing the JavaScript until the entire document has been parsed. Without thedeferattribute, 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.