Skip to main content

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

FeatureCommonJSES Modules
Syntaxrequire/exportsimport/export
LoadingSynchronousAsynchronous
StructureDynamicStatic (analyzable)
Tree shakingNoYes
Top-level awaitNoYes
Node.js defaultYes (.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