Modules
Modules are important in any programming language, once you get past small programs authored by a single person. Once you start having larger projects with multiple people on the project, modules become essential.
In a way, that distinction explains the evolution of modules in JavaScript. In its early days, JS programs were small, so modules weren't essential. The language didn't have any native support for modules, though it had a number of programming constructs that were pressed into service. As programs grew, a module system became necessary.
The next app that our book presents is Coffeerun. It's not huge, but it's big enough to warrant (or, at least, excuse) introducing modules. Unfortunately, the module system wasn't ready when our book was being published, so they used the state of the art at that time, callled IIFEs. I've re-written the Coffeerun example using modern JavaScript modules instead of IIFEs, but I'll also describe IIFEs in this reading, so you can understand the code in your book.
Motivation¶
Before we dig into the specifics, let me tell you a real story that actually happened to me. I was writing some graphics software in a language that doesn't have a modern module system. I had to compute some trig functions, (sines and cosines, mostly), so I imported a bunch of math functions. (In this case, "import" means to add some functions and variables to the list of defined values in my program.) I was also using a variety of colors, such as red, blue, green, even beige and tan.
My program started getting weird errors that I didn't understand. Many hours later, I discovered that the "tan" variable among my colors was interfering with the "tan" function from the trig functions. That was my problem. I renamed "tan" to be "color_tan" or some such thing, and the errors went away.
Modules are a general way that programming languages deal with name clashes like that. (Some languages use another word than "modules" but we'll call them "modules".)
Namespaces¶
First a bit of terminology. When we program, we often define functions and (global) variables, and maybe some global constants, and maybe even some classes (in object-oriented programming). Each of these things has a name and that name maps to a value. Here are some examples:
name | kind and sample value |
---|---|
tan |
variable, '#d2b48c' |
tan |
function, function (theta) { return sin(theta)/cos(theta) } |
numStudents |
variable, 13 |
maxStudents |
constant, 30 |
studentList |
variable, ['Alice','Beth',...] |
addStudent |
function (newStu) { ... } |
The first column is a set of names and the second column is a set of
values. A mapping from a set of names to a set of values is called a
namespace. In any particular namespace, a name has one and only one
value. So, the two entries for tan
are inconsistent. Only one of
them can exist; they can't co-exist. (One of them will "win", but my
program didn't work because it needed both.)
Namespaces and Dictionaries¶
When you learned about dictionaries in Python, you learned them as a mapping from keys to values. The keys are often (but not necessarily) strings. Each string can be considered a name for the corresponding value. Thus, if you understand Python dictionaries, you already understand namespaces.
(I think some versions of Python implement modules using dictionaries, but I'm not certain of that. But they could.)
Recall that JavaScript objects are also a mapping from keys to values, and so they are also a kind of namespace. Here's a JavaScript object for the example above:
var namespace1 = {
tan: '#d2b48c',
numStudents: 13,
maxStudents: 30,
studentsList: ['Alice', 'Beth' ],
addStudent: function (newStu) { }
};
(This isn't 100% accurate, since we can't have constants (read-only values) with a dictionary/object without some additional machinery, but it's close.)
We will return to this idea a bit later.
Namespaces and Functions¶
Earlier, I talked about global variables and functions. But you also know that functions can have local variables. Moreover, these local variables shadow the global variables of the same name.
The set of local variables for function form a namespace. Here's an example with our local variables:
var tan = function (angle) { return sin(angle)/cos(angle); }
function namespace2() {
var tan = 'beige';
var numStudents = 13;
var maxStudents = 20;
var studentsList = ['Al','Bob'];
function addStudent(newStu) {
studentsList.push(newStu);
}
return {tan: tan,
num: numStudents,
max: maxStudents,
list: studentsList,
add: addStudent};
}
This function doesn't do much, other than return a dictionary consisting of a bunch of local names and their values. But the function is perfectly valid and forms a namespace with local definitions of our names.
We'll return to this idea a bit later, too.
Module Metaphor¶
Let's return to my problem of tan
. If we only have one global
namespace, there can only be one definition of tan
and so to have
two, I'd have to rename one of them, like color_tan
. That's ugly,
annoying, and doesn't scale well. We need a better idea.
Let's step back for a moment about think about what we want:
- separation: we want to have separate namespaces for separate
parts of our code. The team working on the trig code and the team
working on the color code should each be able to have their own
definition of
tan
that is independent of the other team's definition. In fact, they should have complete freedom to make up any names they want, without having to consult the other team. - sharing: it should be possible for someone building a program to use code from both the trig team and the color team, without getting obscure bugs.
We need a way to think about how to solve these problems. Each team is going to work in its own module, which I will picture as a kind of "room" in an odd kind of building:
The main module (in white) adjoins two additional modules: trig
(in blue) and colors
(in green). We think of our program as the main
module (it often doesn't have a name at all) with the other modules
helping out.
If our program needs additional modules, then we can put one on the upper wall of the main module, or crowd additional modules along the walls or even make a five-, six- or more sided main module. I won't try to picture those; use your imagination.
(Note that we could draw the visualization where the main module and the other modules share a common wall. That is, a face of each module contacts another face. That's called an interface. The notion of an interface is common in talking about modules.)
Modules in Python¶
Python is a language with a modern module system. Many of you have
probably used it, in CS111 or similar, without really noticing. (For
example, I'm told you imported random
to your Python programs to use
the random.randInt()
function.) To implement the example we showed
above, we would divide our Python program into three (at least)
files. Let's call them:
trig.py
colors.py
main.py
Here's the complete code for trig.py
. There's nothing very
interesting in it; just three functions and a global variable that
helps us convert from degrees to radians. (If you've forgotten your
trig; that's fine. We're only interested in the names of these
things.)
import math
tau = math.pi * 2
def sin(angle):
'''returns the sine of the angle, given in degrees'''
return math.sin( angle * tau / 360.0 )
def cos(angle):
'''returns the cosine of the angle, given in degrees'''
return math.cos( angle * tau / 360.0 )
def tan(angle):
'''returns the tangent of the angle, given in degrees'''
return math.tan( angle * tau / 360.0 )
Here's the complete code for colors.py
. There's even less
interesting code here: just a few assignment statements. Note that
these are the hexadecimal color definitions. Hexadecimal is one way to
describe colors in HTML/CSS. There are 140 "standard"
colors.
red = '#ff0000'
green = '#008000'
lime = '#00ff00'
blue = '#0000ff'
tan = '#d2b48c'
Now, let's see the code for our main program:
import trig
import colors
print('tangent of 45 degrees: {}'.format(trig.tan(45)))
print('some people like the combination of blue, {}, and tan, {}'.
format(colors.blue,colors.tan))
Note the two import
statements. That adds one of those adjoining
rooms in our visual metaphor. Once those are added, we can refer to a
name in another module (another room) by saying module.name
, like trig.tan
or colors.tan
.
If you like the room metaphor, it's just room.name
. Inside the room,
we can omit the room
part and just use the name. Outside the room,
we have to say which room we want, before specifying the name.
Another metaphor that might work for you is to think of it as a family name followed by a given name, like "Hua Mulan". Inside the family, the name is unique and it's sufficient just to give the name, but outside the family, you have to specify the family and then the given name.
Modules in JavaScript¶
A few notes on history. When JS first came out, it was like that other
language I used where there was only one namespace, so my program only
had one tan
and so I got weird errors.
One technique that was used to create separate modules was the function namespace that we learned about a few sections ago. This is the idea behind the IIFEs that your book describes. But IIFEs are hard to work with and hard to understand, so I've decided to avoid those this semester. But there's information below to help you understand the code in the book.
There are also module systems that were invented for server-side
JavaScript using node.js
. We won't discuss those at all.
Eventually, about the time that our book came out, JavaScript introduced a module system that has been implemented in modern browsers. We'll use that.
Modern Modules¶
Python, as we noted above, has an import
statement. However, there
are no limitations on what names the importing module can use. In
other words, our main module can access all the names in either the
trig
or colors
modules.
An alternative is for the module to announce what names it is exporting (and which other modules can import). That's what JavaScript's module system does: the module lists what names it is exporting and the other module lists what names it is importing.
In JavaScript's import, you can list (and, optionally, rename) the names that you are importing from a module, or you can import all of them as a dictionary. Since importing all as a dictionary mimicks what we know from Python and is syntactically convenient, we'll use that.
In modern JavaScript, modules are implemented using two new statements
and a new variant of the script
tag:
- The
export
statement says what names/values to export from a module - The
import
statement says what names/values to import from a module - The
script
tag says to load a.js
file, either as a script or as a module
Module Example¶
We'll continue our trig and colors example, this time in
JavaScript. Here's our colors.js
file. It's pretty similar, but now
we have to list all the names we are exporting.
var red = '#ff0000';
var green = '#008000';
var lime = '#00ff00';
var blue = '#0000ff';
var tan = '#d2b48c';
export { red, green, lime, blue, tan };
Next, let's look at the trig.js
file. Again, this pretty
straightforward, except for the list of names that we are exporting:
// some mathematicians use tau instead of pi
// this should really be a const
var tau = 2.0 * Math.PI;
// Returns the number of radians to turn, based on what fraction of a
// full turn. E.g. 1/4 of a turn is 90 degrees counter-clockwise
function turn(fraction) {
return radiansToDegrees(tau * fraction);
}
function degreesToRadians(degrees) {
return degrees * (tau / 360.0);
}
function radiansToDegrees(radians) {
return radians * (360.0 / tau);
}
function circumference(radius) {
return tau * radius;
}
function area(radius) {
return 0.5 * tau * radius * radius;
}
function sin(degrees) {
return Math.sin(degreesToRadians(degrees));
}
function cos(degrees) {
return Math.cos(degreesToRadians(degrees));
}
function tan(degrees) {
return Math.tan(degreesToRadians(degrees));
}
export { tau, turn,
degreesToRadians, radiansToDegrees,
circumference, area, sin, cos, tan };
Finally, we'll look at the main.html
file, which is designed to be
very similar to our main.py
file.
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<!-- for mobile-friendly pages -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name=author content="Scott D. Anderson">
</head>
<body>
<script type="module">
import * as colors from "./modules/colors.js";
import * as trig from "./modules/trig.js";
console.log('tangent of 45 degrees: ' + trig.tan(45));
console.log('some people like the combination of blue, '
+ colors.blue
+ ', and tan, '
+ colors.tan);
</script>
</body>
</html>
Notice that the import
statement specifies *
which means all
exported names. They will be gathered up into a JS Object that is the
value of the name given in the as
clause:
import * as module_name from "path/to/module.js";
Note also the variant version of the script
tag: to use the new
import
syntax, we have to declare the script to be a module
,
hence:
<script type="module">
</script>
The import statements are at the top of this main module:
import * as colors from "./modules/colors.js";
import * as trig from "./modules/trig.js";
The result works just like Python's module system.
Note that I happened to put the two .js
files in a subfolder called
modules
, so the import statement has to use a relative pathname to
locate those files.
Try it! main.html Open up the JavaScript console and
examine the result of the two console.log()
statements.
Exporting One Name¶
The import *
syntax is one of several syntaxes for the import
statement. We won't look at all of them (but see the references at the
end of this reading if you'd like to learn more), but we will look at
one special case. If you are only exporting one name, it's
unnecessary and cumbersome to create an object to contain that one
name.
For example, if our colors
module created and exported a single
global variable that contained an object that allowed us to look up
all the colors, it might look like this:
var colors = {
red: '#ff0000',
green: '#008000',
lime: '#00ff00',
blue: '#0000ff',
tan: '#d2b48c'
};
export { colors };
That representation actually has several advantages over a collection
of global variables, including having a way to iterate over all the
colors, by using Object.keys()
. But if we imported it like we did:
import * as colors from "./modules/colors.js";
our main module would have to look up a color as, for example,
colors.colors.tan
. So, it's simpler and better just to import that one name:
import { colors } from "./modules/colors.js";
I won't repeat all the code, since the main.html
file is the same
except for the one import
statement, but you are welcome to try the
example using colors object.
Since the modules in the Coffeerun example almost always export just a single name, my re-write will use this trick a lot.
App Example¶
The main.html
example is okay, but it's a little boring. Let's load
jQuery and do some interesting things with our app. Try it!
app.html
In addition to some extra console.log
statements, the app
dynamically creates some list items and sets the text and color of
each one, using the keys in the colors
module (the dictionary
exported from that module).
Here's the code:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<!-- for mobile-friendly pages -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name=author content="Scott D. Anderson">
</head>
<body>
<ul id="colors">
</ul>
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<script type="module">
import { colors } from "./modules/colors.js";
import * as trig from "./modules/trig.js";
var App = {};
App.trig = trig;
App.colors = colors;
console.log('loaded these keys from App:',Object.keys(App));
// First, some color stuff
Object.keys(App.colors).forEach( function (key) {
let val = App.colors[key];
console.log(`${key} => ${val}`);
$("<li>").text(key).css('color',val).appendTo('#colors');
});
// Second, some math stuff;
console.log('tangent of 45 degrees',App.trig.tan(45));
console.log('turn 3/4 of a circle',App.trig.turn(3/4));
console.log('circumference of circle w/ radius 10: ',App.trig.circumference(10));
// three tan values
console.log('Math.tan',Math.tan);
console.log('App.trig.tan',App.trig.tan);
console.log('App.colors.tan',App.colors.tan);
</script>
</body>
</html>
I did some "packaging" of the code. I created an object, stored in a
global variable calle App
. Initially, it's empty, but then I store
the two modules in it:
var App = {};
App.trig = trig;
App.colors = colors;
We then print out the things in the App
, using the Object.keys()
method.
console.log('loaded these keys from App:',Object.keys(App));
From then on, names are always referenced as App.module.name
. We've
packaged everything up into a single object called App.
If I needed
or wanted to export this object for someone else to use, my code is
ready. Or, if you wanted to open up a JS Console and try some of the
functions by hand, you could do that.
Debugging¶
Actually, you can't. There's a limitation in JavaScript modules that
makes debugging a bit harder. In the JS console, you might expect to
be able to access the two dictionaries, colors
and trig
, not to
mention App
. Alas, in JavaScript, the separation part of our
goals is too thorough. The walls that enclose our
module are impenetrable. Even our main module is a module and we can't
reach inside it, even using the JS console. I've tried.1
Fortunately, there is a loophole. In JavaScript, there is a global
variable called globalThis
that is super-global. It exists outside
of modules.
It's a JS Object (just like colors
, trig
and App
), and the
actual global variables of our program are, in fact, keys or
properties of that object.
It might be helpful to see an example. The following creates a global
variable in the current module, such as in trig
or colors
:
var myGlobal = 5;
while the following creates an actual global variable in the main module, accessible via the console:
globalThis.myGlobal = 5;
So, one thing that can help with our debugging is to put our various
modules in the global namespace, so we can access them from the
console. We can do that with a simple assignment to a property of
globalThis
. Like so:
globalThis.App = App;
Since I packaged everything up in to App
, that one assignment makes
everything accessible (via App
).
Here's our last version of the app: app-this.html. It's
identical to app.html
except that it adds that assignment to
globalThis.App
so that we can try things. Open app-this.html
in
another browser tab and try some of the following in the JS console:
App.colors.red;
App.trig.sin(90);
App.trig.turn(0.75);
Strict Mode¶
Note that when we import a module, the entire module is treated as being in strict mode. We learned strict mode earlier in chapter 6, though there we did it on a function-by-function basis. There are many facets of strict mode, but the main one for us is that
you must declare a global variable using
var
The goal is to avoid typos that are hard to debug. The following code is perfectly valid in non-strict mode; it just has a bug in it:
var form = '<form><input name="username"><input type="submit">';
function addInput(input_name) {
from = form + '<input type="text" name="' + input_name + '">';
}
Do you see the bug? The first line of the function should be appending
to form
but because of a typo, it appends to from
. Sloppy
JavaScript ("sloppy" is the opposite of "strict") will just create a
new global variable named from
and append to that. Good luck
debugging!!
Strict JavaScript would require the "new" global varible from
to
have been defined with var
. Since it wasn't, it'll give us an
error, and so we can see and fix the typo right away.
Looking Back¶
This has been a long reading, and I'm reluctant to make it longer, but I think it's important to explain the book's code:
- IIFEs represent what was once "best practice", and so you may see it in legacy code on the web, in examples, tutorials and the like.
- Since IIFEs provide many of the desireable features of modules in a simple and lightweight way, they still find use in modern JavaScript applications.
- there are some interesting and important concepts
- you bought the book, you might as well understand it
If those reasons are not sufficiently compelling to you, you can skip the reading on IIFEs, at least for now. You can always return to it later.
Here's the reading on IIFEs
More Info¶
- MDN Guide to Modules
- MDN reference on export
- MDN reference on import
- MDN reference on strict mode
- MDN reference on globalThis
Summary¶
- JavaScript modules are walled namespaces.
- Each has to list the names that it is exporting
- The importing module has to list the names it is importing and the file that it is importing them from
- modules are always in strict mode
- modules are walled off from the global namespace, but
- you can use assignments to
globalThis
to break out of the module
-
if you know of a way, please let me know. ↩