JavaScript modules: import/export
export sends a variable, function or class out of a module, and import pulls an exported entity into another file. Together they form the built-in module system that arrived with ES6 (2015) and lets you split code into module files and share values, functions, classes and so on between them.
Theory
TL;DR
exportsends an entity outward,importloads it into another module.- A file may have any number of named exports; they are imported inside braces and under exactly their own names.
export defaultis allowed once per file, is imported without braces and under any name.- Renaming works both on export and on import through
as, andimport * as nscollects the whole module into one object. - Imports are static: you cannot write them inside
if,foror a function; for on-demand loading there isawait import(). - A module is loaded and cached once, and all of this only works in a module context (
type="module"in HTML, or.mjsor"type": "module"in Node.js).
Quick example
File math.js:
export const PI = 3.14;
export function add(a, b) {
return a + b;
}File app.js:
import { PI, add } from './math.js';
console.log(PI); // 3.14
console.log(add(2, 3)); // 5Named export and default export
A named export (export) lets you send several entities out of one file.
// math.js
export const PI = 3.14;
export function multiply(a, b) {
return a * b;
}They must be imported by name, inside braces, and the names have to match:
import { PI, multiply } from './math.js';A default export (export default) sends out the one main entity of the file, most often the "main" object, function or class.
// math.js
export default function add(a, b) {
return a + b;
}It can be imported under any name and without braces:
import sum from './math.js';
console.log(sum(2, 3)); // 5A comparison of the two forms:
| Criterion | export (named) | export default |
|---|---|---|
| Count per file | As many as you like | Exactly one |
| Import | Inside braces {} | Without braces |
| Name | Must match | Can be anything |
| Renaming on import | Through as | You simply write another name |
| Example | import { add } from './file.js' | import add from './file.js' |
The three typical combinations:
// math.js: named export
export const PI = 3.14;
export function add(a, b) { return a + b; }// app.js
import { PI, add } from './math.js';
console.log(add(1, 2)); // 3// math.js: default export (the function may be anonymous)
export default function(a, b) { return a * b; }// app.js
import multiply from './math.js';
console.log(multiply(2, 3)); // 6// math.js: mixed export
export const PI = 3.14;
export default function add(a, b) { return a + b; }// app.js
import add, { PI } from './math.js';
console.log(add(1, 2), PI); // 3 3.14Renaming, namespace import and re-export
An entity can be renamed on export:
function sum(a, b) { return a + b; }
export { sum as add };and on import:
import { add as plus } from './math.js';
plus(2, 3); // 5The whole module can be loaded as a single object, which is handy when there are many exports:
import * as math from './math.js';
console.log(math.PI); // 3.14
console.log(math.add(2, 3)); // 5A re-export lets one module repackage others, for example to give a folder a single entry file:
// utils.js
export { add, multiply } from './math.js';Now the consumer takes everything from one place:
import { add } from './utils.js';How modules behave
| Feature | Description |
|---|---|
Works only in modules (type="module" in HTML, or .mjs or "type": "module" in Node.js) | Otherwise you get a SyntaxError |
| Import and export are always static | They cannot be written inside if, for or a function |
| Modules are cached | Loaded once and reused afterwards |
| Module loading is asynchronous | For on-demand loading there is await import() |
| A module body runs in strict mode and has its own scope | Module variables never land on the global object |
Named imports are live bindings: if the module later changes an exported variable, the consumer sees the new value rather than a copy of the old one.
A dynamic import loads a module on demand and returns a promise:
const math = await import('./math.js');
console.log(math.add(2, 3)); // 5It works inside an async function or at the top level of a module, and it is exactly what code splitting is built on.
ES modules versus CommonJS
| Criterion | ES modules (import / export) | CommonJS (require / module.exports) |
|---|---|---|
| Syntax | import x from './m.js' | const x = require('./m.js') |
| When links are resolved | Statically, before execution | Dynamically, at run time |
| Where it works | Browser and Node.js | Historically Node.js |
| Loading | Asynchronous, with await import() | require is synchronous |
| What an import gives | Live bindings to the exported values | A copy of the module.exports object at call time |
| Tree shaking | Possible thanks to static analysis | Practically impossible |
// CommonJS
const { add } = require('./math.js');
module.exports = { add };
// ES modules
import { add } from './math.js';
export { add };Summary
| Keyword | What it does | Example |
|---|---|---|
export | Sends variables and functions outward | export const PI = 3.14; |
export default | Sends out the main entity of the file | export default class User {} |
import | Wires in the exported entities | import { PI } from './math.js'; |
Put simply:
exportmeans "send outward",importmeans "load inward", andexport defaultmeans "send the one main thing".
Common mistakes
- Forgetting the module context. A plain
<script src="app.js">does not understandimport; you need<script type="module" src="app.js">, and in Node.js either the.mjsextension or"type": "module"inpackage.json. - Mixing up the braces.
import { add } from './math.js'takes a named export,import add from './math.js'takes the default. If the module has no default, the second form yieldsundefinedor a bundler error. - Getting a named import's name wrong. Names must match character for character; to use another name you need
as:import { add as plus } from './math.js'. - Writing several
export defaultstatements in one file. Exactly one is allowed, otherwise it is aSyntaxError. - Putting
importinside a condition or a function. A static import is only valid at the top level of a module; for conditional loading there isawait import(). - Dropping the file extension in a relative path. In the browser and in Node.js with ES modules
'./math'does not resolve, you need'./math.js', unlike the habit from CommonJS and bundlers. - Expecting a module to run twice. Modules are cached: no matter how many files import it, the module body executes once.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.