Phase 2 · Core JavaScriptModule 16~38 min read

Modules & Code Organization

Split applications into focused ES modules with clear public APIs and dependency boundaries.

What you'll learn

ES modules turn files into explicit dependency boundaries. Good module design exposes a small stable API and keeps implementation details local.

By the end of this lesson, you'll be able to:

  • Use named, default, and dynamic imports
  • Design a focused module API
  • Recognize circular dependency risks

Core mental model

Use this decision table as a compact reference. Focus on what each tool means and when it earns its place in production code.

ConceptWhat it meansDecision rule
Named exportA module exposes a specific live bindingPrefer for discoverable multi-value APIs
Default exportOne primary value is exportedUse sparingly when the module clearly has one main product
Dynamic importA module loads on demand and returns a promiseUse for optional or route-level code splitting

Professional workflow

Build the behavior in small, observable steps. Each step should leave something you can inspect or test.

  1. Describe the module boundary boundary: inputs, outputs, state, timing, and expected failures.
  2. Implement the smallest correct path with names that expose intent.
  3. Add edge cases and failure handling before introducing abstractions.
  4. Verify behavior with realistic data and one deliberately adversarial example.
  5. Refactor only after the observable behavior is protected.

Make behavior observable

Before optimizing or abstracting, make inputs, outputs, state changes, timing, and failure paths visible. JavaScript becomes much easier to reason about when hidden work is exposed.

Guided code lab

Expose a narrow domain API

Only supported operations are exported; tax configuration remains private.

pricing.js
const taxRate = 0.11;
export function subtotal(lines) {
  return lines.reduce((sum, line) => sum + line.price * line.quantity, 0);
}
export function total(lines) {
  const value = subtotal(lines);
  return value + value * taxRate;
}

Consume named exports

Static imports make dependencies visible before execution.

checkout.js
import { subtotal, total } from "./pricing.js";
const lines = [{ price: 10, quantity: 2 }];
console.log(subtotal(lines));
console.log(total(lines));

Production practice

Contract

Exports are the supported public API; unexported names may change freely.

Verification

Import the module from a consumer test and verify both behavior and failure cases.

Operations

Use dynamic imports at measured boundaries and monitor chunk-loading failures.

Common failure mode

Barrel files can hide circular dependencies and pull far more code into a bundle than the consumer needs.

Independent workshop

Split the Phase 1 grade tracker into data, calculation, formatting, and application modules.

Your finished workshop must include:

  • Named exports with no global variables
  • One dynamic import for an optional report
  • A dependency diagram with no cycle

Definition of done

Demonstrate the happy path and at least two edge cases, keep responsibilities separated, and add a short note explaining one design choice.

Recap & quick check

Key takeaways

  • Modules create scope and explicit dependencies
  • Named exports improve discoverability
  • Imports are live bindings
  • Dynamic import enables on-demand loading

Quick check

1. What does dynamic import return?

2. Are imported bindings copies?

3. What is private to an ES module by default?

Keep the workshop. Later modules deliberately build on these decisions, so each exercise can become part of your final portfolio architecture.