JavaScript Modules (ES6 Import/Export)
Answer
ES6 Modules provide a native way to organize JavaScript code into reusable pieces with explicit exports and imports, replacing older patterns like CommonJS and AMD.
Module Syntax
Named Exports
// math.js - Exporting
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
export class Calculator {
// ...
}
// Alternative: Export at the end
const multiply = (a, b) => a * b;
const divide = (a, b) => a / b;
export { multiply, divide };
// app.js - Importing named exports
import { add, subtract, PI } from "./math.js";
console.log(add(2, 3)); // 5
console.log(PI); // 3.14159
// Rename on import
import { add as addNumbers, subtract as sub } from "./math.js";
addNumbers(1, 2);
// Import all as namespace
import * as math from "./math.js";
math.add(1, 2);
math.PI;
math.Calculator;
Default Export
// logger.js - Default export
export default class Logger {
log(message) {
console.log(`[LOG] ${message}`);
}
error(message) {
console.error(`[ERROR] ${message}`);
}
}
// Alternative syntax
class Logger { /* ... */ }
export default Logger;
// Also works with functions
export default function greet(name) {
return `Hello, ${name}!`;
}
// app.js - Importing default exports
import Logger from "./logger.js"; // No braces!
import greet from "./greet.js";
const logger = new Logger();
logger.log("Starting app");
console.log(greet("World"));
// You can name default imports anything
import MyLogger from "./logger.js";
import sayHello from "./greet.js";
Mixed Exports
// api.js
export const BASE_URL = "https://api.example.com";
export function get(endpoint) {
/* ... */
}
export function post(endpoint, data) {
/* ... */
}
// Default export alongside named exports
export default class ApiClient {
constructor(baseUrl = BASE_URL) {
this.baseUrl = baseUrl;
}
}
// Using mixed exports
import ApiClient, { BASE_URL, get, post } from "./api.js";
// Or
import ApiClient, * as api from "./api.js";
api.get("/users");
api.BASE_URL;
Re-exporting
// components/index.js - Barrel file
export { default as Button } from "./Button.js";
export { default as Input } from "./Input.js";
export { default as Modal } from "./Modal.js";
// Re-export everything
export * from "./Button.js";
// Re-export with rename
export { Button as CustomButton } from "./Button.js";
// Usage - cleaner imports
import { Button, Input, Modal } from "./components";
Dynamic Imports
// Static imports (hoisted, runs at load time)
import { setup } from "./setup.js";
// Dynamic imports (returns Promise, runs at call time)
async function loadFeature() {
const module = await import("./heavy-feature.js");
module.initialize();
}
// Conditional loading
if (user.isPremium) {
const { premiumFeatures } = await import("./premium.js");
premiumFeatures.enable();
}
// Code splitting in React
const LazyComponent = React.lazy(() => import("./HeavyComponent"));
CommonJS vs ES Modules
| Feature | CommonJS | ES Modules |
|---|---|---|
| Syntax | require/exports | import/export |
| Loading | Synchronous | Asynchronous |
| Structure | Dynamic | Static (analyzable) |
| Tree shaking | No | Yes |
| Top-level await | No | Yes |
| Node.js default | Yes (.js) | Need .mjs or type: "module" |
// CommonJS
const fs = require("fs");
const { readFile } = require("fs");
module.exports = { myFunc };
module.exports.default = myFunc;
// ES Modules
import fs from "fs";
import { readFile } from "fs";
export { myFunc };
export default myFunc;
Browser Usage
<!-- Type module for ES6 imports -->
<script type="module" src="app.js"></script>
<!-- Fallback for older browsers -->
<script nomodule src="app-legacy.js"></script>
<!-- Inline module -->
<script type="module">
import { greet } from "./utils.js";
console.log(greet("World"));
</script>
Node.js Configuration
// package.json
{
"type": "module" // Makes .js files use ES modules
}
// Or use .mjs extension for ES modules
// and .cjs extension for CommonJS
Best Practices
// ✅ Use named exports for utilities
export function formatDate(date) {
/* ... */
}
export function parseDate(str) {
/* ... */
}
// ✅ Use default export for main component/class
export default class DatePicker {
/* ... */
}
// ✅ Create index.js barrel files
// components/index.js
export { Button } from "./Button";
export { Input } from "./Input";
// ✅ Use consistent naming
// Bad: export default function() {}
// Good: export default function formatDate() {}
// ✅ Avoid side effects in modules
// Bad
let counter = 0;
export function getCount() {
return counter++;
}
Key Points
- Named exports use
{}; default exports don't - One default export per module, unlimited named
- Use barrel files (index.js) for clean imports
- Dynamic import() for code splitting
- ES Modules enable tree shaking
- Use
type: "module"in package.json for Node.js