Core JavaScript · 9 of 9
ES modules give you three ways to expose things from a file.
// format.js
export const formatDate = (d) => { /* ... */ };
export const formatCurrency = (n) => { /* ... */ };
// usage
import { formatDate, formatCurrency } from "./format.js";
Named exports mirror namespace members most closely, and they come with a real benefit: an editor can rename formatDate everywhere it's used. A default export can't offer that — every importer is free to call it whatever they like.
// logger.js
export default function (message) { /* ... */ };
// usage
import log from "./logger.js";
import whatever from "./logger.js"; // with default exports you can call the function anything you like
Only use default exports for a file with one obvious main export (a single component / single function). For anything else, use named exports
// index.js — the folder's public surface
export { formatDate, formatCurrency } from "./format.js";
export { default as log } from "./logger.js";
// ./format-internals.js is absent, so it isn't part of the API
A barrel lists what a folder is willing to hand out. Anything left off it stays an implementation detail you can move or rewrite without checking who imported it.
Prefer export { … } from over export *. The wildcard re-exports whatever the source file happens to expose, so new exports join the public surface the moment someone adds them.
The barrel states the boundary; it doesn't enforce it — a consumer can still reach past it to ./format-internals.js. That needs tooling: an ESLint no-restricted-imports rule, or the exports field in package.json for a published package.