import & export
Learn how JavaScript modules share named exports, default exports, renamed imports, re-exports, live bindings, and strict module scope without leaking globals.
- 01Share and use valuesExport values from one file and import them in another: named, default, all at once, or under a new name.
- 02Pass exports onUse an index file that passes on exports from other files, and know what happens when two names clash.
- 03Know what an import isExplain why an import shows changes, why you can't assign to it, and why modules are private and strict.
What import and export mean
A module is a JavaScript file that keeps its code to itself. Its variables and functions are private: other files can't see them. A module can choose a few things to share:
exportmarks a value that this file shares.importlets another file use a shared value.
Big websites are not one long file. They are made of hundreds of small files. Each file shares only what other files need and keeps the rest private. That way you can see which file uses what.
export shares a name from one file, and import gives another file a read-only name that looks at that shared value.
Modules use ideas from three earlier lessons: Scope & lexical environments, Strict mode, and Hoisting & the temporal dead zone. Open one of them if that idea feels unclear.
Named and default exports
INTERACTIVEA file can share values in two ways:
- A named export has a name, such as
price. To import it, write that exact name inside curly braces:import { price }. - A default export is the file's one main value. Its export name is
default. To import it, leave out the curly braces and pick any name you like.
A file can have many named exports, but only one default export.
Picture a small cafe. Every item on the shelf has a label, so you ask for "tea" by name. The cafe also has one house special. You can order it and call it "lunch" or "special" at your table. Everyone still gets the same dish.
- In real life: Items on a shelf, each with a label
- In JavaScript: Named exports, like
teaandsize - In real life: Asking for an item by its label
- In JavaScript:
import { tea } from './shop.js' - In real life: The house special
- In JavaScript: The default export, which you import under any name
- In real life: Many items, but one house special
- In JavaScript: Many named exports, but one default export
// shelf.jsexport const price = 5;export default "house special";export { price as cost }; // order.jsimport { price, cost } from "shelf";import special from "shelf";import * as shelf from "shelf";console.log(price, cost, special, shelf.default);The file shelf.js shares three exports:
- Line 2 makes a named export,
price, with the value 5. - Line 3 makes the default export, the text
"house special". - Line 4 shares
pricea second time, under the namecost.
In order.js, line 7 asks for price and cost by name. Line 8 takes the default export and calls it special. Line 10 prints 5 5 house special house special.
price by its exact export name. Line 8 takes the default export and picks its own name for it, special.Line 9, import * as shelf, imports everything at once. It gives you one object, shelf, with each export as a property: shelf.price, shelf.cost, and shelf.default. JavaScript calls it a module namespace object. It is read-only: you can't add, change, or delete its properties. It also has no prototype, so it has no inherited methods like toString().
The lab below runs real modules in a small test page inside this page, called an iframe. Pick what the first file exports and how the second file imports it.
export const tea = "green";export default "house special"; import { tea } from "shop";console.log(tea);Loading the chosen code in the test page…
Pick what the file exports and how the other file imports it. The code runs as real modules in a small test page (an iframe).
Try a mismatch. Choose "Default export only" and import { tea }. The lab shows the real browser error. The browser checks every import before any line of the file runs, so none of the file's code runs.
Renaming with as
CONCRETEThe word as gives an import or an export a new name. On an import, the new name is used only in the importing file. On an export, the new name is the one other files ask for. Either way, the original variable doesn't change. Use as when:
- two files export the same name,
- the name would clash with a variable you already have,
- a clearer name would help.
You buy two products that are both labeled "name". At home, you put your own sticker on one: "userName". Now you can tell them apart. The labels in the shop stay the same.
- In real life: The package says
cost - In JavaScript: The exporting file shares
cost - In real life: You add your own sticker that says
price - In JavaScript:
import { cost as price } - In real life: The shop's label stays the same
- In JavaScript: Other files still ask for
cost
// prices.jsconst cost = 12;export { cost as priceInDollars }; // cart.jsimport { priceInDollars as itemPrice } from "./prices.js";const price = "shown in the UI";console.log(itemPrice, price);Line 3 shares cost under a new name, priceInDollars. Line 6 asks for priceInDollars and calls it itemPrice in this file. That leaves the name price free for line 7. Line 8 prints 12 shown in the UI.
Read priceInDollars as itemPrice as: "ask for priceInDollars, then call it itemPrice here." You can rename on export, on import, or both.
Passing exports on
INTERACTIVEA file can pass on exports from other files. This is called a re-export. A file that mostly re-exports is called a barrel file, and it is usually named index.js. Other files can then import from this one place, instead of from each file one by one.
In an office building, reception sends you on to the right office. Reception makes your visit easier. It doesn't do the work of every office itself.
- In real life: The reception desk
- In JavaScript:
index.js, the barrel file - In real life: Sending you on to the math office
- In JavaScript:
export * from './math.js' - In real life: Sending you to one person in another office
- In JavaScript:
export { shout } from './strings.js' - In real life: Handing you the list of one whole office
- In JavaScript:
export * as strings from './strings.js'
// math.jsexport const add = (a, b) => a + b;export const name = "math"; // strings.jsexport const shout = (text) => text.toUpperCase();export const name = "strings"; // index.jsexport * from "math";export { shout } from "strings";export * as strings from "strings"; // app.jsimport { add, shout, strings } from "index";console.log(add(2, 3), shout("ok"), strings.name);The three lines in index.js each pass on exports in a different way:
- Line 10,
export * from, passes on every named export ofmath.js. - Line 11 passes on just one export of
strings.js:shout. - Line 12 passes on all of
strings.jsas one object, namedstrings.
Line 15 imports all three from index.js. Line 16 prints 5 OK strings.
index.js keeps nothing of its own. It passes add and shout on, so app.js can import both from one place.Two rules to know about export * from:
- It skips the default export. It passes on named exports only.
- If two files export the same name, it leaves that name out, because JavaScript can't tell which one you mean. For example,
math.jsandstrings.jsboth exportname. Ifindex.jsusedexport * fromon both,namewould be left out. To pick one yourself, writeexport { name } from './file.js';.
The lab below shows both of these, plus one more mistake.
export * from "math";export { shout } from "strings";export * as strings from "strings"; import { add, shout, strings } from "index";console.log(add(2, 3), shout("ok"), strings.name);Loading the chosen code in the test page…
index.js passes on names from the two files. app.js imports them all from this one place.
Setup files and JSON files
MODULE PATTERNSSome imports don't bring in a value at all. import "./setup.js"; only runs the file. This is called a side-effect import. A side effect is anything a file does when it runs, like printing a message or changing the page. Use this import for files that set something up, such as:
- a theme stylesheet,
- a file that registers a custom HTML element,
- a file that adds one event listener.
This import gives you no names to use. If you also need a value from the file, add a normal named or default import.
// setup.jsconsole.log("setup runs");export const theme = "light"; // button.jsexport default "Button"; // index.jsexport { default } from "./button.js"; // app.jsimport "./setup.js";import { theme } from "./setup.js";import Button from "./index.js";console.log(theme, Button);Line 12 runs setup.js, so line 2 prints setup runs first. Line 13 imports theme by name from the same file. The file does not run again, because a module runs only once. Line 14 gets the default export of button.js through index.js. Line 15 prints light Button.
export * from skips the default export. To pass it on, write export { default } from './button.js';, as line 9 does. Then anyone reading index.js can see that the default export is passed on.
// settings.json{ "theme": "light", "pageSize": 3 } // app.mjsimport settings from "./settings.json" with { type: "json" };console.log(settings.theme, settings.pageSize);Line 5 imports a JSON file. The part with { type: 'json' } is called an import attribute. It tells JavaScript to read the file as JSON data, not as code. Line 6 prints light 3 in Node 22.
Browsers added JSON imports at different times, so check that the browsers you need support them. Older code may write assert instead of with. That spelling is out of date.
Imports look into the file
STEP THROUGHAn import is not a copy of a value. It is a name that looks into the exporting file. When that file changes the value, the import shows the new value. This is called a live binding. A binding is a name joined to a value, like a variable. You can read a live binding, but you can't assign to it.
A photo shows one moment and never changes. A window shows what is happening right now. An import works like a window.
- In real life: Looking through a window into a room
- In JavaScript: Reading an imported name
- In real life: Someone in the room moves a chair
- In JavaScript: The exporting file changes
count - In real life: You see the chair in its new spot
- In JavaScript: The import shows the new value
- In real life: You can't move the chair through the glass
- In JavaScript: The importing file can't assign to the import
In the step-through below, counter.js exports count and a function, increment(), that adds 1 to it. Then app.js imports both.
app.js reads count, calls increment(), reads count again, then tries to change it. Step through each line.
script
// counter.jsexport function increment() { count += 1;} // app.jsimport { count, increment } from "counter";console.log(count);increment();console.log(count);count = 10;Now choose "Copy first" above and step through again. This time, line 9 copies count into a normal variable, snapshot, before increment() runs.
increment() runs, the import count looks into counter.js and sees the new 1. snapshot is a copy made before the change, so it still holds 0.counter.js made count with export let, so counter.js may change it. In app.js, the name count is read-only. To change the value from app.js, call a function that counter.js exports for that job, like increment().
Two files can import each other. This is called a circular import. It can work, because imports look into the other file. But one file may read a name before the other file has set it. Then you get the same error as reading a let or const before its line runs. That waiting time is called the temporal dead zone. The Hoisting & TDZ lesson explains it.
// a.jsimport { b } from "./b.js";export const a = "A"; // b.jsimport { a } from "./a.js";console.log(a); // may throw if a is still uninitializedexport const b = "B";Say a.js is loaded first. Its import on line 2 makes b.js run before the rest of a.js. Line 7 in b.js then reads a, but line 3 has not run yet. So the read throws a ReferenceError.
Modules are private and strict
INTERACTIVEA module is not just an old-style script with new words. Modules follow different rules:
- They always use strict mode. So assigning to a name you never declared throws an error.
- Variables at the top of a module stay private to that module.
- At the top of a module,
thisisundefined. - Each module file runs only once, however many times it is imported.
The lab below checks all four rules in a real browser module.
// visits.jsconsole.log(this === undefined);let visits = 0;visits += 1;try { leaked = "global"; }catch (error) { console.log(error.name); }export const visitsSeen = visits;export const globalHasVisits = "visits" in globalThis; // app.jsimport { visitsSeen, globalHasVisits } from "visits";import { visitsSeen as again } from "visits";console.log(visitsSeen, again, globalHasVisits);Loading the chosen code in the test page…
Top-level this is undefined. Assigning to a name that was never declared throws. visits is not on globalThis. And importing the same file twice shares its one run.
The lab prints three lines:
- Line 2 prints
true, becausethisisundefined. - Line 5 assigns to
leaked, which was never declared. It throws, and line 6 printsReferenceError. - Line 13 prints
1 1 false.app.jsimportsvisits.jstwice, but the file ran once, so both names show 1. Andvisitsis not onglobalThis.
These rules fix many problems from the var and the global object lesson. A let visits at the top of a module stays private. It never becomes globalThis.visits.
Where you will use this
SORTERReal projects use all of these forms. Here is a simple way to choose:
- Named exports for a file with several tools, like price helpers.
- A default export for a file with one main value, like a
Buttoncomponent. - Re-exports in an
index.jsthat gives a folder one place to import from.
Whichever style you pick, use it the same way across the whole project. Then everyone can read it quickly.
Each row of the table below is one form. Read across to see how you import it and when it fits.
| Export form | How you import it | When to use it |
|---|---|---|
| Named export | import { price } from './shop.js' | The file shares several values, each with a clear name. |
| Default export | import anything from './shop.js' | The file shares one main value. |
| Import everything | import * as shop from './shop.js' | You want all of a file's exports on one read-only object. |
| Re-export | export { price } from './shop.js' | You want to pass on another file's export. |
- A helper file has three tools:
parsePrice,formatPrice, andtaxRate. - A file holds one main thing: a
Buttoncomponent. features/cart/index.jspasses on the cart tools that other files may use.- A checks file shares
isEmailandisStrongPassword. - A theme file exports the one theme object the app uses.
ui/index.jscollectsButton,Modal, andToastfrom their own files.
Sort each file into the export form that fits it best.
Common misconceptions
Each row of this table is a common mistake, next to what is really true.
| The mistake | What is really true |
|---|---|
| A default export has no name. | Its name is default. The importing file picks its own name for it. |
| An import is a copy. | An import looks into the exporting file, and you can't assign to it. |
| A re-exporting file owns the values. | It only passes them on. It gets no variables of its own. |
| A module is just an old script. | A module is strict and private. In a page, it waits until the HTML is read, and it runs only once. |
- "A default export has no name." Its export name is
default. Only the name in the importing file is up to you. - "Imports are hoisted, so I can write them anywhere." Imports do run before the rest of the file. That is called hoisting. But you must still write them at the top level of the module, not inside a function or an
if. - "
import * asgives a normal object." It gives a read-only object with no prototype. You can't add or change its properties. - "A barrel file always helps." It gives one place to import from. But many barrel files make it hard to find where code really lives.
- "Modules only matter in browsers." Browsers, Node, and many other JavaScript tools use modules. Each one decides how to find a file from the text after
from.
Practice exercises
4 EXERCISESThis object stands in for a file's exports. Predict exactly what the code prints.
const module = { price: 5, default: "house special" };
const { price } = module;
const special = module.default;
console.log(price + " / " + special);price is 5 and special is house special. The last line joins them with / , so it prints 5 / house special.
The cart file wants a variable named price, but prices.js only exports cost. Type the fixed import line.
// prices.js
export const cost = 12;
// cart.js
import { price } from "./prices.js"; // fix this lineimport { cost as price } from "./prices.js";Ask for the real export name, cost. Then use as to call it price in this file.
This code acts like an import that looks into its file. Write the two printed lines, separated by a comma.
let count = 0;
function increment() { count += 1; }
console.log(count);
increment();
console.log(count);It prints 0, then 1. An import works the same way: reading count again after the change shows the new value.
In a module, code runs leaked = "global", but leaked was never declared. Which error name appears?
It throws a ReferenceError. Modules always use strict mode, and strict mode never makes accidental global variables.
Check your understanding
8 QUESTIONSQuestion 1 of 8What is a default export?
Choose an answer to see the explanation.
Question 2 of 8This object stands in for a file's exports. What does the code print?
Read the code, then predictconst module = { price: 5, default: "house special" }; const { price } = module; const special = module.default; console.log(price + " / " + special);Choose an answer to see the explanation.
Question 3 of 8Why write
import { cost as price }?Choose an answer to see the explanation.
Question 4 of 8This code acts like an import that looks into its file. What does it print?
Read the code, then predictlet count = 0; function increment() { count += 1; } console.log(count); increment(); console.log(count);Choose an answer to see the explanation.
Question 5 of 8A file imports
countand then runscount = 10. What happens?Choose an answer to see the explanation.
Question 6 of 8A file uses
export * fromon two files. Both export aname. What happens toname?Choose an answer to see the explanation.
Question 7 of 8Which line passes on another file's default export?
Choose an answer to see the explanation.
Question 8 of 8Which sentence about modules is true?
Choose an answer to see the explanation.
Key takeaways
- Import a named export by its exact name, inside curly braces. A default export is the export named
default; you pick its name when you import it. asgives an import or an export a new name. The other file doesn't change.- A re-export passes on another file's exports.
export * fromskips the default export and leaves out names that two files share. - An import looks into the exporting file. It shows changes, and you can't assign to it.
- Modules use strict mode, keep their variables private, and run only once.
Final definition.import and export connect module files. Each import looks at a value that another file shares, and each file keeps everything else private.
Up next: Dynamic import & top-level await.