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

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:

main module and two additional modules, trig and colors
A main module and two additional modules, trig and colors

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 or let or const

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

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.


  1. if you know of a way, please let me know.