What you'll be able to do

By the end of this page you should be able to export and import functions with named and default exports, read a simple multi-file setup, and use import() when you only need a module after a click or route change.

Previous post: event loop and task queues.

Who this is for

  • People who put every helper in one giant script.js and want a cleaner split
  • Beginners who saw export / import in a tutorial and mixed named with default
  • Anyone who heard "lazy load" and wondered what import() actually does

You can skip this if you already ship multi-file ES modules confidently. This opens Phase 10 — useful deeper JS.

Why modules exist

A module is a file that can export values for other files to import. That keeps math helpers out of your UI file, and lets the browser (or bundler) know what depends on what.

Scenario A — small page. math.js exports add; main.js imports it and wires a button.

Scenario B — growing app. Without modules you paste the same formatDate into three scripts and fix bugs three times.

The rule: one idea per file when it helps; share through exports, not copy-paste.

Named exports (most everyday code)

// math.js
export function add(a, b) {
  return a + b;
}
export const PI = 3.14;
// main.js
import { add, PI } from "./math.js";
console.log(add(2, PI));

Scenario A — pick what you need. Import only add if you do not need PI.

Scenario B — rename on import. import { add as sum } from "./math.js" when names collide.

Names must match (or be aliased). Curly braces mean "named."

Default export (one main thing)

// greeter.js
export default function greet(name) {
  return "Hi " + name;
}
import greet from "./greeter.js";

Scenario A — one primary function or class per file feels natural as default.

Scenario B — mixing both. A file can have one default plus named exports; beginners often overuse default and then struggle to tree-shake or rename consistently. Prefer named for most helpers; use default when the file really has one star export.

Dynamic import() — load later

button.addEventListener("click", async function () {
  const mod = await import("./heavy.js");
  mod.run();
});

Scenario A — rare feature. Chart code loads only when the user opens Analytics.

Scenario B — static import at top. If every page needs the module, a normal top-level import is simpler.

import() returns a Promise — that is why you await it (you already know promises from earlier posts).

Browser gotcha: type="module"

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

Scenario A — classic script without type. import syntax throws.

Scenario B — local files. Some browsers restrict modules from file://; use a tiny local server when practicing.

A tiny practice file

<!DOCTYPE html>
<html>
  <body>
    <p id="out"></p>
    <script type="module">
      // Inline module for a one-file demo; normally use separate .js files.
      function add(a, b) {
        return a + b;
      }
      document.getElementById("out").textContent = "2+3=" + add(2, 3);
    </script>
  </body>
</html>

Then split add into math.js with export and import it from a main.js on a local server.

Mistakes I see a lot

1. Forgetting file extensions in browser imports. ./math.js often required in native ES modules.

2. Mixing require and import in the same beginner mental model. Node has its own story; in the browser path, stick to ES modules first.

3. Default-importing a named export (or the reverse). Read the export style in the source file once before guessing the import.

4. Dynamic-importing everything "for performance" when the module is tiny and always needed. Extra async complexity for no win.

What to try before the next post

  1. Create math.js + main.js with a named export and type="module".
  2. Convert one export to default and fix the import.
  3. Load a second module only after a button click with import().

Next in this series: dates you will actually use — formatting and comparing dates without a calendar rabbit hole.

Try this next outside the series

Deeper JavaScript pays off when you connect it to routes, imports, and performance-sensitive app code.