import / export
import and export are the two halves of the ES module system. export declares what a file makes available; import pulls those values into another file. The syntax is small, but it has a few interesting properties that catch developers off guard — bindings are live, the structure is static, and there is a quiet difference between named exports and the single default export.
Named exports
A file can have any number of named exports. The exported name is the variable name (or you can rename on the way out).
utils.js
// Inline form — export at the point of declaration.
export const VERSION = "1.0.0";
export function greet(name) {
return "Hello, " + name;
}
export class Box {
constructor(value) { this.value = value; }
}
// Or list everything at the bottom — same effect.
const PI = 3.14159;
function area(r) { return PI * r * r; }
export { PI, area };
// Rename on the way out.
function _privateName() { return "secret"; }
export { _privateName as publicName };app.js
import { VERSION, greet, Box, area, publicName } from "./utils.js";
console.log(VERSION); // "1.0.0"
console.log(greet("Ada")); // "Hello, Ada"
console.log(area(2)); // 12.56636
console.log(publicName()); // "secret"
// Rename on the way in if a name collides.
import { greet as sayHi } from "./utils.js";
console.log(sayHi("Lin"));The default export
Each module may also export one anonymous default. It is conventionally used when the file is "about" a single thing — a React component, a class, a config object.
logger.js
export default function log(message) {
console.log("[log]", message);
}
// You can also have named exports alongside the default.
export const LEVEL = "info";app.js
// Default imports do not use braces, and you choose the name yourself.
import log, { LEVEL } from "./logger.js";
log("hello"); // [log] hello
console.log(LEVEL); // infoNamespace imports
If you want every named export grouped under one object, use * as:
app.js
import * as utils from "./utils.js";
console.log(utils.VERSION);
console.log(utils.greet("Ada"));
// utils.default — if utils.js has a default export, it appears here.This is handy when you only need one or two functions occasionally and don't want to maintain the import list. It also makes the source of each value obvious at the call site.
Re-exporting (barrels)
A module can forward exports from another module. This is how "barrel" files (the conventional index.js that re-exports a folder) are built.
components/index.js
export { Button } from "./Button.js";
export { Card } from "./Card.js";
// Rename on the way through.
export { default as Dialog } from "./Dialog.js";
// Re-export everything (named) from another file.
export * from "./icons.js";
// Re-export everything as a sub-namespace.
export * as forms from "./forms.js";app.js
import { Button, Card, Dialog, forms } from "./components/index.js";Imports are live, read-only bindings
This is the part that surprises people. An import is not a copy of the value — it is a live, read-only view of the export.
counter.js
export let count = 0;
export function increment() { count++; }app.js
import { count, increment } from "./counter.js";
console.log(count); // 0
increment();
console.log(count); // 1 — the imported binding updated
// But the importer cannot mutate the binding directly:
// count = 5; // TypeError: Assignment to constant variable.0 1
Static, hoisted, and analysed before code runs
import and export are not normal statements. The engine collects them first, builds the module graph, then runs the code. Practical consequences:
importdeclarations are hoisted to the top of the module — you can use the binding in code that appears above the import line (though most teams put imports at the top anyway for readability).You cannot put
import { x } from pathinside anifor a function. The path must be a string literal known at parse time.Need conditional or runtime loading? Use the function-like
import("...")instead — see Dynamic Imports.Bundlers can statically discover unused exports and drop them — the famous tree shaking.
Cheat-sheet
Every shape, in one place
// ---- exports ----
export const a = 1;
export function b() {}
export class C {}
export { a as one, b as two }; // rename
export default function () {} // default
export * from "./other.js"; // re-export all named
export { x } from "./other.js"; // re-export one
export { default as Y } from "./y.js"; // re-export default as named
// ---- imports ----
import "./side-effect.js"; // run for side effects, import nothing
import a from "./mod.js"; // default
import { x, y } from "./mod.js"; // named
import { x as ex } from "./mod.js"; // rename
import a, { x } from "./mod.js"; // default + named
import * as m from "./mod.js"; // namespace
import a, * as m from "./mod.js"; // default + namespaceRead on for two close cousins: Dynamic Imports for loading on demand, and CommonJS vs ES Modules for the older Node-style require.