forked from marijnh/Eloquent-JavaScript
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy path10_modules.txt
More file actions
879 lines (716 loc) · 32 KB
/
10_modules.txt
File metadata and controls
879 lines (716 loc) · 32 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
:chap_num: 10
:prev_link: 09_regexp
:next_link: 11_language
:load_files: ["code/chapter/10_modules.js", "js/loadfile.js"]
= Modules =
[quote, Master Yuan-Ma, The Book of Programming]
____
A beginning programmer writes her programs like an ant builds her
hill, one piece at a time, without thought for the bigger structure.
Her programs will be like loose sand. They may stand for a while, but
growing too big they fall apart.
Realizing this problem, the programmer will start to spend a lot of
time thinking about structure. Her programs will be rigidly
structured, like rock sculptures. They are solid, but when they must
change, violence must be done to them.
The master programmer knows when to apply structure and when to leave
things in their simple form. Her programs are like clay, solid yet
malleable.
____
Every program has a shape. On a small scale, this shape is determined
by its division into functions, and the blocks inside those functions.
Programmers have a lot of freedom in the shape they give their
programs. Shape follows more from the taste of the
programmer than from the program's intended functionality.
When looking at a larger program in its entirety, individual functions
start to blend into the background. It would be good to have a
larger unit of organization.
_Modules_ divide programs into clusters of code that, by _some_
criterion, belong together. This chapter explores some of the
benefits that such clustering provides, and shows techniques for
building modules in JavaScript.
== Organization ==
There are number of reasons why authors divide their books into
chapters and sections. These divisions make it easier for a reader to
see how the book is built up, and to find specific parts that they are
interested in. They also help the _author_, by providing a clear focus
for every section.
The benefits of organizing a program into several files or modules are
similar. Structure helps people who aren't yet familiar with the
code find what they are looking for, and makes it easier for the
programmer to keep things that are closely related close together.
Some programs are even organized along the model of a traditional
text, with a well-defined order in which the reader is encouraged to
go through the program, and lots of prose (comments) providing a
coherent description of the code. This make reading the program a lot
less intimidating—reading unknown code is usually intimidating—but has
the downside of being more work to set up. It also makes the program more
difficult to change, because prose tends to be more
tightly interconnected than code. This style is called _literate
programming_. The “project” chapters of this book can be considered
literate programs.
As a general rule, structuring things costs energy. In the early
stages of a project, when you are not quite sure yet what goes where
or what kind of modules the program needs at all, I endorse a
minimalist, structureless attitude. Just put everything wherever it
is convenient to put it until the code stabilizes. That way, you won't
be wasting time moving pieces of the program back and forth, and won't
accidentally lock yourself into a structure that does not actually fit
your program.
== Namespaces ==
Most modern programming languages have a scope level between _global_
(everyone can see it) and _local_ (only this function can see it).
JavaScript does not. Thus, by default, everything that needs to be
visible outside of the scope of the current function is visible
_everywhere_.
Namespace pollution, the problem of a lot of unrelated code having to
share a single set of global variable names, was mentioned in chapter
4, where the `Math` object was given as an example of an object that
acts like a module by grouping math-related functionality.
Though JavaScript provides no actual module construct yet, objects can
be used to create publicly accessible sub-namespaces, and functions
can be used to create an isolated, private namespace inside of a
module. Later in this chapter, I will discuss way to build reasonably
convenient, namespace-isolating modules on top of the primitive
concepts that JavaScript gives us.
== Reuse ==
In a “flat” project, which isn't structured as a set of modules,
it is not apparent which parts of the code are
needed to use a particular function. In my program for spying on
my enemies (see Chapter 9), I wrote a function for reading configuration files.
If I want to use that function in another project, I must go
and copy out the part of the old program that look like they are
relevant to the functionality that I need, and paste them into my new
program. Then, if I find a mistake in that code, I'll only fix it in whichever
program that I'm working with at the time, and forget to also fix it
in the other program.
Once you have lots of such shared, duplicated pieces of code, you will
find yourself wasting a lot of time and energy on moving them around
and keeping them up to date.
Putting pieces of functionality that stand on their own into
separate files and modules makes them easier to track, update,
and share, because all the various
pieces of code that want to use the module load it from the same actual file.
This idea gets even more powerful when the relations between
modules—which other modules each module depends on—are explicitly
stated. You can then automate the process of installing and upgrading
external modules.
Taking this idea even further, imagine an online service that tracks
and distributes hundreds of thousands of such modules, allowing you to
search for the functionality you need, and, once you find it, set up
your project to automatically download it.
(((NPM)))This service exists. It is called _NPM_ (http://npmjs.org[_npmjs.org_]).
NPM consists of an online database of modules, and a tool for
downloading and upgrading the modules your program depends on. It grew
out of node.js, the browser-less JavaScript environment we will discuss in
Chapter FIXME, but can also be useful when programming for the browser.
== Decoupling ==
Another important role of modules is isolating pieces of code from
each other, in the same way that the object interfaces from Chapter 6
do. A well-designed module will provide an interface for external code
to use. As the module gets updated with bug-fixes and new
functionality, the existing interface stays stable, so that
other modules can use the new, improved version without any changes to
themselves.
Note that a stable interface does not mean no new functions, methods, or variables are added.
It just means that existing functionality isn't removed and its
meaning is not changed.
A good module interface should allow the module to grow without
breaking the old interface. This means
exposing as few of the module's internal concepts as possible,
while also making the “language” that the interface exposes powerful and
flexible enough to be applicable in a wide range of situations.
For interfaces that expose a single, focused concept, like a
configuration file reader, this design comes naturally. For others, like a
text editor, which has many different aspects that external
code might need to access (content, styling, user actions, and so on),
it requires careful design.
== Functions as namespaces ==
Functions are the only things in JavaScript that create a new
scope. So if we want our modules to have their own scope, we will have
to base them on functions.
Consider this trivial module for associating names with
day-of-the-week numbers, as returned by a `Date` object's `getDay`
method:
[source,javascript]
----
var names = ["Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"];
function dayName(number) {
return names[number];
}
console.log(dayName(1));
// → Monday
----
The `dayName` function is part of the module's interface, but the `names`
variable is not. We would prefer _not_ to spill it into the global
scope.
We can do this:
[source,javascript]
----
var dayName = function() {
var names = ["Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"];
return function(number) {
return names[number];
};
}();
console.log(dayName(3));
// → Wednesday
----
Now `names` is a local variable in an (unnamed) function. This
function is created and immediately called, and its return value (the
actual `dayName` function) is stored in a variable. We could have
pages and pages of code in this function, with a hundred local
variables, and they would all be internal to our module—visible to the
module itself, but not to outside code.
We can use a similar pattern to isolate code from the outside world
entirely. The module below logs a value to the console, but does not actually
provide any values for other modules to use.
[source,javascript]
----
(function() {
function square(x) { return x * x; }
var hundred = 100;
console.log(square(hundred));
})();
// → 10000
----
This code simply outputs the square of one hundred, but in the real world
it could be a module that adds a method to some prototype, or sets up
a widget on a web page. It is wrapped in a function to prevent
the variables it uses internally from polluting the global scope.
Why did we wrap the namespace function in a pair of parentheses? This
has to do with a quirk in JavaScript's syntax. If an _expression_
starts with the keyword `function`, it is a function expression.
However, if a _statement_ starts with that keyword, it is a function
declaration, which requires a name and cannot be immediately called.
Even though a statement may start with an expression, the second rule
takes precedence, and if we had left out the extra parentheses in the
example above, it would produce a syntax error. You can think of the parentheses
as a trick to force the language to see that we are writing an
expression.
== Objects as interfaces ==
Now imagine that
we want to add a `dayNumber` function to our day-of-the-week module that goes
from a name to a number. We can't simply return the function anymore,
but must wrap the two functions in an object.
[source,javascript]
----
var weekDay = function() {
var names = ["Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"];
return {
name: function(number) { return names[number]; },
number: function(name) { return names.indexOf(name); }
};
}();
console.log(weekDay.name(weekDay.number("Sunday")));
// → Sunday
----
For bigger modules, gathering all the _exported_ values into an object
at the end of the function becomes awkward, since many of the exported
functions are likely to be big, and you'd prefer to write them somewhere
else, near related internal code. A convenient alternative is to declare
an object (usually
named `exports`) and add properties to that whenever we are
defining something that needs to be exported. In the example below,
the module function takes its interface object as argument, allowing
code outside of the function to create it and store it in a variable.
(Outside of a funcion, `this` refers to the global scope object).
[source,javascript]
----
(function(exports) {
var names = ["Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"];
exports.name = function(number) {
return names[number];
};
exports.number = function(name) {
return names.indexOf(name);
};
})(this.weekDay = {});
console.log(weekDay.name(weekDay.number("Saturday")));
// → Saturday
----
== Detaching from the global scope ==
The above pattern is commonly used by JavaScript modules intended for
the browser. The module will claim a single global name, and wrap
its code in a function in order to have its own private namespace.
But this pattern still breaks if multiple modules happen to claim the
same name, or if you want to load two versions
of a module alongside each other.
With a little plumbing, we can create a system that allows one module to
directly ask for the interface object of another module,
without going through the global scope.
Our goal is a `require` function which, when given a module name, will
load that module's file (from disk or the Web, depending on the platform
we are running on), and return the appropriate interface value.
This approach solves the
problems mentioned above, and has the added benefit of making you program's
dependencies explicit, making it harder to accidentally make use of
some module without stating that you need it.
For `require` we need two things. First, we want
a function `readFile`,
which returns the content of a given file as a string.
(A single such function is not present in standard JavaScript,
but different JavaScript environments, such as the browser and node.js,
provide their own ways of accessing files.
For now, let's just pretend we have this simple function.)
Secondly, we need to be able
to actually execute this string as JavaScript
code.
== Evaluating data as code ==
There are several ways to take data (a string of code) and run it as
part of the current program.
The most obvious way is the special operator `eval`, which
will execute a string of code in the _current_ scope. This is usually
a rather bad idea, because it breaks some of the sane properties that
scopes normally have, such as being isolated from the outside world.
[source,javascript]
----
function evalAndReturnX(code) {
eval(code);
return x;
}
console.log(evalAndReturnX("var x = 2"));
// → 2
----
A better way of interpreting data as code is to use the `Function`
constructor. This takes two arguments: a string containing a
comma-separated list of argument names, and a string containing
the function's body.
[source,javascript]
----
var plusOne = new Function("n", "return n + 1;");
console.log(plusOne(4));
// → 5
----
This is precisely what we need for our modules. We can wrap a module's code in a
function, with that function's scope becoming our module scope.
== Require ==
The following is a very minimal implementation of `require`:
// test: wrap
[source,javascript]
----
function require(name) {
var code = new Function("exports", readFile(name));
var exports = {};
code(exports);
return exports;
}
console.log(require("weekDay").name(1));
// → Monday
----
Since the `new Function` constructor wraps
the module code in a function, we don't have write a wrapping
namespace function in the module file itself. And since we make `exports`
an argument to the module function, the module does not have
to declare it. This removes a lot of clutter from our
example module:
[source,javascript]
----
var names = ["Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"];
exports.name = function(number) {
return names[number];
};
exports.number = function(name) {
return names.indexOf(name);
};
----
When using this pattern, a module typically starts with a few variable
declarations that load the modules it depends on.
// test: no
[source,javascript]
----
var weekDay = require("weekDay");
var today = require("today");
console.log(weekDay.name(today.dayNumber()));
----
The simplistic implementation of `require` given above has several
problems. For one, it will load and run a module every time it is
++require++d, so if several modules have the same dependency, or a
`require` call is put inside of a function that will be called
multiple times, time and energy will be wasted.
This can be solved by storing the modules that have already been
loaded in an object, and simply returning the existing value when one
is loaded multiple times.
The second problem is that it is not possible for a module to directly
export value other than the `exports` object, such as a function. For
example, a module might want to only export
the constructor of the object type it defines. Right now, it can not
do that, because `require` always uses the `exports` object it creates
as the exported value.
The traditional solution for this is to provide modules with another variable,
`module`, which is an object that has a property `exports`. This
property initially points at the empty object created by `require`,
but can be overwritten with another value in order to export something
else.
// test: wrap
// include_code
[source,javascript]
----
function require(name) {
if (name in require.cache)
return require.cache[name];
var code = new Function("exports, module", readFile(name));
var exports = {}, mod = {exports: exports};
code(exports, mod);
require.cache[name] = module.exports;
return module.exports;
}
require.cache = Object.create(null);
----
We now have a module system that uses a single global variable
(`require`) to allow modules to find and use each other without going
through the global scope.
This style of module system is called _CommonJS modules_, after the
pseudo-standard that first specified it. It is built into the node.js
system. Real implementations do a lot more than the example I showed.
Most importantly, they have a much more intelligent way of going from
a module name to an actual piece of code, allowing both pathnames
relative to the current file, and module names that point directly to
locally installed modules.
== Slow-loading modules ==
Though it is possible to use the style above when writing JavaScript for
the browser, it is somewhat involved. The reason for this is that
reading a file (module) from the Web is a lot slower than reading it
from the hard disk.
While a script is running in the browser, nothing else can happen to the
web site on which it runs, for reasons that will become clear in Chapter 14. This means that if every `require` call
went and loaded something from some far-away web server, the page would
freeze for a painfully long time while loading its scripts.
One way to work around this problem is to run a
program like _http://browserify.org[Browserify]_) on your code
before you serve it on a web page. This will look for calls to `require`
and gather the dependencies it finds
together into a big file. When you actually serve the code, you
only have to call `require` once with this big file.
Another solution is to wrap your module in a function, load the
modules it depends on in the background, and only run this function
when all its dependencies have been loaded. That is what the
_Asynchronous Module Definition_ (_AMD_) module system does.
Our trivial program with dependencies, in AMD, would looks like this:
// test: no
[source,javascript]
----
define(["weekDay", "today"], function(weekDay, today) {
console.log(weekDay.name(today.dayNumber()));
});
----
The `define` function is central to this approach. It
takes first an array of module names, and then a function that takes
one argument for each dependency. It will load the dependencies (if
they haven't already been loaded) in the background, allowing the page
to continue working while the files are being fetched. Once all
dependencies are loaded, `define` will call the function it was given,
with the interfaces of those dependencies as arguments.
The modules that are loaded this way must themselves contain a call to
`define`. The value used as their interface is whatever was returned
by the function passed to `define`. Here is the `weekDay` module
again:
[source,javascript]
----
define([], function() {
var names = ["Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"];
return {
name: function(number) { return names[number]; },
number: function(name) { return names.indexOf(name); }
};
});
----
In order to be able show a minimal implementation of `define`, we will
pretend we have a `backgroundReadFile` function, which takes a file
name and a function, and will call the function with the content of
the file as soon as it has finished loading it. (Chapter 17 will
explain how to write that function.)
For the purpose of keeping track of modules while they are being
loaded, the implementation of `define` will use objects that describe
the state of modules, telling us whether they are available yet, and
providing their interface when they are.
The `getModule` function, when given a name, will return such an
object, and ensure that the module is scheduled to be loaded. It uses
a cache object to avoid loading the same module twice.
// include_code
[source,javascript]
----
var defineCache = Object.create(null);
var currentMod = null;
function getModule(name) {
if (name in defineCache)
return defineCache[name];
var mod = {exports: null,
loaded: false,
onLoad: []};
defineCache[name] = mod;
backgroundReadFile(name, function(code) {
currentMod = mod;
new Function("", code)();
});
return mod;
}
----
We assume the loaded file also contains a (single) call to `define`.
The `currentMod` variable is used to tell this call about the module
object that is currently being loaded, so that it can update this
object when it finishes loading. We will come back to this mechanism
in a moment.
The `define` function itself uses `getModule` to fetch or create the
module objects for the current module's dependencies. Its task is to
schedule the `moduleFunction` (the function that contains the module's
actual code) to be run whenever those dependencies are loaded. For
this purpose, it defines a function `whenDepsLoaded`, that is added to
the `onLoad` array of all dependencies that are not yet loaded. This
function immediately returns if there are still unloaded dependencies,
so it will only do actual work once, when the last dependency has
finished loading. It is also called immediately, from `define` itself,
in case there are no dependencies that need to be loaded.
// include_code
[source,javascript]
----
function define(depNames, moduleFunction) {
var myMod = currentMod;
var deps = depNames.map(getModule);
deps.forEach(function(mod) {
if (!mod.loaded)
mod.onLoad.push(whenDepsLoaded);
});
function whenDepsLoaded() {
if (!deps.every(function(m) { return m.loaded; }))
return;
var args = deps.map(function(m) { return m.exports; });
var exports = moduleFunction.apply(null, args);
if (myMod) {
myMod.exports = exports;
myMod.loaded = true;
myMod.onLoad.every(function(f) { f(); });
}
}
whenDepsLoaded();
}
----
When all dependencies are available, `whenDepsLoaded` calls the
function that holds the module, giving it the dependencies' interfaces
as arguments.
The first thing `define` does is store the value that `currentMod` had
when it was called in a variable `myMod`. Remember that `getModule`,
just before evaluating the code for a module, stored the corresponding
module object in `currentMod`. This allows `whenDepsLoaded` to store
the return value of the module function in that module's `exports`
property, set the module's `loaded` property to true, and call all the
functions that are waiting for the module to load.
This code is a lot harder to follow than the `require` function. Its
execution does not follow a simple, predictable path. Instead,
multiple operations are set up to happen at some unspecified time in
the future, which obscures the way the code executes.
A real AMD implementation is, again, quite a lot more clever about
resolving module names to actual URLs, and generally more robust than
the one above. The _RequireJS_ (_requirejs.org_) project provides a
popular implementation of this style of module loader.
== Interface design ==
(((interface design)))Designing interfaces for modules and object
types is one of the subtler aspects of programming. Any non-trivial
piece functionality can be modeled in various ways. Finding a way that
works well requires insight and foresight.
The best way to learn the value of good interface design is to use
lots of interfaces, some good, some bad. Experience will teach
you what works and what doesn't. Never assume that a painful interface
is “just the way it is”. Fix it, or wrap it in a new interface that
works better for you.
=== Predictability ===
(((convention)))If programmers can predict the way your interface
works, they (or you) won't get sidetracked as often by the need to
look up how to use it. Thus, try to follow conventions. When
there is another module or part of the standard JavaScript environment
that does something similar to what you are implementing, it might be
a good idea to make your interface resemble the existing interface.
That way, it'll feel familiar to people who know the existing
interface.
Another area where predictability is important is the actual
_behavior_ of your code. It can be tempting to make an unnecessarily clever interface with
the justification that it's more convenient to use. For
example, you could accept all kinds of different types and
combinations of arguments, and do the “right thing” for all of them.
Or you could provide dozens of specialized convenience functions that provide
slightly different flavors of your module's functionality. These might
make code that builds on your interface slightly shorter, but they
will also make it much harder for people to build a clear mental model
of the module's behavior.
=== Composability ===
(((composability)))In your interfaces, try to use the simplest data
structures possible, and make functions do a single, clear
thing. Whenever practical, make them pure functions (see Chapter 3).
For example, it is not uncommon for modules to provide their own
array-like collection objects, with their own interface for counting
and extracting elements. Such objects won't have `map` or `forEach`
methods, and any existing function that expects a real array won't be
able to work with them. This is an example of poor
__composability__—the module cannot be easily composed with other
code.
(((spell-check example)))Another example would be a module for
spell-checking text, which we might need when we want to write a text
editor. The spell-checker could be made to operate directly on
whichever complicated data structures the editor uses, and directly
call internal functions in the editor to have the user choose between
spelling suggestions. If we go that way, the module cannot be used
with any other programs. On the other hand, if we define the
spell-checking interface so that you can pass it a simple string and
it will return the position in the string where it found a possible
misspelling, along with an array of suggested corrections, then we
have an interface that could also be composed with other systems,
because strings and arrays are always available in JavaScript.
=== Layered interfaces ===
(((layering (of an interface))))When designing an interface for a
complex piece of functionality—sending email, for example—you often run
into a dilemma. On the one hand, you do not want to overload the user
of your interface with details. They shouldn't have to study your
interface for 20 minutes before they can send an email. On the other
hand, you do not want to hide all the details either—when people need
to do complicated things with your module, they should be able to.
Often the solution is to provide two interfaces: a detailed
_low-level_ one for complex situations and a simple _high-level_ one
for routine use. The second can usually be built very easily using the
tools provided by the first. In the email module, the high-level
interface could just be a function that takes a message, a sender
address, and a receiver address, and sends the email. The low-level
interface would allow full control over email headers, attachments,
sending HTML mail, and so on.
== Summary ==
Modules provide structure to bigger programs by separating the code
into different files and namespaces. Giving these modules well-defined
interfaces makes them easier to use and reuse,
and makes it possible to continue using them as the module
itself evolves.
Though the JavaScript language itself is characteristically unhelpful
when it comes to modules, the flexible functions and objects it
provides make it possible to define rather nice module systems.
Function scopes can be used as internal namespaces for the module, and
objects can be used to store sets of exported values.
There are two popular, well-defined approaches to such modules. One is
called _CommonJS Modules_, and revolves around a `require` function
that fetches a module by name and returns its interface. The other is
called _AMD_, and uses a `define` function that takes an array of
module names and a function, and, after loading the modules, runs the
function with their interfaces as arguments.
== Exercises ==
=== Month names ===
Write a simple module similar to the `weekDay` module, which can
convert month numbers (zero-based, as in the `Date` type) to names,
and names back to numbers. Give it its own namespace, since it will
need an internal array of month names, and use plain JavaScript,
without any module loader system.
ifdef::html_target[]
// test: no
[source,javascript]
----
// Your code here.
console.log(month.name(2));
// → March
console.log(month.number("November"));
// → 10
----
endif::html_target[]
!!solution!!
This follows the `weekDay` module almost exactly. A function
expression, called immediately, wraps the variable that holds the
array of names, along with the two functions that must be exported.
The functions are put in an object, and returned. The returned
interface object is stored in the `month` variable.
!!solution!!
=== A return to electronic life ===
Hoping that Chapter 7 is still somewhat fresh in your mind, think back
to the system designed in that chapter and come up with a way to separate
the code into modules. To refresh your memory, these are the
functions and types defined in that chapter, in order of appearance.
----
Vector
Grid
directions
randomElement
BouncingCritter
elementFromChar
World
charFromElement
Wall
View
directionNames
WallFollower
dirPlus
LifelikeWorld
Plant
PlantEater
SmartPlantEater
Tiger
----
Don't exaggerate and create too many modules. A book that starts a
new chapter for every page would probably get on your nerves, if only
because of all the space wasted on titles. Similarly, having to open
ten files to read a tiny project isn't helpful. Aim for three to five
modules.
You can choose to have some functions become internal to their module,
and thus inaccessible to other modules.
There is no single correct solution here. Module organization is
largely a matter of taste.
!!solution!!
Here is what I came up with. I've put parentheses around internal
functions.
----
Module "grid"
Vector
Grid
directions
Module "world"
(randomElement)
(elementFromChar)
(charFromElement)
View
World
LifelikeWorld
directions [re-exported]
Module "simple_ecosystem"
(randomElement) [duplicated]
(directionNames)
(dirPlus)
Wall
BouncingCritter
WallFollower
Module "ecosystem"
Wall [duplicated]
Plant
PlantEater
SmartPlantEater
Tiger
----
I have re-exported the `directions` array from the `grid` module from
`world`, so that modules built on that (the ecosystems) don't have to
know or worry about the existence of the `grid` module.
I also duplicated two generic and tiny helper values (`randomElement`
and `Wall`) since they are used as internal details in different
contexts, and do not belong in the interfaces for these modules.
!!solution!!
=== Circular dependencies ===
A tricky subject in dependency management is circular dependencies,
where module A depends on B, and B also depends on A. Many module
systems simply forbid this. CommonJS allows a limited form:
it works as long as the modules do not replace their default
`exports` object with another value, and only start accessing each
other's interface after they finish loading.
Can you think of a way in which support for this feature could be
implemented? Look back to the definition of `require`, and consider
what the function would have to do to allow this.
!!solution!!
The trick is to add the `exports` object created for a module to
`require`'s cache _before_ actually running the module. This means the
module will not yet have had a chance to override `module.exports`, so
we do not know whether it may want to export some other value. After
loading, the cache object is overridden with `module.exports`, which
may be a different value.
But if, in the course of loading the module, a second module is loaded
that asks for the first module, its default `exports` object, likely
still empty at this point, will be in the cache, and the second module
will receive a reference to it. If it doesn't try to do anything with
the object until the first module has finished loading, things will
work.
!!solution!!