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.
- Motivation
- Namespaces
- Namespaces and Dictionaries
- Module Metaphor
- Modules in Python
- Modules in JavaScript
- Modern Modules
- Module Example
- Exporting One Name
- App Example
- Debugging
- Strict Mode
- More Info
- Summary
- Appendix 1: IIFEs — Immediately Invoked Function Expressions
- Appendix 2: Namespaces and Functions
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, 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
Murphy's Law says that the one you need won't win, and sometimes
you'll need 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) { }
};
How would you use such a namespace? Let's use objects to solve our color versus trig dilemma:
var colors = {
'red': '#ff0000',
'blue': '#0000ff',
'tan': '#d2b48c'
}
var trig = {
'cos': function (theta) { ... },
'sin': function (theta) { ... },
'tan': function (theta) { ... }
}
function foo() {
console.log('my favorite color is ', colors.tan);
console.log('the tangent of my favorite angle is ', trig.tan(Math.pi/4));
}
Of course, the code above is all in the same file and we can't have names that are shared inside the module but not available outside the module (for "helper" functions and such), so we don't yet have a module system, but we're getting closer.
Note that many developers in the JavaScript community use the word namespace narrowly to refer to objects being used this way and they don't use the word more broadly. I'm not going to be pedantic about terminology, but it's useful to know, and I'll mention this a few other places.
We will return to this idea a bit later.
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 and 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. The main module, like the trig
and colors
modules,
can also have global variables, constants and functions defined.
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 or use multiple dimensions. I won't try to draw 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 CS 111 or similar, without really noticing. (For
example, in CS 111 you may have 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.)
This code below imports the Python math
module, which provides a
value for pi and some built-in trig functions.
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 IIFEs which were used in JavaScript before the module system and still are. But IIFEs are hard to work with and hard to understand, so I've decided to avoid those this semester. Still, IIFEs are still used for lightweight modules in modern JavaScript, so you may run into them outside this course. There's information in that companion reading if you're curious.
There are also module systems that were invented for server-side
JavaScript using node.js
. We won't discuss those at all.
Eventually, about 2016, 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 namespace object. Since importing all as a namespace object 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. The code is all pretty
familiar, but now we have to list all the names we are exporting. The
addition of the export
statement at the end allows names from this
file to be imported into another module and gathered up into a
dictionary-like object.
var red = '#ff0000';
var green = '#008000';
var lime = '#00ff00';
var blue = '#0000ff';
var tan = '#d2b48c';
export { red, green, lime, blue, tan };
Otherwise, this looks just like our regular JavaScript files. So the module system is not a big change in our coding.
Next, let's look at the trig.js
file. Again, this is pretty
straightforward, except for the list of names that we are
exporting. Note that you are not required to export every name; we
could omit tau
, and then it would not be possible for the importing
code to access trig.tau
; tau
would be a name that is private to
our module. (That's one thing we couldn't do with the dictionary
approach above.)
// 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. In order to load a .js
file as a
module, we add an import
statement to a <script type="module">
element. And the imported module has to have an export
statement
that will be matched up with the import
statement.
<!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 module name given in the as
clause:
import * as module_name from "path/to/module.js";
This is the object as namespace idea that is common in the JavaScript world.
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! The code is in main.html. Open up the
JavaScript console and examine the result of the two console.log()
statements. This code uses slightly different syntax for the import
statement, described in the next section.
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, we 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
namespace/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 called 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.
Debugging¶
For experimenting or debugging, you might want to open up a JS Console and try some of the functions by hand. We have done that a lot in this course, and it's a great idea. Unfortunately, you can't. There's a limitation in JavaScript modules that makes debugging a bit harder. (This annoys me a lot, but they didn't ask me.)
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 or namespace. Thus, it's just like colors
,
trig
and App
. 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 fred = 5;
while the following creates an actual global variable in the main module, accessible via the console:
globalThis.fred = 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. There are many facets of strict mode, but the main one for us is that
you must declare a global variable using
var
orlet
orconst
Any reference to a global name that wasn't properly declared will give an error.
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) {
forn = 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 forn
. JavaScript in
"sloppy" mode ("sloppy" is the opposite of "strict") will just create
a new global variable named forn
and append to that. Good luck
debugging!!
Strict JavaScript would require the "new" global variable forn
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.
Don't get too excited by strict mode. It saves us from a few kinds of errors but not every kind of error. Nevertheless, it's worth doing.
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
One example of exporting:
var red = '#ff0000';
var green = '#008000';
var lime = '#00ff00';
var blue = '#0000ff';
var tan = '#d2b48c';
export { red, green, lime, blue, tan };
another example, exporting a single dictionary:
var colors = {
red: '#ff0000',
green: '#008000',
lime: '#00ff00',
blue: '#0000ff',
tan: '#d2b48c'
};
export { colors };
Either way, notice the export
at the end of the file.
Here are the two corresponding examples of importing:
<script type="module">
import * as colors from "./modules/colors.js";
import * as trig from "./modules/trig.js";
// store the other modules in the App
App.trig = trig;
App.colors = colors;
...
console.log('App.colors.tan',App.colors.tan);
</script>
Or, where the color values are already in a dictionary:
<script type="module">
import { colors } from "./modules/colors.js";
import * as trig from "./modules/trig.js";
// store the other modules in the App
App.trig = trig;
App.colors = colors;
...
console.log('App.colors.tan',App.colors.tan);
</script>
Appendices¶
This has been a long reading, and I'm reluctant to make it longer, but I think it's important to mention a few other ideas.
Appendix 1: IIFEs — Immediately Invoked Function Expressions¶
- 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
If those reasons are not sufficiently compelling to you, you can skip the reading on IIFEs, at least for now.
Here's the reading on IIFEs
Appendix 2: 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 a function form a namespace, in the sense of a mapping from names to values. 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.
Note that some JavaScript developers would not use the word namespace for the mapping from names to values within a function. They might instead call it an execution context. With some minor differences, it's still a mapping from names to values. Po-ta-to, Po-tah-to.
Function execution contexts is how we can use IIFEs for namespaces, so that shows that the distinction may not be very helpful.
-
if you know of a way, please let me know. ↩