Learn to organize code with ES modules — import/export syntax, named and default exports, and module patterns.
As applications grow, keeping all code in a single file becomes unmanageable. Modules let you split code into separate files, each with its own scope.
Benefits of modules:
Before ES modules, developers used various patterns to achieve modularity:
IIFEs (Immediately Invoked Function Expressions):
const myModule = (function() {
const privateVar = 'hidden';
return { publicMethod: () => privateVar };
})();CommonJS (Node.js):
const fs = require('fs');
module.exports = { myFunction };ES modules (ESM) are now the official standard for JavaScript modules, supported natively in browsers and Node.js.
Named exports let you export multiple values from a module. Each export has a specific name that must be used when importing.
Exporting — use the export keyword before a declaration:
// math.js
export const PI = 3.14159;
export function add(a, b) { return a + b; }
export class Calculator { /* ... */ }Or export multiple values at once using an export list:
const PI = 3.14159;
function add(a, b) { return a + b; }
export { PI, add };Importing — use curly braces with the exact export names:
import { PI, add } from './math.js';
console.log(add(2, 3)); // 5
console.log(PI); // 3.14159Renaming on import with as:
import { add as sum } from './math.js';
console.log(sum(2, 3)); // 5Importing everything as a namespace object:
import * as math from './math.js';
console.log(math.PI); // 3.14159
console.log(math.add(2, 3)); // 5A default export is the "main" thing a module provides. Each module can have at most one default export.
Exporting a default value:
// greet.js
export default function greet(name) {
return 'Hello, ' + name + '!';
}Or with an expression:
// config.js
export default {
theme: 'dark',
language: 'en'
};Importing a default export — no curly braces needed, and you can use any name:
import greet from './greet.js';
import myConfig from './config.js';
console.log(greet('World')); // 'Hello, World!'
console.log(myConfig.theme); // 'dark'Combining default and named exports:
// utils.js
export default function main() { /* ... */ }
export function helper() { /* ... */ }
export const VERSION = '1.0';import main, { helper, VERSION } from './utils.js';Default exports are commonly used for the primary export of a file — a class, a component, or the main function.
ES modules have several important features that distinguish them from regular scripts.
Strict mode by default — modules automatically run in strict mode. You do not need 'use strict';.
Module-level scope — variables declared in a module are scoped to that module, not added to the global object:
// In a module
const secret = 'hidden'; // Not accessible from other modulesImports are hoisted — import statements are moved to the top during parsing, regardless of where you write them.
Static structure — imports and exports are analyzed at build time, not runtime. This enables tree-shaking (removing unused code) in bundlers.
Dynamic import — for cases where you need to load a module conditionally or on demand, use import() as a function. It returns a Promise:
const button = document.getElementById('load');
button.addEventListener('click', async () => {
const module = await import('./heavy-feature.js');
module.init();
});Dynamic imports are useful for code splitting — loading parts of your application only when needed, improving initial load time.
In the browser, use <script type="module"> to load ES modules:
<script type="module" src="app.js"></script>// ========== Named Exports ==========
// Inline export
export const name = 'Kodojo';
export function greet(who) { return `Hello, ${who}!`; }
// Export list
const a = 1;
const b = 2;
export { a, b };
// Renaming on export
export { a as alpha, b as beta };
// ========== Default Export ==========
export default class App {
constructor() { this.name = 'Kodojo'; }
}
// ========== Imports ==========
// Named imports
import { name, greet } from './module.js';
// Renaming on import
import { name as appName } from './module.js';
// Namespace import
import * as mod from './module.js';
// Default import
import App from './module.js';
// Default + named
import App, { name, greet } from './module.js';
// Dynamic import (returns a Promise)
const mod2 = await import('./module.js');How many default exports can a module have?