Chapter 11: Chapter 11. Code modularization techniques
The introduction explains why growing JavaScript applications benefit from modules and why developers used modularization techniques before ES6 introduced native modules.
After this chapter you will be able to
- Purpose of modules. Explain why breaking a growing application into smaller, loosely coupled modules makes its code easier to understand, maintain, and reuse.
- Global name clashes. Explain how mainline JavaScript variables can create naming clashes as applications grow or incorporate third-party code.
- Pre-ES6 scope limitation. Explain why writing modular code in pre-ES6 JavaScript requires creative use of existing language features.
- Minimum module system capabilities. Describe the two minimum capabilities of a module system and why hiding internals matters.
- Private state in function scope. Explain how a function used as a module hides local variables from outside code while a closure can keep them alive after the function returns, as in the click counter.
- Returned public module interface. Describe how the module pattern returns an object whose methods expose selected functionality while closures retain access to private variables and functions.
- Augmenting an existing module. Trace how an immediate function adds scroll-counting functionality to MouseCounterModule without modifying its original code.
- Private state across extensions. Explain why a module extension made in a separate immediate function cannot access private variables defined by the original module.
- AMD module definition and loading. Trace how AMD’s define function specifies a module and its dependencies, and how those dependencies are loaded before the module is initialized.
- CommonJS files and exports. Explain how a CommonJS file becomes a module, which of its contents are available to other files, and how another file accesses them.
- Explicit module visibility. Explain that only identifiers explicitly exported from an ES6 module are accessible outside it, while other identifiers—including top-level ones—remain accessible only within the module. Describe the roles of export and import.
- Module scope and exports. Explain why a top-level variable in an ES6 module is not automatically global or accessible from another module, and how exporting changes that.
- Named exports and imports. Describe how a module exposes multiple named identifiers and how another module gains access to them.
- Default export distinction. Distinguish a default export from named exports, including how each is imported and whether they can coexist.
What it builds on
- Do you know? (chapter 5) · The idea that closures retain access to variables from an enclosing function.
Section by section
Introduction
The introduction explains why growing JavaScript applications benefit from modules and why developers used modularization techniques before ES6 introduced native modules.
- Purpose of modules: Explain why breaking a growing application into smaller, loosely coupled modules makes its code easier to understand, maintain, and reuse.
Why it matters: This is the reason for organizing code into modules rather than leaving a growing application as one monolithic code base. - Global name clashes: Explain how mainline JavaScript variables can create naming clashes as applications grow or incorporate third-party code.
Why it matters: Avoiding collisions between names is another reason to organize code into modules. - Native module gap: Distinguish the pre-ES6 lack of a built-in module-grouping feature from ES6’s introduction of native modules, without describing how either approach works.
Why it matters: It explains why the chapter discusses both techniques built from older JavaScript constructs and ES6 modules.
Key terms: modules, loosely coupled, consistent abstractions, encapsulate implementation details, global variables, mainline code, naming clashes, third-party code, pre-ES6, objects
11.1 Modularizing code in pre-ES6 JavaScript
The section explains why pre-ES6 JavaScript needs modularization techniques and the minimum capabilities a module system must provide.
- Pre-ES6 scope limitation: Explain why writing modular code in pre-ES6 JavaScript requires creative use of existing language features.
Why it matters: This limitation explains why pre-ES6 modularization relies on other JavaScript features. - Minimum module system capabilities: Describe the two minimum capabilities of a module system and why hiding internals matters.
Why it matters: These capabilities give you a way to judge whether a modularization technique provides useful access without exposing its implementation.
Key terms: global scope, function scope, namespace, module, interface, module internals
11.1 Do you know?
This section poses three self-check questions about pre-ES6 modules, AMD and CommonJS, and ES6 module statements without explaining the answers.
11.1.1 Using objects, closures, and immediate functions to specify modules
This section explains how the pre-ES6 module pattern uses immediate functions, objects, and closures to hide implementation details and expose an interface, then shows how to extend modules and where the pattern falls short.
- Private state in function scope: Explain how a function used as a module hides local variables from outside code while a closure can keep them alive after the function returns, as in the click counter.
Why it matters: Private state is the basis for hiding a module’s implementation. - Returned public module interface: Describe how the module pattern returns an object whose methods expose selected functionality while closures retain access to private variables and functions.
Why it matters: The returned object gives other code a way to use a module without exposing its implementation. - Augmenting an existing module: Trace how an immediate function adds scroll-counting functionality to MouseCounterModule without modifying its original code.
Why it matters: Augmentation lets code add module functionality without changing the original module’s source. - Private state across extensions: Explain why a module extension made in a separate immediate function cannot access private variables defined by the original module.
Why it matters: This explains a concrete limit of extending modules through separate function calls. - Unmanaged module dependencies: Explain why the module pattern leaves developers responsible for dependency order and why this can become troublesome in large applications.
Why it matters: Dependency order becomes harder to manage as an application accumulates interdependent modules.
Key terms: scope, closure, numClicks, global namespace, module pattern, immediate function, public interface, closures, MouseCounterModule, augmenting modules
11.1.2 Modularizing JavaScript applications with AMD and CommonJS
The section explains how AMD and CommonJS define modules, expose interfaces, and load dependencies, then compares their suitability for browsers and servers.
- AMD module definition and loading: Trace how AMD’s define function specifies a module and its dependencies, and how those dependencies are loaded before the module is initialized.
Why it matters: This explains why an AMD module can declare what it needs without relying on a particular script-inclusion order. - CommonJS files and exports: Explain how a CommonJS file becomes a module, which of its contents are available to other files, and how another file accesses them.
Why it matters: The file boundary and exports object determine what stays private and what other code can use. - Browser and server loading tradeoffs: Distinguish why AMD suits browser loading and why CommonJS is more popular on servers, including the obstacle to using CommonJS directly in a browser.
Why it matters: A module format’s loading behavior affects where it is practical to use. - Competing standards and interoperability: Explain the reuse problem caused by competing AMD and CommonJS standards and state the limited solution described here.
Why it matters: It connects the two formats’ incompatibility to the motivation for a shared module standard.
Key terms: AMD, define, module ID, dependencies, factory function, public interface, CommonJS, file-based modules, module scope, module.exports
11.2 ES6 modules
This section introduces ES6 modules’ influences, their explicit boundary between internal and accessible identifiers, and the tooling needed to use them in the browser context described.
- Explicit module visibility: Explain that only identifiers explicitly exported from an ES6 module are accessible outside it, while other identifiers—including top-level ones—remain accessible only within the module. Describe the roles of export and import.
Why it matters: This boundary determines which parts of a module other code can use. - Combined module advantages: Describe the features ES6 modules take from CommonJS and AMD as presented here.
Why it matters: The comparison explains what this module standard draws from the earlier approaches. - Module use tooling: State why the section says module code needs tooling for browser use and name the two approaches it gives.
Why it matters: It connects the standard’s syntax to the practical browser-use options given in the section.
Key terms: ES6 modules, identifiers, top-level scope, export, import, CommonJS, AMD, file based, asynchronous module loading, transpile
11.2.1 Exporting and importing functionality
This section explains how ES6 modules keep unexported code private and how to export, import, and rename module functionality.
- Module scope and exports: Explain why a top-level variable in an ES6 module is not automatically global or accessible from another module, and how exporting changes that.
Why it matters: Module scope lets you choose what other code can access without exposing every top-level variable. - Named exports and imports: Describe how a module exposes multiple named identifiers and how another module gains access to them.
Why it matters: Named exports define a module’s public functionality, while imports make that functionality available elsewhere. - All named exports import: Describe how import * brings a module’s named exports under one identifier and how those exports are then accessed.
Why it matters: This shorthand avoids listing many named imports while preserving the module’s export boundary. - Default export distinction: Distinguish a default export from named exports, including how each is imported and whether they can coexist.
Why it matters: The distinction explains why a module’s main export and its additional named exports use different import forms. - Export and import aliases: Explain how as renames an exported or imported identifier and which name is usable on each side of the module boundary.
Why it matters: Aliases let modules expose useful public names and let importers avoid local naming clashes.
Key terms: module variable, top-level, export, named export, export keyword, import keyword, import *, named exports, property notation, default export
11.3 Summary
11.4 Exercises
Concepts explained in this chapter
- module patternA design pattern that encapsulates private implementation details behind a public interface, typically by returning an object from a closure or immediate function.
- moduleA bounded unit of code used to organize and reuse program functionality.
- CommonJSA JavaScript module system that defines reusable modules with synchronous loading, commonly used in Node.js.
- AMDA JavaScript module format designed to support asynchronous module loading and dependency resolution, especially in browsers.
- immediately invoked function expressionA function expression that is defined and invoked immediately in the same syntactic construct.
- closureA function together with the lexical environment in which it was created, allowing it to access variables from that environment after the outer function has finished executing.
- import statementJavaScript module syntax for bringing exported bindings from another module into the current module's scope.
- apiThe public set of functions and properties through which a module exposes its capabilities.
- private variableA variable intended to be inaccessible from outside an abstraction boundary, such as an object or function scope.
- default exportThe binding or value designated as a module's default export, which can be imported under a chosen local name.
- exportA JavaScript module keyword that makes bindings available for import by other modules.
- scopeThe region in which an identifier's binding is visible and can be resolved.
- named exportAn exported module binding identified by its declared or explicitly assigned name, allowing a module to expose multiple bindings.
- exportsA CommonJS variable that initially references a module's export object and can be used to define exported members.
- module.exportsThe CommonJS property whose assigned value is exposed as a module's public API.
- requireA CommonJS function that loads a module and returns its exported interface.
- dependencyA module or component that another module requires in order to function correctly.
- function scopeA scope in which a variable is visible throughout the entire function body rather than being limited to a block.
These guides are generated from the text of Manning books by Manning's knowledge graph and link to the sections they draw on. The full books are available on liveBook and at manning.com.
