liveBook · Manning

Module

As explained in The Joy of JavaScript

A module system is a set of rules and mechanisms for organizing code into modules and controlling how they export and import bindings.

A module organizes code behind an explicit boundary: it exposes what other files need and keeps the rest out of their way. Splitting an application into modules reduces the amount of code you must reason about at once and lets different parts evolve separately. Functions and objects can provide similar boundaries within a file; a module system manages dependencies and exposure across files. section 6.0 section 6.2.4

JavaScript offers two broad ways to manage those boundaries:

  • Dynamic systems define dependencies and exposed values through running code. CommonJS uses require(...); object namespaces, IIFEs, and factory functions can also assemble modules programmatically. This permits conditional loading, but leaves more of the boundary management to application code. section 6.3 section 6.2.4
  • ECMAScript Modules (ESM) use native import and export syntax to declare contracts statically. Top-level imports give runtimes and tools a dependency map ahead of execution; use import(...) when loading must be conditional. section 6.3 section 6.4.3

Importing an ESM API

import { checkIndex, checkLinkage } from './block/validations.js'; The braces select named exports; they do not destructure a copied object. Imports remain connected to their exports as live bindings. Use import { foo as newFoo } to rename a binding, or import * as ValidationsUtil to collect exports under one namespace. section 6.4.3 section 6.3

Write ESM specifiers with the target environment in mind. Relative paths start with ./ or ../ and explicitly include a file extension; bare package specifiers are an exception. ESM files enter strict mode automatically. In Node.js, a package.json type of "module" or an .mjs extension identifies ESM, while .cjs identifies CommonJS. section 6.4.1 section 6.4 section 6.4.4

Static contracts also help tooling. A build tool can follow imports from an entry point and omit unused modules or exports from a bundle—tree-shaking that can reduce code sent to a browser. This is a reason to keep modules focused rather than pack unrelated code into one file; it is not a substitute for choosing and configuring a build tool. section 6.5.1 section 6.5

Figure 6.2 Bundler tools can use the static structure of your application to achieve tree-shaking by identifying any unused modules of code and removing them from the packaged application file.

The dependency-tree figure illustrates how a bundler identifies unused modules and removes them from the packaged application. section 6.5.1

Modules also provide a context for asynchronous loading. import(...) loads a module asynchronously, and top-level await lets an ESM module wait without wrapping its code in an async function. A module that imports it must wait for that dependency to finish executing, so use this facility when the wait is necessary. section 6.4.3 section 8.6

How The Joy of JavaScript covers it

How deep it goes
Works through dynamic patterns, ESM syntax, loading behavior, and tooling benefits, but defers loader configuration and build-tool specifics.
Examples
Contrasts CommonJS require with ESM import, then uses validation imports, conditional algorithm loading, and a bundler dependency tree.
What it assumes
Builds on functions, composition, and file-level organization while introducing ESM import and export syntax from its fundamentals.
What you can do after
Build it from scratch using ESM imports and exports, and evaluate static versus dynamic loading for an application's dependencies.
How it connects
Connects modules to scope, separation of concerns, live bindings, dynamic import, tree-shaking, and top-level await.

Also explained in

Compare how 35 books explain module →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of The Joy of JavaScript. The section links point to the parts of the book it draws on.