Loading...
Loading...
Split code into files. Import what you need, export what others can use.
Module 9 · Modern JavaScript (ES6+)
ES2015 and later additions transformed how JavaScript is written. let and const, template literals, modules, spread/rest, optional chaining, and nullish coalescing are now standard in every modern codebase.
Learn Modules in JavaScript — covering Named Exports, Default Exports, Import Patterns, Dynamic Imports with interactive visual examples and step-by-step explanations.
ES2015 (ES6) and the releases that followed added the syntax modern JavaScript is written in today. let and const replaced var, template literals replaced string concatenation, and import/export replaced global script tags.
This module covers the ES6+ features you will see in every codebase and job interview. Each chapter shows the old pattern alongside the modern equivalent so you can read legacy code and write idiomatic JavaScript.
Understanding modules is essential for every JavaScript developer. It shows up in frontend UI code, backend APIs, and framework internals — and getting it wrong leads to bugs that are hard to trace. This chapter builds intuition with visuals so the behavior sticks.
Export multiple values from a module by name.
// math.js — export individually: export const PI = 3.14159; export const E = 2.71828; export function add(a, b) { return a + b; } export function multiply(a, b) { return a * b; } // Or export all at once at the bottom: const PI = 3.14159; const E = 2.71828; function add(a, b) { return a + b; } function multiply(a, b) { return a * b; } export { PI, E, add, multiply }; // Rename on export: export { add as sum, multiply as product };
// Importing named exports: import { PI, add } from "./math.js"; console.log(PI); // 3.14159 console.log(add(2, 3)); // 5 // Rename on import: import { add as sum } from "./math.js"; sum(2, 3); // 5 // Import everything as a namespace: import * as math from "./math.js"; math.PI; // 3.14159 math.add(2, 3); // 5
One main export per module — imported without braces.
// User.js — one default export per file: export default class User { constructor(name) { this.name = name; } } // Or export separately: class User { /* ... */ } export default User; // Functions: export default function fetchData(url) { return fetch(url).then(r => r.json()); } // Even expressions: export default [1, 2, 3];
// Importing defaults — no braces, any name works: import User from "./User.js"; // ✓ import MyUser from "./User.js"; // ✓ same thing, different name import Whatever from "./User.js"; // ✓ name doesn't matter // Combine default + named: import React, { useState, useEffect } from "react"; import axios, { AxiosError } from "axios"; // ⚠️ Only ONE default per module: export default class A {} export default class B {} // ❌ SyntaxError
Named exports
Multiple per file
Must use exact name
Better for utilities
Default export
One per file
Any import name
Better for components
All the ways to import modules.
// Named import: import { useState, useEffect } from "react"; // Default import: import React from "react"; // Both: import React, { useState } from "react"; // Namespace (all as object): import * as utils from "./utils.js"; // Rename: import { render as renderDOM } from "react-dom"; // Side-effect only (no values imported): import "./styles.css"; import "./polyfills.js"; // Re-export (barrel files): // index.js export { Button } from "./Button.js"; export { Input } from "./Input.js"; export { Modal } from "./Modal.js"; // Now consumers: import { Button, Input, Modal } from "./components"; // Re-export everything: export * from "./math.js"; export { default } from "./User.js";
Load modules on demand — code splitting.
// import() returns a Promise: const module = await import("./heavy-module.js"); module.doSomething(); // Conditional loading: if (user.isAdmin) { const { AdminPanel } = await import("./AdminPanel.js"); renderAdmin(AdminPanel); } // Lazy load on event: button.addEventListener("click", async () => { const { openModal } = await import("./modal.js"); openModal(); });
Dynamic import() loads modules at runtime. The browser downloads the code only when the import is executed — perfect for code splitting.
Common conventions and best practices.
// 1. Barrel file (re-export pattern): // components/index.js export { Button } from "./Button"; export { Input } from "./Input"; export { Card } from "./Card"; // Import: import { Button, Input, Card } from "./components"; // 2. Module-level state (singleton): // counter.js let count = 0; export const increment = () => ++count; export const getCount = () => count; // Same instance shared across all importers // 3. Constants file: // config.js export const API_URL = "https://api.example.com"; export const MAX_RETRIES = 3; export const TIMEOUT = 5000; // 4. Type-only imports (TypeScript): import type { User, Post } from "./types"; import { type Config, loadConfig } from "./config";
Module features:
• Strict mode by default (no need for "use strict")
• Own scope (no global pollution)
• Executed once (cached on subsequent imports)
• Static structure (imports hoisted, analyzable)
• Live bindings (exports update when source changes)
Common questions about modules.