EduFlow
JavaScript Fundamentals
0%

Section 5 · Writing Real Programs

Modules: import and export

15 min3 min readSpot the bug

Lesson at a glance

Split a growing program into modules with export and import, load them in the browser with type=“module” from a dev server, and organise Pocket into files with clear jobs.

  1. 1A module is a file with its own scope
  2. 2Loading modules in the browser
  3. 3Modules in Node.js
  4. 4Organising Pocket

No video here — just the real thing: a written lesson, runnable examples, a quiz and practice.

Pocket now has money helpers, validation, storage, rendering, event handlers and network code. In one file that is several hundred lines, every function can see every variable, and changing one part means scrolling past all the others. Real programs are split into files, each with one job, and JavaScript’s way of doing that is modules.

A module is a file with its own scope

In a module, top-level variables and functions are private to that file. To share something, you export it. To use something from another module, you import it:

src/money.js
export function toCents(dollars) {
  return Math.round(dollars * 100);
}

export function formatMoney(cents, symbol = "$") {
  return `${symbol}${(cents / 100).toFixed(2)}`;
}

const SECRET_ROUNDING_NOTE = "not exported, so only this file can see it";
src/main.js
import { toCents, formatMoney } from "./money.js";

console.log(formatMoney(toCents(12.2)));

The curly braces list the named exports you want. The path starts with ./, meaning “relative to this file”, and in the browser it includes the .js extension. SECRET_ROUNDING_NOTE cannot be imported at all; that is the point. Each module decides what it offers, and everything else is hidden, much like the closures from section 2, at file level.

A few more forms you will see:

src/main.js
import { formatMoney as fmt } from "./money.js";   // rename on import
import * as money from "./money.js";                // everything, as money.toCents etc.

There is also a default export, one per file, imported without braces and under any name you like: export default function renderApp() {} and then import renderApp from "./ui.js". Many teams prefer named exports, and so does this course: a named export has the same name in every file that uses it, so search and automatic renaming in your editor work reliably.

Loading modules in the browser

The entry file is loaded with type="module", and it imports everything else:

index.html
<script type="module" src="./src/main.js"></script>

Module scripts behave a little differently from the classic scripts you have used so far. They are deferred automatically, so the DOM is ready when they run. They run in strict mode, which turns some silent mistakes into errors. And each module runs once, however many files import it, so a module-level variable is shared by all its importers.

In practice, most projects use a build tool such as Vite. It serves your modules during development, lets you import packages from npm by name (import { something } from "some-package"), and at build time bundles everything into a few optimised files. The import and export syntax you write is exactly the same.

Modules in Node.js

Node.js supports the same syntax. A file is treated as an ES module when the nearest package.json contains "type": "module", or when the file name ends in .mjs. (Recent Node.js versions also detect import syntax in a plain .js file, but setting "type" explicitly is clearer.) You will also meet the older Node.js system, CommonJS, which uses require() and module.exports; it is still common in existing projects, but new code uses import and export.

Organising Pocket

A good split follows jobs, and keeps the parts that touch the outside world separate from the pure logic:

main.js imports from ui.js, storage.js and expenses.js. ui.js imports from money.js. expenses.js and money.js are pure logic that never touch the page or storage. main.js ui.js storage.js expenses.js money.js green: pure logic, no DOM, no storage
Pocket’s modules and what imports what
  • money.js: toCents, formatMoney. Pure.
  • expenses.js: addExpense, removeExpense, totalCents, totalsByCategory, validateExpense. Pure: data in, data out.
  • storage.js: loadExpenses, saveExpenses. The only file that knows about localStorage.
  • ui.js: renderExpenses and other DOM code.
  • main.js: the entry point. Loads data, connects event listeners, and calls the others.

The rule behind the split: dependencies point from the outside in. main.js knows about everything; expenses.js knows about nothing but data. That means you can test, or reuse in Node.js, the core logic without a browser, and you can swap localStorage for a server later by changing one file.

This course’s playground runs one file at a time, so the practice below is about reading module code. In the next lesson you will meet another way to organise code, classes, which group data with the functions that act on it.

Key takeaways

  • A module is a file with its own scope; nothing in it is visible to other files unless it is exported.
  • Prefer named exports (export function formatMoney), which keep the same name everywhere and are easy to search for.
  • In the browser, load the entry file with <script type="module">, use relative paths with the .js extension, and serve the files over http.
  • In Node.js, files are ES modules when the nearest package.json has "type": "module" or the file ends in .mjs.
  • [object Object]

Further reading

Check your understanding

3 questions

Question 1 of 3

The browser console says Uncaught SyntaxError: Cannot use import statement outside a module. What is the fix?

Question 2 of 3

money.js declares function toCents(d) { … } without export. What happens when main.js runs import { toCents } from "./money.js";?

Question 3 of 3

You open index.html by double-clicking it, and the module script fails to load with a CORS error. Why?

Practice

Spot the bug

Why won't Pocket's modules load?

Pocket was split into two modules, and now the page shows nothing and the console reports errors. The HTML loads src/main.js correctly with type="module" from a plain static file server (no build tool such as Vite). Select the two lines that stop these modules from working in the browser.

Select the line(s) you think are wrong, then check.