JavaScript Modules

Modules let you split JavaScript code across multiple files. Each file is its own module. You explicitly declare what each module shares with other files (exports) and what it needs from them (imports).

Without modules, all your JavaScript shares the same global scope, which makes it easy to accidentally create naming collisions and hard to reason about where a value came from.

ES Modules

ES modules are the standard module system in modern JavaScript. They work in browsers and in Node.js.

Named exports

Export multiple things from one file by name.

// math.js
export function add(a, b) {
  return a + b;
}

export function subtract(a, b) {
  return a - b;
}

export const PI = 3.14159;

Import specific names using curly braces:

// app.js
import { add, PI } from './math.js';

console.log(add(2, 3)); // 5
console.log(PI);        // 3.14159

Default exports

Use a default export when a file has one main thing to share.

// greet.js
export default function greet(name) {
  return `Hello, ${name}!`;
}

Import a default export without curly braces. You can use any name you like.

import greet from './greet.js';

console.log(greet('Anna')); // Hello, Anna!

Importing everything

Import all named exports from a module using * as:

import * as MathUtils from './math.js';

console.log(MathUtils.PI);         // 3.14159
console.log(MathUtils.add(1, 2));  // 3

Renaming imports

Use as to rename an import at the point of use:

import { add as sum, subtract as minus } from './math.js';

console.log(sum(2, 3));   // 5
console.log(minus(5, 2)); // 3

Re-exporting (barrel exports)

You can re-export from another module. This is useful for creating an index file that collects exports from several files.

// index.js (barrel file)
export { add, subtract } from './math.js';
export { default as greet } from './greet.js';

Then import everything from one place:

import { add, greet } from './index.js';

ES modules in the browser

Add type="module" to your script tag:

<script type="module" src="app.js"></script>

Module scripts are deferred by default and always use strict mode.

ES modules in Node.js

Two options:

  1. Use the .mjs file extension.
  2. Add "type": "module" to your package.json.
{
  "type": "module"
}

After that, all .js files in the project are treated as ES modules.

Named vs default exports

A quick guide on when to use each:

SituationUse
Exporting multiple utilities from one fileNamed exports
One main thing per file (a component, a class)Default export
A mix of helpers and one primary thingDefault + named

You can combine both in the same file:

// users.js
export const MAX_USERS = 100; // named

export default class UserService { // default
  // ...
}

Legacy: CommonJS

Before ES modules, Node.js used CommonJS, which uses require() and module.exports. You will still see this in older Node.js code, many npm packages, and older tutorials.

// math.js (CommonJS)
function add(a, b) {
  return a + b;
}

module.exports = { add };
// app.js (CommonJS)
const { add } = require('./math.js');

console.log(add(2, 3)); // 5

CommonJS does not work in browsers. Modern Node.js supports both CommonJS and ES modules, but ES modules are recommended for new projects.

Common mistakes

Mixing CommonJS and ES module syntax

You cannot use import and require() in the same file. Pick one system and stick to it.

Forgetting the file extension in browser imports

In browsers, the full path including .js is required:

// Wrong in a browser
import { add } from './math';

// Right
import { add } from './math.js';

Bundlers like Vite and webpack resolve extensions automatically, but bare browser ES modules do not.

Circular imports

If file A imports from file B, and file B imports from file A, you have a circular import. JavaScript can handle them, but they often cause bugs where a value is undefined at import time. Restructure your code so the dependency only goes one way.

FAQ

Should I use CommonJS or ES modules?

Use ES modules for all new code. They are the standard, they work in browsers and Node.js, and they have better support for tree-shaking (removing unused code from bundles). Use CommonJS only when maintaining older Node.js projects.

What is the difference between named and default exports?

Named exports are identified by their variable name, so you import them with curly braces and the exact name. Default exports have no name; you give them one when you import them. A file can have many named exports but only one default export.