Introduction to JavaScript modules

Time Box

ActivityTime
Reading0.5 hours
Exercise0.5 hours

So, we've been writing JavaScript programs, but up till now most of the JavaScript programs have been quite small, which means the context is small.

The amount of information we have to keep in our heads at any given time to kind of have an understanding of the program has been small.

In the real world, or in the world where people are paying us money to write programs, or in the world where we're trying to affect the world with programs, important programs aren't necessarily small.

So the skill of writing and working with large programs is important. A big part of that skill is what we call modules.

There's an adjective to it as well, modularity is a really important idea, and modularity is the idea of something being able to be broken up or composed of a lot of different parts, and for each of those parts to be kind of self-contained and to make sense on their own.

The Lego Brick: A metaphor for modularity

I think the poster child for modularity is Lego bricks.

When you are building a Lego castle or a Lego car or whatever you're building, you use hundreds of lego bricks.

Each Lego brick is quite simple by themselves, but the interesting part about Lego bricks is that they have these two features that make them stick together.

They have the "stud" which is the knobs that stick out and the "anti-stud" which is the socket that the stud fits perfectly into.

By plugging one brick into another, you're taking two strong things and making one strong thing that is the "exact" shape that you want. Then we can take that new shape and add another brick to it, getting more complex shapes. So this is how we can make a strong castle, house or whatever without nails or cement or glue.

So by taking one simple design, we can use thousands of those to build a much grander design than maybe we would be able to make individually.

When we're making bigger programs we want to follow this pattern.

We're going to write small portions of the program that are high quality by themselves, and then connect them all together to make one high quality program that does what we want. We can even use pieces of code that other people have written.

Modularity to reduce cognitive load

So for now, what the programs that we have been writing have been kind of all in one part, all in one file. Every part of our program is right next to every other part of our program, and if we want to think about any part of our program, we kind of have to think about all the parts of our program at once.

This is a bad fit for the human brain. At least for most of us.

So we try to make parts of our program separate from the rest, so that when we're thinking about them we can think about them in isolation. The ways we've dealt with this so far have been to take parts of code and wrap them in a function.

This means that we can think of the code from two sides:

  • the interface: how the code operates from the point of view of the person calling the function
  • the implementation: how the code operates from the point of view of the person authoring the function

This is similar to the difference between how we think about a car as either:

  • The person driving the car
  • The engineer designing the car

Each of these people are thinking about the same car but at different times and in very different terms.

Splitting our code up into separate files

So, a function can create modularity, but the code is still all together and it can be hard to build a readable program with just that strategy. What we need to be able to do is split our code up into separate files.

Javascript provides a way to connect files with the keywords import and export. These are the stud and anti-stud from the lego analogy. They are what allows modules to use things from other modules.

So we can have a look at the syntax of importing-exporting, so let's imagine that we have two files. One of them is all about encoding a string, and one of them is all about encoding and that's called encode.js.

// encode.js
export function encode(plaintext) {
let codedText = ''
for (let i = 0; i < plaintext.length; i++) {
let cc = plaintext.charCodeAt(i) + 9 // they'll never figure this out
codedText += String.fromCharCode(cc)
}
return codedText
}

And then we're going to have a file that is kind of like the driver or the main module. So this module is going to be thinking at the level of user events and things like that, whereas the other module is going to be thinking in terms of strings.

// main.js
import { encode } from './encode.js'
const input = document.getElementById('PlainTextArea')
const output = document.getElementById('CodedText')
input.addEventListener('change', (event) => {
const plaintext = input.value
const codedText = encode(plaintext)
output.textContent = codedText
})

So we're going to have a function coming out of our encode module called encode. And then our main module is going to take the function from that module and use them to set up kind of a UI.

This isn't exactly the split we'll always have.

To put this all together we'll need an html file

<!-- index.html -->
<textarea id="PlainTextArea"> </textarea>
<div id="CodedText"></div>
<script src="./main.js" type="module"></script>

Note that it is important that instead of type="text/javascript" we use type="module". Import and export are "modern" features (They're about 8 years old), so we need to opt-in to them with this attribute.

Exercises

  1. Create a folder with index.html, main.js and encode.js files. Recreate this example and view it in your browser by running npx vite in your new folder
  2. (stretch) Add another module to decode strings