Understanding type="module"
in JavaScript: A Comprehensive Guide
JavaScript has evolved to include features that make coding more modular and maintainable. One such feature is the use of ES6 modules with type="module"
. In this blog, we will explore what type="module"
is, its features, and how it behaves in different scenarios.
What We Will Cover
- Introduction to
type="module"
- Features of ES6 Modules
- Behavior of Non-modular vs. Modular Scripts
- Complex Import Scenarios
- Summary of Key Points
Introduction to type="module"
When you include a JavaScript file in an HTML document, you typically do this:
<script src="script.js"></script>
With ES6 modules, you can use:
<script type="module" src="module.js"></script>
This tells the browser that the script is a module. Let’s dive into what this means.
Features of ES6 Modules
- Scoped Variables: Variables declared in a module are not global; they are scoped to that module.
- Import and Export: You can export functions, objects, or values from a module and import them into other modules.
- Deferred Execution: Module scripts are executed after the HTML document is fully parsed.
- Single Execution: Modules are only executed once, even if imported multiple times.
Behavior of Non-modular vs. Modular Scripts
Let’s look at an example to understand the differences.
Non-modular Scripts
Consider two scripts, script1.js
and script2.js
, both defining a function greet
.
script1.js:
function greet() {
console.log("Hello from script1!");
}
greet();
script2.js:
function greet() {
console.log("Hello from script2!");
}
greet();
HTML:
<script src="script1.js"></script>
<script src="script2.js"></script>
In this case, the greet
function in script2.js
overwrites the one in script1.js
. The output will be:
Hello from script2!
Hello from script2!
Modular Scripts
Now, let’s change the scripts to use modules.
HTML:
<script type="module" src="script1.js"></script>
<script type="module" src="script2.js"></script>
Since each module has its own scope, the greet
functions do not conflict. The output will be:
Hello from script1!
Hello from script2!
Complex Import Scenarios
Let’s explore a more complex scenario with three scripts.
script1.js:
import { functionFromScript2 } from './script2.js';
import { functionFromScript3 } from './script3.js';
console.log("This is from script1");
functionFromScript2();
functionFromScript3();
script2.js:
import { functionFromScript3 } from './script3.js';
console.log("This is from script2");
export function functionFromScript2() {
console.log("Function from script2");
functionFromScript3();
}
script3.js:
console.log("This is from script3");
export function functionFromScript3() {
console.log("Function from script3");
}
HTML:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Module Example</title>
</head>
<body>
<script type="module" src="script1.js"></script>
</body>
</html>
Detailed Explanation
When the HTML file is loaded, the browser will execute the module scripts. Here’s a step-by-step breakdown:
- script1.js Starts Execution
- The browser encounters the import statements in script1.js.
- script1.js imports script2.js.
2. script2.js Starts Execution
- script2.js starts executing.
- script2.js imports script3.js.
3. script3.js Starts Execution
- script3.js starts executing.
- It logs “This is from script3” to the console.
- Execution of script3.js completes.
4. script2.js Resumes Execution
- After importing script3.js, script2.js continues execution.
- It logs “This is from script2” to the console.
- It exports the functionFromScript2 function.
5. script1.js Resumes Execution
- After importing script2.js, script1.js imports script3.js again.
- Since script3.js has already been executed, it does not log again.
- script1.js continues execution and logs “This is from script1” to the console.
- It calls functionFromScript2 from script2.js.
6. functionFromScript2 Execution
- functionFromScript2 logs “Function from script2” to the console.
- It calls functionFromScript3 from script3.js.
- functionFromScript3 logs “Function from script3” to the console.
7. functionFromScript3 Execution in script1.js
- script1.js calls functionFromScript3 from script3.js again.
- functionFromScript3 logs “Function from script3” to the console again.
Final Output
The final console output will be:
This is from script3
This is from script2
This is from script1
Function from script2
Function from script3
Function from script3
Why This Happens
- Single Execution:
script3.js
is executed only once, even though it is imported twice (byscript1.js
andscript2.js
). - Sequential Import and Execution: The order of import statements determines the order of script execution.
- Function Calls Across Modules: Functions exported from one module and called from another execute as expected, maintaining their own scope and context.
Summary of Key Points
- Scoped Variables: Each module has its own scope, preventing conflicts.
- Import and Export: Modules can export and import functionality, promoting code reuse.
- Deferred Execution: Module scripts execute after the HTML is fully parsed.
- Single Execution: Modules are executed only once, even if imported multiple times.
- Better Dependency Management: Modules make it clear what dependencies exist and manage them efficiently.
By using type="module"
in JavaScript, you can write cleaner, more maintainable, and modular code. Understanding these concepts helps you leverage the full power of ES6 modules in your projects.