JavaScript applications have grown massively in size and complexity over the years. To manage this growth, developers need tools for organizing code, avoiding global scope pollution, and promoting reusability. Enter the ES6 Module System β a native solution to modular programming in JavaScript.
This article explores what ES6 modules are, how they work, and why they're now the standard for modern JavaScript development.
π What Are ES6 Modules?
An ES6 Module is a JavaScript file that explicitly exports variables, functions, or classes so they can be imported into other files.
They were introduced in ECMAScript 2015 (ES6) and are now supported in all modern browsers and Node.js environments.
π― Why Use Modules?
- Encapsulation: Avoid polluting the global namespace.
- Reusability: Share logic across files.
- Maintainability: Organize code by responsibility.
- Dependency Management: Declare whatβs used where.
- Performance: Modules are statically analyzable (great for bundlers and tree-shaking).
π€ Exporting from a Module
There are two types of exports: named and default.
1. Named Exports
You can export multiple items from the same module:
// utils.js export const PI = 3.14; export function add(a, b) { return a + b; } export class Circle { constructor(radius) { this.radius = radius; } }
2. Default Export
Each module can have one default export:
// logger.js export default function log(msg) { console.log(msg); }
You can also export a value directly:
export default 42;
π₯ Importing in Other Files
1. Import Named Exports
import { PI, add } from './utils.js'; console.log(add(2, 3)); // 5
You can also rename imports:
import { add as sum } from './utils.js';
2. Import Default Export
import log from './logger.js'; log('Hello world');
3. Import All (Namespace Import)
import * as Utils from './utils.js'; console.log(Utils.PI);
π§ͺ Combining Named and Default Exports
// math.js export const multiply = (a, b) => a * b; export default function divide(a, b) { return a / b; }
// main.js import divide, { multiply } from './math.js';
βοΈ Module Characteristics
| Feature | Behavior |
|---|---|
| File-scoped | Variables stay within the module, not global |
| Strict mode by default | No need for "use strict" |
| Singleton execution | Module is loaded and run once (shared instance) |
| Static structure | Imports/exports are statically analyzed |
| Top-level only | import/export must be at the top level of a file |
π Using Modules in Browsers
Use the type="module" attribute in <script>:
<script type="module" src="main.js"></script>
Notes:
- Modules run in strict mode.
deferis implicitly enabled, so the script loads after the HTML is parsed.- Module scripts are scoped, so their top-level declarations are not global.
π Using Modules in Node.js
Node.js added support for ES6 modules via:
- Files with
.mjsextension, or package.jsonwith"type": "module"
Example:
{ "type": "module" }
// math.mjs export function square(x) { return x * x; }
// main.mjs import { square } from './math.mjs';
π¦ ES6 Modules vs CommonJS (Node.js require())
| Feature | ES6 Modules (import/export) | CommonJS (require/module.exports) |
|---|---|---|
| Syntax | import / export | require() / module.exports |
| Execution | Static | Dynamic |
| Top-level only | Yes | No |
| Tree-shaking | β Supported | β Not supported |
| Standard | β Official ECMAScript | β Node-specific |
π§± Real-World Example: Building a Module-Based App
Directory Structure:
project/
+-- index.html
+-- main.js
+-- utils/
| +-- math.js
math.js
export function add(a, b) { return a + b; } export function subtract(a, b) { return a - b; }
main.js
import { add, subtract } from './utils/math.js'; console.log(add(10, 5)); // 15 console.log(subtract(10, 5)); // 5
index.html
<script type="module" src="main.js"></script>
π Common Mistakes to Avoid
| Mistake | Fix |
|---|---|
Forgetting type="module" in <script> | Add type="module" |
Using import inside functions or conditionals | Move to top-level scope |
Using .js extension inconsistently | Always include extension in browser environments |
| Mixing CommonJS and ES6 modules | Use only one format per project (or use interop carefully) |
π Conclusion
The ES6 module system is now the standard way to organize JavaScript code, replacing older patterns like IIFEs, CommonJS, and AMD. It improves code clarity, enforces modular structure, and provides powerful tooling benefits (like tree-shaking and scope isolation).
If you're building modern JavaScript applicationsβwhether in the browser or Node.jsβyou should embrace ES6 modules as your go-to solution for scalable code architecture.
Test your understanding with 3 quick questions