About <script> tag (defer, async, module)
MWC

MWC @project42da

Joined:
Jul 23, 2023

About <script> tag (defer, async, module)

Publish Date: Mar 3
0 0

Issues with the <script> Tag

  • Blocks browser parsing
    • HTML parsing is blocked until the scripts are downloaded and executed
    • In the past, <script>s were placed at the bottom of <body> to mitigate this issue
    • However, placing <script>s at the end of <body> doesn't solve the page loading speed issue, as the script still needs to be loaded and executed after parsing the HTML
  • Shares scope between <script>
    • <script>s share the same scope, which can lead to scope pollution if not handled carefully.

TL;DR

defer and async resolve the parsing-blocking issue, module prevents scope pollution.

  • When the browser encounters an inline script or <script> without async, defer, or type="module" during parsing,it pauses parsing to fetch and execute the script before continuing.(Render blocking)
  • Using module allows scripts to be modularized, ensuring that each module has its own scope.

async, defer, type module

defer

The browser downloads scripts with the defer attribute(hereafter referred to as deferred scripts) in parallel with HTML parsing, allowing the page to continue rendering while the script is being fetched in the background.

As as result, HTML parsing is not blocked while a deferred scripts is being downloaded. Additionally, execution of the deferred scripts are delayed until the page has finished rendering.

The defer attribute only applies to external scripts. if a <script> tag does not have a src attribute, the defer attribute is ignored, and the script executes immediately, potentially blocking HTML parsing.

<div>...Content before the script...</div>

<!-- The DOMContentLoaded event fires after the deferred script is executed -->
<script>
  document.addEventListener('DOMContentLoaded', () => {
    alert("The `defer` script has executed, and the DOM is now ready!");
  });
</script>
<script defer src="bigscript.js"></script>

<!-- This content is visible immediately! -->
<div>...Content after the script...</div>
Enter fullscreen mode Exit fullscreen mode

Key Characteristics of Deferred Scripts

  • Deferred scripts never block page rendering.
  • Deferred scripts execute after the DOM is fully parsed but before the DOMContentLoaded event fires.
    • The DOMContentLoaded event waits for all deferred scripts to finish executing.
    • In the example above, the alert appears only after the DOM tree is complete and the deferred script has executed.
  • Deferred scripts execute in the same order they appear in the HTML document.
    • Even if a smaller script downloads faster, it will execute in the order it appears in the document.

async

Scripts with the async attribute (async scripts) operate completely independently from the rest of the page.

  • Like defer scripts, async** scripts are downloaded in the background.**
    • This means the HTML page continues processing and rendering its content without waiting for the script to finish downloading.
    • However, when an async script executes, HTML parsing is temporarily blocked until the script finishes running.
  • Unlike defer, DOMContentLoaded and async scripts do not wait for each other, meaning their execution order is not guaranteed.
    • If the async script finishes downloading after the page has fully loaded, DOMContentLoaded may fire before the script executes.
    • Conversely, if the async script is small or cached, it may execute before DOMContentLoaded fires.
  • Other scripts do not wait for async scripts, and async scripts do not wait for other scripts.
    • Because of this, if there are multiple async scripts on a page, their execution order is unpredictable.
    • Scripts execute as soon as they finish downloading, regardless of their order in the document.
  • async scripts are generally used for tasks that do not affect DOM rendering, such as Google Analytics.

type="module"

Declares the script as a JavaScript module. Scripts with the module type always behave as if they have the defer attribute, meaning they load resources in parallel and do not block HTML parsing.

<script type="module">
// Code inside the script is also treated as a module.
</script>
<script type="module" src="function.js"></script>
Enter fullscreen mode Exit fullscreen mode

Since the script is treated as a module, module requests are loaded and executed only once.

<script src="classic.js"></script>
<script src="classic.js"></script>
<!-- classic.js executes multiple times. -->

<script type="module" src="module.mjs"></script>
<script type="module" src="module.mjs"></script>
<script type="module">import './module.mjs';</script>
<!-- module.mjs executes only once. -->
Enter fullscreen mode Exit fullscreen mode
  • Module scripts are executed only once, even if they are included multiple times in the HTML or imported from other modules.
  • This ensures that resources like modules are loaded only once, preventing redundant executions.

To treat a script as a module, the type="module" attribute must be set. This is necessary for using module code inside the <script> tag or in a file loaded via the src attribute.

  • Otherwise, you’ll encounter the error: Uncaught SyntaxError: Cannot use import statement outside a module. (The import statement cannot be used outside of a module.)

Solving Scope Pollution with module

Code written inside a <script> tag is available globally, which increases the risk of scope pollution.

<script>
  const a = "a";
</script>
<script type="module">
  console.log(a, c); // "a" "c"
  const b = "b";
</script>
<script src="c.js">
// c.js code
// const c = "c";
// The variable c is initialized with "c".
</script>
<script>
  console.log(a); // "a"
  console.log(c); // "c"
  console.log(b); // Uncaught ReferenceError: b is not defined
</script>
Enter fullscreen mode Exit fullscreen mode
  • Even when dividing the code across multiple <script> tags or external files, all scripts share the same global scope, which poses a risk for errors and unintended side effects.
  • By using modules, each script has its own isolated scope, making it safer and easier to manage code.
    • Code declared inside a module can only be accessed through export and import statements.
  • In the example above, the script with the module type is handled asynchronously, meaning it is executed last.
  • Note: When using the src attribute in a <script> tag, inline scripts will not be executed.

References

Comments 0 total

    Add comment