Site icon Ampersand Tutorials

JavaScript Modules: import and export (2026)

Quick answer: A JavaScript module is a file with its own scope that exposes a public surface with export and consumes other modules with import. Use named exports for most things, a default export for a file’s single main value, and load a module in the browser with <script type="module">. Modules are deferred and strict by default and load only once, however many times they are imported.The sections below follow JavaScript modules in the order you meet them: exporting, importing, JavaScript modules in the browser, dynamic import, and a file layout that keeps a growing project readable.

Exporting from JavaScript modules

// math.js
export const PI = 3.14159;

export function area(r) {
  return PI * r * r;
}

export class Circle {
  constructor(radius) { this.radius = radius; }
}

// one default export per module, if you want one
export default function log(message) {
  console.log(message);
}

A module’s top-level variables are private unless exported. That boundary is the main reason modules exist: it stops two files from accidentally sharing a name in the global scope.

Importing

// named imports — must match the exported names
import { area, PI } from "./math.js";

// renamed on import
import { area as circleArea } from "./math.js";

// default import — any name you like
import log from "./math.js";

// namespace import — everything under one object
import * as math from "./math.js";
math.area(2);

Prefer named exports. They are easier to search for, they survive renaming tools better, and they let an editor auto-import without guessing a default name. Reserve export default for a file whose whole purpose is one thing — a component, a config object.

In the browser

<script type="module" src="./app.js"></script>

The type="module" attribute changes three things. The script is deferred, so it runs after the HTML is parsed. It runs in strict mode. And import and export are legal only inside it. Loading a module from the file system has CORS restrictions, so serve your files over HTTP during development rather than opening them with a file:// URL.

Dynamic import

A static import is hoisted and always loaded. When you want a module only in some cases — a heavy chart library when a tab opens, for instance — use a dynamic import(), which returns a promise.

button.addEventListener("click", async () => {
  const { renderChart } = await import("./chart.js");
  renderChart(data);
});

The bundler splits the dynamically imported file into its own chunk and fetches it on demand. That is the same mechanism behind lazy routes in frameworks such as Angular.

Top-level await

Inside a module you can use await at the top level, which makes asynchronous setup read plainly.

// config.js
const res = await fetch("/api/config");
export const config = await res.json();

Anything that imports this module waits until the fetch resolves, so consumers never see a half-initialised module. Use it sparingly: a slow top-level await delays every importer, so keep it to genuinely required setup.

A JavaScript module layout that scales

That completes the JavaScript series. To learn JavaScript properly with a mentor on real projects, Ampersand Academy teaches web development one-to-one.

Frequently asked questions

What is the difference between a named export and a default export?

A named export imports by its exact name and a file can have many. A default export is a single value imported under any name you choose. Named exports are easier to search and refactor.

Why does my module throw a CORS error?

You are probably opening the page with a file URL. Module loading needs HTTP, so serve the files with a local server such as the one built into your editor or a dev tool.

What does type=module do in a script tag?

It enables import and export, makes the script deferred so it runs after the HTML is parsed, and runs the code in strict mode.

How do I load a module only when it is needed?

Use a dynamic import, which returns a promise, such as const module = await import of the file path. Bundlers split it into a separate chunk loaded on demand.

What is top-level await?

It lets you use await at the top of a module rather than inside an async function. Importers wait until the awaited promise settles, so they never see an incomplete module.

Exit mobile version