Introduction to ES6 Modules
JavaScript modules allow developers to break down large codebases into smaller, manageable pieces. This modular approach not only enhances maintainability but also promotes code reuse across projects. Introduced in ECMAScript 2015 (ES6), the module system brought a standardized way of organizing and sharing JavaScript code.
Basic Concepts: export
and import
In ES6 modules, export
is used to expose variables, functions, or classes from one module so they can be imported and utilized elsewhere. Conversely, import
brings in these exports into another file where they are needed.
Named Exports
Named exports enable a module to export multiple items. Each item has its own name, which must be preserved when importing. Here’s how named exports work:
// utilities.js
export function cube(x) {
return x * x * x;
}
export const PI_PLUS_SQRT2 = Math.PI + Math.SQRT2;
To import these named exports into another module, you must use the same names unless you alias them with as
:
// app.js
import { cube, PI_PLUS_SQRT2 } from './utilities';
console.log(cube(3)); // 27
console.log(PI_PLUS_SQRT2); // approximately 4.5558
Alternatively, all named exports can be imported under a single namespace:
import * as utils from './utilities';
console.log(utils.cube(3)); // 27
console.log(utils.PI_PLUS_SQRT2); // approximately 4.5558
Default Exports
A module can have only one default export, making it ideal for exporting a single item or the primary functionality of that module. The import name is arbitrary and does not need to match any specific identifier.
Here’s how to define a default export:
// safeString.js
export default class SafeString {
constructor(string) {
this.string = string;
}
toString() {
return '' + this.string;
}
}
To import the default export, you can choose any name for it:
import CustomSafeString from './safeString';
const safeInstance = new CustomSafeString('Hello, world!');
console.log(safeInstance.toString()); // "Hello, world!"
Key Differences Between Named and Default Exports
-
Multiplicity:
- Named exports allow multiple items to be exported.
- There can only be one default export per module.
-
Import Syntax:
- For named exports, curly braces
{}
must enclose the imported names, matching their original definition unless aliased. - Default imports do not require curly braces and use any chosen name.
- For named exports, curly braces
-
Use Cases:
- Named exports are suitable for modules that export several related functionalities or utilities.
- Default exports are often used when a module provides one main functionality or class.
Practical Example
Consider three different modules, modul.js
, modul2.js
, and modul3.js
. The latter utilizes default export:
// modul3.js
export default function hello3() {
console.log("Module3: Saying hello for the third time!");
}
In an HTML file utilizing these modules, you can import hello3
with any name:
<script type="module">
import * as mod from './modul.js';
import { hello2 } from './modul2.js';
import greetThirdTime from './modul3.js';
mod.hello();
console.log("Module: " + mod.variable);
hello2();
greetThirdTime(); // Executes the function
</script>
Best Practices
- Consistency: Maintain consistency in using named versus default exports across a project to improve readability.
- Clarity: Use default exports for modules that export one main entity (e.g., a class or primary function) and named exports when multiple items are exported.
- Documentation: Clearly document the purpose of each module, especially if it contains both default and named exports.
Conclusion
JavaScript modules offer a powerful way to organize code effectively. Understanding how export
and default
work is crucial for leveraging their full potential. By mastering these concepts, developers can create modular, maintainable applications that are easy to scale and share across projects.