# Chapter 10. Modules The goal of modular programming is to allow large programs to be assembled using modules of code from disparate authors and sources and for all of that code to run correctly even in the presence of code that the various module authors did not anticipate. As a practical matter, modularity is mostly about encapsulating or hiding private implementation details and keeping the global namespace tidy so that modules cannot accidentally modify the variables, functions, and classes defined by other modules. Until recently, JavaScript had no built-in support for modules, and programmers working on large code bases did their best to use the weak modularity available through classes, objects, and closures. Closure-based modularity, with support from code-bundling tools, led to a practical form of modularity based on a require() function, which was adopted by Node. require()-based modules are a fundamental part of the Node programming environment but were never adopted as an official part of the JavaScript language. Instead, ES6 defines modules using import and export keywords. Although import and export have been part of the language for years, they were only implemented by web browsers and Node relatively recently. And, as a practical matter, JavaScript modularity still depends on code-bundling tools. The sections that follow cover: - Do-it-yourself modules with classes, objects, and closures - Node modules using require() - ES6 modules using export, import, and import() ## 10.1 Modules with Classes, Objects, and Closures Though it may be obvious, it is worth pointing out that one of the important features of classes is that they act as modules for their methods. Think back to Example 9-8. That example defined a number of different classes, all of which had a method named has(). But you would have no problem writing a program that used multiple set classes from that example: there is no danger that the implementation of has() from SingletonSet will overwrite the has() method of BitSet, for example. The reason that the methods of one class are independent of the methods of other, unrelated classes is that the methods of each class are defined as properties of independent prototype objects. The reason that classes are modular is that objects are modular: defining a property in a JavaScript object is a lot like declaring a variable, but adding properties to objects does not affect the global namespace of a program, nor does it affect the properties of other objects. JavaScript defines quite a few mathematical functions and constants, but instead of defining them all globally, they are grouped as properties of a single global Math object. This same technique could have been used in Example 9-8. Instead of defining global classes with names like SingletonSet and BitSet, that example could have been written to define only a single global Sets object, with properties referencing the various classes. Users of this Sets library could then refer to the classes with names like Sets.Singleton and Sets.Bit. Using classes and objects for modularity is a common and useful technique in JavaScript programming, but it doesn’t go far enough. In particular, it doesn’t offer us any way to hide internal implementation details inside the module. Consider Example 9-8 again. If we were writing that example as a module, maybe we would have wanted to keep the various abstract classes internal to the module, only making the concrete subclasses available to users of the module. Similarly, in the BitSet class, the _valid() and _has() methods are internal utilities that should not really be exposed to users of the class. And BitSet.bits and BitSet.masks are implementation details that would be better off hidden. As we saw in §8.6, local variables and nested functions declared within a function are private to that function. This means that we can use immediately invoked function expressions to achieve a kind of modularity by leaving the implementation details and utility functions hidden within the enclosing function but making the public API of the module the return value of the function. In the case of the BitSet class, we might structure the module like this: ```js const BitSet = (function() { // Set BitSet to the return value of this function // Private implementation details here function isValid(set, n) { ... } function has(set, byte, bit) { ... } const BITS = new Uint8Array([1, 2, 4, 8, 16, 32, 64, 128]); const MASKS = new Uint8Array([~1, ~2, ~4, ~8, ~16, ~32, ~64, ~128]); // The public API of the module is just the BitSet class, which we define // and return here. The class can use the private functions and constants // defined above, but they will be hidden from users of the class return class BitSet extends AbstractWritableSet { // ... implementation omitted ... }; }()); ``` This approach to modularity becomes a little more interesting when the module has more than one item in it. The following code, for example, defines a mini statistics module that exports mean() and stddev() functions while leaving the implementation details hidden: ```js // This is how we could define a stats module const stats = (function() { // Utility functions private to the module const sum = (x, y) => x + y; const square = x => x * x; // A public function that will be exported function mean(data) { return data.reduce(sum)/data.length; } // A public function that we will export function stddev(data) { let m = mean(data); return Math.sqrt( data.map(x => x - m).map(square).reduce(sum)/(data.length-1) ); } // We export the public function as properties of an object return { mean, stddev }; }()); // And here is how we might use the module stats.mean([1, 3, 5, 7, 9]) // => 5 stats.stddev([1, 3, 5, 7, 9]) // => Math.sqrt(10) ``` ### 10.1.1 Automating Closure-Based Modularity Note that it is a fairly mechanical process to transform a file of JavaScript code into this kind of module by inserting some text at the beginning and end of the file. All that is needed is some convention for the file of JavaScript code to indicate which values are to be exported and which are not. Imagine a tool that takes a set of files, wraps the content of each of those files within an immediately invoked function expression, keeps track of the return value of each function, and concatenates everything into one big file. The result might look something like this: ```js const modules = {}; function require(moduleName) { return modules[moduleName]; } modules["sets.js"] = (function() { const exports = {}; // The contents of the sets.js file go here: exports.BitSet = class BitSet { ... }; return exports; }()); modules["stats.js"] = (function() { const exports = {}; // The contents of the stats.js file go here: const sum = (x, y) => x + y; const square = x = > x * x; exports.mean = function(data) { ... }; exports.stddev = function(data) { ... }; return exports; }()); ``` With modules bundled up into a single file like the one shown in the preceding example, you can imagine writing code like the following to make use of those modules: ```js // Get references to the modules (or the module content) that we need const stats = require("stats.js"); const BitSet = require("sets.js").BitSet; // Now write code using those modules let s = new BitSet(100); s.insert(10); s.insert(20); s.insert(30); let average = stats.mean([...s]); // average is 20 ``` This code is a rough sketch of how code-bundling tools (such as webpack and Parcel) for web browsers work, and it’s also a simple introduction to the require() function like the one used in Node programs. ## 10.2 Modules in Node In Node programming, it is normal to split programs into as many files as seems natural. These files of JavaScript code are assumed to all live on a fast filesystem. Unlike web browsers, which have to read files of JavaScript over a relatively slow network connection, there is no need or benefit to bundling a Node program into a single JavaScript file. In Node, each file is an independent module with a private namespace. Constants, variables, functions, and classes defined in one file are private to that file unless the file exports them. And values exported by one module are only visible in another module if that module explicitly imports them. Node modules import other modules with the require() function and export their public API by setting properties of the Exports object or by replacing the module.exportsobject entirely. ### 10.2.1 Node Exports Node defines a global exports object that is always defined. If you are writing a Node module that exports multiple values, you can simply assign them to the properties of this object: ```js const sum = (x, y) => x + y; const square = x => x * x; exports.mean = data => data.reduce(sum)/data.length; exports.stddev = function(d) { let m = exports.mean(d); return Math.sqrt(d.map(x => x - m).map(square).reduce(sum)/(d.length-1)); }; ``` Often, however, you want to define a module that exports only a single function or class rather than an object full of functions or classes. To do this, you simply assign the single value you want to export to module.exports: ```js module.exports = class BitSet extends AbstractWritableSet { // implementation omitted }; ``` The default value of module.exports is the same object that exports refers to. In the previous stats module, we could have assigned the mean function to module.exports.mean instead of exports.mean. Another approach with modules like the stats module is to export a single object at the end of the module rather than exporting functions one by one as you go: ```js // Define all the functions, public and private const sum = (x, y) => x + y; const square = x => x * x; const mean = data => data.reduce(sum)/data.length; const stddev = d => { let m = mean(d); return Math.sqrt(d.map(x => x - m).map(square).reduce(sum)/(d.length-1)); }; // Now export only the public ones module.exports = { mean, stddev }; ``` ### 10.2.2 Node Imports A Node module imports another module by calling the require() function. The argument to this function is the name of the module to be imported, and the return value is whatever value (typically a function, class, or object) that module exports. If you want to import a system module built in to Node or a module that you have installed on your system via a package manager, then you simply use the unqualified name of the module, without any “/” characters that would turn it into a filesystem path: ```js // These modules are built in to Node const fs = require("fs"); // The built-in filesystem module const http = require("http"); // The built-in HTTP module // The Express HTTP server framework is a third-party module. // It is not part of Node but has been installed locally const express = require("express"); ``` When you want to import a module of your own code, the module name should be the path to the file that contains that code, relative to the current module’s file. It is legal to use absolute paths that begin with a / character, but typically, when importing modules that are part of your own program, the module names will begin with ./ or sometimes ../ to indicate that they are relative to the current directory or the parent directory. For example: ```js const stats = require('./stats.js'); const BitSet = require('./utils/bitset.js'); ``` (You can also omit the .js suffix on the files you’re importing and Node will still find the files, but it is common to see these file extensions explicitly included.) When a module exports just a single function or class, all you have to do is require it. When a module exports an object with multiple properties, you have a choice: you can import the entire object, or just import the specific properties (using destructuring assignment) of the object that you plan to use. Compare these two approaches: ```js // Import the entire stats object, with all of its functions const stats = require('./stats.js'); // We've got more functions than we need, but they're neatly // organized into a convenient "stats" namespace. let average = stats.mean(data); // Alternatively, we can use idiomatic destructuring assignment to import // exactly the functions we want directly into the local namespace: const { stddev } = require('./stats.js'); // This is nice and succinct, though we lose a bit of context // without the 'stats' prefix as a namspace for the stddev() function. let sd = stddev(data); ``` ### 10.2.3 Node-Style Modules on the Web Modules with an Exports object and a require() function are built in to Node. But if you’re willing to process your code with a bundling tool like webpack, then it is also possible to use this style of modules for code that is intended to run in web browsers. Until recently, this was a very common thing to do, and you may see lots of web-based code that still does it. Now that JavaScript has its own standard module syntax, however, developers who use bundlers are more likely to use the official JavaScript modules with import and export statements. ## 10.3 Modules in ES6 ES6 adds import and export keywords to JavaScript and finally supports real modularity as a core language feature. ES6 modularity is conceptually the same as Node modularity: each file is its own module, and constants, variables, functions, and classes defined within a file are private to that module unless they are explicitly exported. Values that are exported from one module are available for use in modules that explicitly import them. ES6 modules differ from Node modules in the syntax used for exporting and importing and also in the way that modules are defined in web browsers. The sections that follow explain these things in detail. First, though, note that ES6 modules are also different from regular JavaScript “scripts” in some important ways. The most obvious difference is the modularity itself: in regular scripts, top-level declarations of variables, functions, and classes go into a single global context shared by all scripts. With modules, each file has its own private context and can use the import and export statements, which is the whole point, after all. But there are other differences between modules and scripts as well. Code inside an ES6 module (like code inside any ES6 class definition) is automatically in strict mode (see §5.6.3). This means that, when you start using ES6 modules, you’ll never have to write "use strict" again. And it means that code in modules cannot use the with statement or the arguments object or undeclared variables. ES6 modules are even slightly stricter than strict mode: in strict mode, in functions invoked as functions, this is undefined. In modules, this is undefined even in top-level code. (By contrast, scripts in web browsers and Node set this to the global object.) #### ES6 MODULES ON THE WEB AND IN NODE ES6 modules have been in use on the web for years with the help of code bundlers like webpack, which combine independent modules of JavaScript code into large, non-modular bundles suitable for inclusion into web pages. At the time of this writing, however, ES6 modules are finally supported natively by all web browsers other than Internet Explorer. When used natively, ES6 modules are added into HTML pages with a special ` ``` Code inside an inline `