By default, everything a JavaScript file declares belongs to that file alone. A constant written in cart.js exists nowhere else, and that is a good thing: without that separation, two files using a variable called total would trample each other.
export is the door you deliberately open in that wall. Whatever you do not export stays private, which makes every export line a design decision rather than a syntax detail.
Definition
export marks a value, a function or a class as reachable from another file, which picks it up with import. Everything else in the file stays invisible from the outside.
// currency.js
const SYMBOL = '$'; // private: never exported
export const DECIMALS = 2;
export function format(amount) {
return SYMBOL + amount.toFixed(DECIMALS);
}Here SYMBOL takes part in the calculation but never leaves the file. Another module can neither read nor change it, yet it can call format() without knowing that it exists.
Named or default
A file can carry as many named exports as it needs, and at most one default export.
| Written as | On the import side | When to use it |
|---|---|---|
export const A = 1 | import { A } | Several values of equal weight |
export default myFunction | import anyNameYouLike | The file offers only one thing |
The default export is declared with the default keyword and leaves the naming to whoever imports it. Convenient, but harder to search for across a project: three files can call the same thing by three different names.
The ways to write it
The keyword sits in front of a declaration, or in a list at the end of the file, which then reads as a summary of what the module publishes.
function format(m) { return m.toFixed(2); }
const DECIMALS = 2;
export { format, DECIMALS };
export { format as formatPrice }; // renaming
export { DECIMALS as default }; // a default export, the other way
export * from './other-currency.js'; // bulk re-exportThat last form powers index files, which gather several modules behind a single address so the rest of the project has shorter imports to write.
An export is a link, not a copy
This is the least known and most useful part. What travels is not the value at import time, but the variable itself. If the exporting file changes it, every file that imported it reads the new value.
// counter.js
export let visits = 0;
export function visit() { visits += 1; }
// page.js
import { visits, visit } from './counter.js';
console.log(visits); // 0
visit();
console.log(visits); // 1The link only runs one way. The importing file can read the new value, never write one: on the import side, the name behaves like a const.
Frequently asked questions
Can I export from inside a condition?
No. Like import, the keyword only exists at the top level of a file, never inside an if, a loop or a function. That constraint lets tools know what a module publishes without running it, and it is what makes dead-code elimination possible at build time.
Should I prefer named exports?
On a team project, usually yes: the name is fixed, so a search through the code finds every use at once. The default export keeps its place when a file holds exactly one obvious thing, a component or a class for instance.
What if I export an object and someone changes it elsewhere?
The change is visible everywhere, because every file handles the same object in memory. Exporting a mutable structure amounts to creating shared state, which is sometimes intended and often the source of bugs that are hard to locate. Prefer exporting a function that returns a copy, or freeze the object with Object.freeze().