What Is a Module?

A module is a collection of related data and functions (often referred to as methods in this context), characterized by a division between hidden private details and public accessible details, usually called the “public API.”

A module is also stateful: it maintains some information over time, along with functionality to access and update that information.

NOTE:
A broader concern of the module pattern is fully embracing system-level modularization through loose-coupling and other program architecture techniques. That’s a complex topic well beyond the bounds of our discussion, but is worth further study beyond this book.

To get a better sense of what a module is, let’s compare some module characteristics to useful code patterns that aren’t quite modules.

Namespaces (Stateless Grouping)

If you group a set of related functions together, without data, then you don’t really have the expected encapsulation a module implies. The better term for this grouping of stateless functions is a namespace:

  1. // namespace, not module
  2. var Utils = {
  3. cancelEvt(evt) {
  4. evt.preventDefault();
  5. evt.stopPropagation();
  6. evt.stopImmediatePropagation();
  7. },
  8. wait(ms) {
  9. return new Promise(function c(res){
  10. setTimeout(res,ms);
  11. });
  12. },
  13. isValidEmail(email) {
  14. return /[^@]+@[^@.]+\.[^@.]+/.test(email);
  15. }
  16. };

Utils here is a useful collection of utilities, yet they’re all state-independent functions. Gathering functionality together is generally good practice, but that doesn’t make this a module. Rather, we’ve defined a Utils namespace and organized the functions under it.

Data Structures (Stateful Grouping)

Even if you bundle data and stateful functions together, if you’re not limiting the visibility of any of it, then you’re stopping short of the POLE aspect of encapsulation; it’s not particularly helpful to label that a module.

Consider:

  1. // data structure, not module
  2. var Student = {
  3. records: [
  4. { id: 14, name: "Kyle", grade: 86 },
  5. { id: 73, name: "Suzy", grade: 87 },
  6. { id: 112, name: "Frank", grade: 75 },
  7. { id: 6, name: "Sarah", grade: 91 }
  8. ],
  9. getName(studentID) {
  10. var student = this.records.find(
  11. student => student.id == studentID
  12. );
  13. return student.name;
  14. }
  15. };
  16. Student.getName(73);
  17. // Suzy

Since records is publicly accessible data, not hidden behind a public API, Student here isn’t really a module.

Student does have the data-and-functionality aspect of encapsulation, but not the visibility-control aspect. It’s best to label this an instance of a data structure.

Modules (Stateful Access Control)

To embody the full spirit of the module pattern, we not only need grouping and state, but also access control through visibility (private vs. public).

Let’s turn Student from the previous section into a module. We’ll start with a form I call the “classic module,” which was originally referred to as the “revealing module” when it first emerged in the early 2000s. Consider:

  1. var Student = (function defineStudent(){
  2. var records = [
  3. { id: 14, name: "Kyle", grade: 86 },
  4. { id: 73, name: "Suzy", grade: 87 },
  5. { id: 112, name: "Frank", grade: 75 },
  6. { id: 6, name: "Sarah", grade: 91 }
  7. ];
  8. var publicAPI = {
  9. getName
  10. };
  11. return publicAPI;
  12. // ************************
  13. function getName(studentID) {
  14. var student = records.find(
  15. student => student.id == studentID
  16. );
  17. return student.name;
  18. }
  19. })();
  20. Student.getName(73); // Suzy

Student is now an instance of a module. It features a public API with a single method: getName(..). This method is able to access the private hidden records data.

WARNING:
I should point out that the explicit student data being hard-coded into this module definition is just for our illustration purposes. A typical module in your program will receive this data from an outside source, typically loaded from databases, JSON data files, Ajax calls, etc. The data is then injected into the module instance typically through method(s) on the module’s public API.

How does the classic module format work?

Notice that the instance of the module is created by the defineStudent() IIFE being executed. This IIFE returns an object (named publicAPI) that has a property on it referencing the inner getName(..) function.

Naming the object publicAPI is stylistic preference on my part. The object can be named whatever you like (JS doesn’t care), or you can just return an object directly without assigning it to any internal named variable. More on this choice in Appendix A.

From the outside, Student.getName(..) invokes this exposed inner function, which maintains access to the inner records variable via closure.

You don’t have to return an object with a function as one of its properties. You could just return a function directly, in place of the object. That still satisfies all the core bits of a classic module.

By virtue of how lexical scope works, defining variables and functions inside your outer module definition function makes everything by default private. Only properties added to the public API object returned from the function will be exported for external public use.

The use of an IIFE implies that our program only ever needs a single central instance of the module, commonly referred to as a “singleton.” Indeed, this specific example is simple enough that there’s no obvious reason we’d need anything more than just one instance of the Student module.

Module Factory (Multiple Instances)

But if we did want to define a module that supported multiple instances in our program, we can slightly tweak the code:

  1. // factory function, not singleton IIFE
  2. function defineStudent() {
  3. var records = [
  4. { id: 14, name: "Kyle", grade: 86 },
  5. { id: 73, name: "Suzy", grade: 87 },
  6. { id: 112, name: "Frank", grade: 75 },
  7. { id: 6, name: "Sarah", grade: 91 }
  8. ];
  9. var publicAPI = {
  10. getName
  11. };
  12. return publicAPI;
  13. // ************************
  14. function getName(studentID) {
  15. var student = records.find(
  16. student => student.id == studentID
  17. );
  18. return student.name;
  19. }
  20. }
  21. var fullTime = defineStudent();
  22. fullTime.getName(73); // Suzy

Rather than specifying defineStudent() as an IIFE, we just define it as a normal standalone function, which is commonly referred to in this context as a “module factory” function.

We then call the module factory, producing an instance of the module that we label fullTime. This module instance implies a new instance of the inner scope, and thus a new closure that getName(..) holds over records. fullTime.getName(..) now invokes the method on that specific instance.

Classic Module Definition

So to clarify what makes something a classic module:

  • There must be an outer scope, typically from a module factory function running at least once.

  • The module’s inner scope must have at least one piece of hidden information that represents state for the module.

  • The module must return on its public API a reference to at least one function that has closure over the hidden module state (so that this state is actually preserved).

You’ll likely run across other variations on this classic module approach, which we’ll look at in more detail in Appendix A.