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.
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.
There are two types of exports: named and default.
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; } }
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;
import { PI, add } from './utils.js'; console.log(add(2, 3)); // 5
You can also rename imports:
import { add as sum } from './utils.js';
import log from './logger.js'; log('Hello world');
import * as Utils from './utils.js'; console.log(Utils.PI);
// 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';
| 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 |
Use the type="module" attribute in <script>:
<script type="module" src="main.js"></script>
defer is implicitly enabled, so the script loads after the HTML is parsed.Node.js added support for ES6 modules via:
.mjs extension, orpackage.json with "type": "module"{ "type": "module" }
// math.mjs export function square(x) { return x * x; }
// main.mjs import { square } from './math.mjs';
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 |
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>
| 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) |
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