JavaScript Modules

Split code into files with export and import.

Lesson 55 of 56Advanced13 min read

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:

JavaScript
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:

JavaScript
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 .js extension.
  • Imports must sit at the top level of a module, not inside functions or if statements.

Using modules in HTML

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.
  • export shares values; a file can have many named exports and one default export.
  • import brings 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.

Example 1

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>
ConsoleEdit the code, then press Run or Ctrl+Enter
Line-by-line explanation
  1. Lines 3 to 7The module's source: two named exports and one default export.
  2. Line 9Dynamic import() returns the module. The default export lives under the key default.
  3. Line 9Top-level await works because this is a module script.
Example 2

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>
ConsoleEdit the code, then press Run or Ctrl+Enter
Line-by-line explanation
  1. Line 2A static named import. With real files this line would be import { slugify, shout } from "./strings.js";.
  2. Lines 4 to 5Use the imported functions like any other.
Example 3

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>
ConsoleEdit the code, then press Run or Ctrl+Enter
Line-by-line explanation
  1. Line 2A top-level variable in a module is private to that module.
  2. 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"

Don't
<script src="app.js"></script>
<!-- SyntaxError: Cannot use import statement outside a module -->
Do
<script type="module" src="app.js"></script>

Missing ./ or .js in browser imports

Don't
import { add } from "utils";
Do
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

Don't
// utils.js: export function format() {}
import format from "./utils.js"; // undefined, because there's no default export
Do
import { 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.

Fill in the blankseasy

Export a function and import it in another file.

// greet.js
 function hello(name) { return `Hello ${name}`; }

// main.js
 { hello }  "./greet.js";
Multiple choicemedium

How many default exports can a module have?

Multiple choicemedium

Which script tag lets you use import?

Fix the bugmedium

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>
ConsoleEdit the code, then press Run or Ctrl+Enter
Checks
  • Logs "Double: 42"
  • Uses a named import

JavaScript Modules Quiz

3 questions to check what you learned.

  1. What keyword shares code from a module?

    • share
    • public
    • module.out
    • export
  2. Are top-level variables in a module global?

    • No. They're private to the module unless exported
    • Yes
    • Only const ones
    • Only in Chrome
  3. 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:

  1. Imports the default export as format and the named export TAX
  2. Logs Price with tax: £12.00 by calling format(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>
ConsoleEdit the code, then press Run or Ctrl+Enter
Checks
  • Logs "Price with tax: £12.00"
  • Uses import