Academy

Modules

The Problem With One Big File

Imagine writing an entire application in a single file. Every function, every variable, every constant — all in the same global scope. Two problems arise immediately:

Name collisions. You write a function called format. Three libraries you used also have a function called format. They overwrite each other silently.

No privacy. Every internal helper is exposed to every other part of the code. Nothing is truly internal. Everything can be accidentally modified.

Modules solve both problems: each file has its own scope, and you explicitly choose what to expose.

What Modules Actually Are

Before seeing the syntax, understand the mechanism. A module is a file with its own private scope — variables defined inside are invisible outside unless deliberately exported.

This is exactly what a closure does. In fact, before native modules existed, developers used closures to simulate them:

The IIFE (Immediately Invoked Function Expression) runs once, creates a private scope, and returns only what it wants to expose. This is exactly what a native module does — the syntax is just cleaner.

Native Module Syntax

Native ES modules work across files in real projects. The syntax is straightforward — here is what it looks like. You cannot run multi-file code in this sandbox, but you will use this syntax every day in real projects, so read it carefully.

Exporting from a file (math-utils.js):

// Named exports — export specific things by name
export function square(n) {
  return n * n;
}
export function cube(n) {
  return n * n * n;
}
export const PI = 3.14159265358979;

// Or export at the bottom — same result
function average(...nums) {
  return nums.reduce((a, b) => a + b, 0) / nums.length;
}
export { average };

// Default export — one per file, imported without braces
export default function format(n, decimals = 2) {
  return n.toFixed(decimals);
}

Importing into another file (main.js):

// Named imports — must match the exported name exactly

// Default import — you choose the name

// Both at once

// Rename on import

// Import everything into a namespace object
Math.square(5);
Math.PI;

Named vs Default Exports

The distinction matters and trips people up. A file can have many named exports but only one default export:

In practice: use named exports when a file provides multiple utilities. Use a default export when a file has one primary thing to offer — a class, a React component, a configuration object.

Module Scope Is Truly Separate

Each module has its own scope. Same variable names in different modules never collide:

In real modules this isolation is absolute. Two files can both declare const API_URL = ... and they will never interfere.

Re-exporting — Building a Public API

Real projects often have many internal files but want to expose a clean single entry point. Re-exports let you do this:

index.js (the public API of a whole folder):

export { create, isPassing } from "./student.js";
export { validate } from "./validators.js";
export { default as format } from "./formatter.js";

// Consumers import from one place, not from ten files:
// import { create, validate, format } from "./student-module/index.js";

Dynamic Imports

Sometimes you don't want to load a module upfront — you want to load it on demand, only when needed. Dynamic import() returns a Promise:

// Static import — loaded immediately when the file is parsed

// Dynamic import — loaded only when this code runs
async function handleClick() {
  const { heavyFunction } = await import("./heavy-module.js");
  heavyFunction();
}

This is how large applications avoid loading everything on startup. Split your code into chunks, load each chunk only when the user actually needs it. Every modern bundler (Vite, webpack) understands dynamic imports and handles the splitting automatically.

How This Looks in Your Astro Project

You have already been using modules on this very site. Every .mdx lesson file starts with:

That is a module import. JSRunner.jsx is a module. It exports one thing — the JSRunner component — as its default export. The path ../../../../components/ is a relative path from the lesson file up to the components folder.

Every React component, every utility function, every Astro layout on your site is a module. You have been living inside this system the entire time.

What You Have Learned

Modules are how professional JavaScript organises itself — private by default, explicit about what it shares, immune to naming collisions.

The key ideas:

  • Every module has its own scope — variables are private unless exported
  • Named exports (export function foo) — a file can have many; imported with { foo }
  • Default export (export default) — one per file; imported without braces, any name you choose
  • Re-exports let you build a single clean entry point from many internal files
  • Dynamic import() returns a Promise — load code on demand, not upfront
  • The IIFE module pattern is the closure-based predecessor — understanding it means you understand what native modules do
  • You have been using modules since lesson 01 — import JSRunner from ... is a default import

In the next lesson: Iterators & Generators — the protocol that powers for...of, spread, and destructuring. You have used these tools in every lesson; now you will see exactly how they work and how to build your own iterable data structures.