As projects grow, a single huge app.js file becomes hard to find your way around. Modules let you split code into small files that each do one job, and share pieces between them with export and import. Every modern JavaScript project, and every framework, is built this way.
Modules also protect you from a classic problem: two scripts that both create a variable with the same name and overwrite each other. Each module has its own private scope, and only the things you explicitly export are visible to other files.
Exporting from a module
In math.js:
export const PI = 3.14159;
export function area(r) { return PI * r * r; }
export default function greet(name) { return `Hi ${name}`; }- Named exports: as many as you like, imported using their exact names.
- Default export: at most one per file, imported with any name you choose.
Many teams prefer named exports for everything, because the exact names make it easier to search a project and avoid mismatched imports.
Importing into another file
In app.js:
import greet, { PI, area } from "./math.js";
import { area as circleArea } from "./math.js"; // rename
import * as math from "./math.js"; // everything as one object- Paths to your own files start with
./or../, and in the browser they must include the.jsextension. - Imports must sit at the top level of a module, not inside functions or
ifstatements.
Using modules in HTML
<script type="module" src="app.js"></script>Module scripts are automatically deferred, run in strict mode, and keep their top-level variables private to the file, so there are no accidental global variables. They also allow top-level await.
Modules must be served over http:// or https://, for example with VS Code's Live Server. Opening the HTML file directly from your computer with file:// blocks them, which is a very common source of confusion.
Loading a module on demand with import()
import("./chart.js") loads a module only when it's needed and returns a promise. It's useful for large features that many visitors never use, such as a charting library on a settings page, because the main page can load faster without it.
JavaScript Modules Syntax
import / export
// utils.js
export const add = (a, b) => a + b;
export default class Logger { }
// main.js
import Logger, { add } from "./utils.js";
const { chart } = await import("./chart.js");JavaScript Modules Summary
- Modules split code into separate files with their own private scope.
exportshares values; a file can have many named exports and one default export.importbrings exports into another file using a relative path with.js.- Load modules with
<script type="module">, served over http or https. import()loads a module only when it's needed.
JavaScript Modules Examples
Each example runs in the page. Edit the code and press Run to see what changes.
Named and default exports
The examples on this page can't create real files, so they import from tiny data: URL modules instead. The import and export syntax is exactly what you'd write with real files.
<script type="module">
// Pretend this URL is "./math.js"
const mathModule = "data:text/javascript," + encodeURIComponent(`
export const PI = 3.14159;
export function area(r) { return PI * r * r; }
export default function round2(n) { return Math.round(n * 100) / 100; }
`);
const { PI, area, default: round2 } = await import(mathModule);
console.log("PI is", PI);
console.log("Area of r=3:", round2(area(3)));
</script>Line-by-line explanation
- Lines 3 to 7The module's source: two named exports and one default export.
- Line 9Dynamic
import()returns the module. The default export lives under the keydefault. - Line 9Top-level
awaitworks because this is a module script.
Static import syntax
Real projects usually use static import statements at the top of the file.
<script type="module">
import { slugify, shout } from "data:text/javascript,export const slugify = s => s.trim().toLowerCase().replaceAll(' ', '-'); export const shout = s => s.toUpperCase() + '!';";
console.log(slugify(" My First Module "));
console.log(shout("modules are neat"));
</script>Line-by-line explanation
- Line 2A static named import. With real files this line would be
import { slugify, shout } from "./strings.js";. - Lines 4 to 5Use the imported functions like any other.
Modules keep variables private
<script type="module">
const secret = "only inside this module";
console.log("Module sees:", secret);
</script>
<script>
console.log("Global sees:", typeof secret);
</script>Line-by-line explanation
- Line 2A top-level variable in a module is private to that module.
- Line 6A normal script can't see it, so there's no global pollution. (Because modules are deferred, the module runs later, so this line actually logs first.)
Common JavaScript Modules Mistakes
Forgetting type="module"
<script src="app.js"></script>
<!-- SyntaxError: Cannot use import statement outside a module --><script type="module" src="app.js"></script>Missing ./ or .js in browser imports
import { add } from "utils";import { add } from "./utils.js";Without a build tool, browsers need a real relative path with the extension. (Bare names like "react" work in projects that use bundlers or import maps.)
Mixing up default and named imports
// utils.js: export function format() {}
import format from "./utils.js"; // undefined, because there's no default exportimport { format } from "./utils.js";Curly braces for named exports; no braces for the default export.
JavaScript Modules in Real Projects
Modules everywhere
npm packages, React components (export default function App()), Node.js code, and build tools like Vite are all built on ES modules. When you learn a framework, the first lines of every file will be imports.
JavaScript Modules Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
Export a function and import it in another file.
// greet.js
function hello(name) { return `Hello ${name}`; }
// main.js
{ hello } "./greet.js";
export shares it; import { name } from "path" uses it.How many default exports can a module have?
Which script tag lets you use import?
The module should import double (a named export) and log Double: 42. Fix the import.
<script type="module">
import double from "data:text/javascript,export const double = n => n * 2;";
console.log("Double:", double(21));
</script>- Logs "Double: 42"
- Uses a named import
JavaScript Modules Quiz
3 questions to check what you learned.
What keyword shares code from a module?
- share
- public
- module.out
- export
Are top-level variables in a module global?
- No. They're private to the module unless exported
- Yes
- Only const ones
- Only in Chrome
What does
import("./big.js")return?- The module synchronously
- A Promise for the module
- undefined
- A string
JavaScript Modules Challenge
Use a module's exports
Complete the module script so that it:
- Imports the default export as
formatand the named exportTAX - Logs
Price with tax: £12.00by callingformat(10 * (1 + TAX))
<script type="module">
const priceModule = "data:text/javascript," + encodeURIComponent(`
export const TAX = 0.2;
export default function format(n) { return "£" + n.toFixed(2); }
`);
// import from priceModule here (use await import)
</script>- Logs "Price with tax: £12.00"
- Uses import