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>
withoutasync
,defer
, ortype="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.
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>
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.
- The
- 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
andasync
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 beforeDOMContentLoaded
fires.
- If the
-
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.
- Because of this, if there are multiple
-
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>
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. -->
- 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>
- 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
andimport
statements.
- Code declared inside a module can only be accessed through
- 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.