Skip to content

Modules (Import/Export)

ES6 modules provide a standardized way to organize and share code across files. Introduced in ES6 (2015), they allow importing and exporting functions, objects, and values between JavaScript files.

Various non-standard solutions existed:

// CommonJS (Node.js)
const express = require('express');
module.exports = myFunction;
// AMD (RequireJS)
define(['dependency'], function(dep) {
return myModule;
});
// Global variables (browser)
window.myLibrary = { /* ... */ };
// IIFEs to avoid global pollution
(function() {
// private code
window.myModule = { /* public API */ };
})();

Standard, clean module syntax:

// math.js - Named exports
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
// Alternative: export at end
function multiply(a, b) {
return a * b;
}
function divide(a, b) {
return a / b;
}
export { multiply, divide };
// main.js - Import named exports
import { add, subtract } from './math.js';
import { PI } from './math.js';
// or import all
import * as math from './math.js';
console.log(math.add(2, 3)); // 5
// user.js - Default export (one per file)
export default class User {
constructor(name) {
this.name = name;
}
}
// app.js - Import default
import User from './user.js';
const alice = new User('Alice');
// Mix default and named exports
export default function main() { /* ... */ }
export const VERSION = '1.0.0';
export const DEBUG = true;
// Import both
import main, { VERSION, DEBUG } from './app.js';
// Rename imports
import { add as sum } from './math.js';
// Re-export from another module
export { add, subtract } from './math.js';
export * from './utils.js';
  • Standard: Same syntax for browser and Node.js (with ESM)
  • Explicit: Clear dependencies at top of file
  • Tree-shaking: Bundlers can remove unused exports
  • Static analysis: Import/export are static, enabling better tooling
  • Scoped: Each module has its own scope (no global pollution)
  • Async loading: Modules load asynchronously in browsers
  • File extension required in browsers (.js, .mjs)
  • Imports are hoisted (executed before other code)
  • Imports are read-only bindings (can’t reassign)
  • Default export can be unnamed
  • Can’t use import/export inside blocks (must be top-level)
  • Dynamic imports with import() for lazy loading
// Dynamic import (ES2020)
button.addEventListener('click', async () => {
const module = await import('./heavy-feature.js');
module.initialize();
});
// Conditional import
if (condition) {
import('./module-a.js'); // Error! Must be top-level
}
// Use dynamic import instead
if (condition) {
import('./module-a.js').then(module => {
module.doSomething();
});
}
// Import is read-only
import { count } from './counter.js';
count = 10; // TypeError!
// Default export can be expression
export default function() { /* ... */ }
export default class { /* ... */ }
export default { key: 'value' };
  1. Export a function formatDate from a file

    Answer
    export function formatDate(date) {
    return date.toISOString();
    }
  2. Import formatDate and use it as format

    Answer
    import { formatDate as format } from './utils.js';
  3. What’s the difference between default and named exports?

    Answer Default export: one per file, imported without braces. Named exports: multiple per file, imported with braces and exact name (unless renamed).