diff --git a/docs/ch10.md b/docs/ch10.md index 057ad3694c367080b49a6e2988f517f5c91a0732..718b15d6b0b4e5adb2bc46323766b0c0cef985f5 100644 --- a/docs/ch10.md +++ b/docs/ch10.md @@ -5,11 +5,9 @@ Until recently, JavaScript had no built-in support for modules, and programmers 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() +- 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. @@ -19,7 +17,7 @@ The reason that the methods of one class are independent of the methods of other 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) { ... } @@ -34,8 +32,9 @@ const BitSet = (function() { // Set BitSet to the return value of this function // ... 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 @@ -62,11 +61,12 @@ const stats = (function() { // 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]; } @@ -90,8 +90,9 @@ modules["stats.js"] = (function() { 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; @@ -102,6 +103,7 @@ 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 @@ -113,7 +115,7 @@ Node modules import other modules with the require() function and export their p ### 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; @@ -122,13 +124,15 @@ 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; @@ -140,11 +144,12 @@ const stddev = d => { // 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 @@ -152,14 +157,16 @@ 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'); @@ -174,6 +181,7 @@ 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. @@ -184,14 +192,14 @@ ES6 adds import and export keywords to JavaScript and finally supports real modu 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