Courses Learn Blog Contact
Get Started →
Lesson 6

Day 6: Modern JavaScript and Modules

Splitting one long file into modules that each do one job, the ES6 syntax you will read daily, and naming that survives a code review.

Webbo3 Level 2: JavaScript Development 16 min read Free

Webbo3 Level 2 · JavaScript Development · Day 6 of 20

Modern JavaScript and Modules

Breaking one enormous file into pieces that each do one job, and the modern features you will read in every codebase you ever open.

By the end of today you can

  • Split a project into modules with export and import
  • Explain why one 800-line file is a problem before it becomes one
  • Use shorthand properties, computed keys, methods and rest without looking them up
  • Name things so the next person does not have to ask you what they mean
  • Reorganise your Day 5 project into files that each do one job

Your Day 5 project probably lives in one file. It works, and at three hundred lines it is still findable. At eight hundred it will not be.

Every team you will ever join solved that problem the same way: many small files, each with one job, that say out loud what they hand out and what they need. Today you learn how.

1. The Problem Modules Solve

Definition

A module is a JavaScript file whose contents are private by default. Nothing inside it can be used by another file unless the module deliberately exports it, and the other file deliberately imports it.

Real-Life Example

A shared office where everyone keeps everything on one big table. Two people both have a folder called "Report", someone moves the wrong one, and nobody can work without bumping into everyone else.

Modules give everyone their own desk with a drawer. You keep your things in your drawer and hand over only what somebody asks for, by name.

The Three Problems of One Big File

  1. Name clashes: two functions called render, written six weeks apart, silently overwrite each other.
  2. Everything touches everything: a variable near the top is visible to code four hundred lines below that has no business changing it.
  3. Nobody can work alongside you: every change by two people to the same file is a conflict.

A module fixes all three at once, because each file only shares what it chooses to.

Picture This

Picture the office with a desk and a drawer for everyone. Your drawer is private. When a colleague needs something, you hand it over by name, and nothing else leaves your desk.

2. export and import

Definition

export marks something in a module as available to other files. import brings it into another file, naming exactly what it needs and which file it comes from.

Real-Life Example

A restaurant kitchen and its menu. The kitchen can make many things, but only what is on the menu can be ordered. export is putting a dish on the menu; import is ordering it by name.

Types of Export

KindWriting itImporting it
Namedexport function createTask() {}import { createTask } from './tasks.js'
Several namedexport { a, b }import { a, b } from './file.js'
Renamed on importexport { a }import { a as first } from './file.js'
Default (one per file)export default function () {}import anything from './file.js'
EverythingAny exportsimport * as tasks from './tasks.js'

Type 1: Named Exports

Date.now() gives the number of milliseconds since 1970, which makes a quick, unique-enough id for a task created by hand.

What this code doesWe want to split the task rules into their own file and use them from another file, importing only what we need by name.
js/tasks.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// tasks.js: one job, the task data and the rules about it

// Available to other files
export const STORAGE_KEY = 'webbo3.tasks';

// Build a new task from the text a user typed
export function createTask(text) {
  return { id: Date.now(), text: text.trim(), done: false };
}

// Keep only the tasks that are not done
export function activeOnly(tasks) {
  return tasks.filter((task) => !task.done);
}

// NOT exported: private to this file
function secretHelper() {
  return 'nobody outside can call me';
}
js/app.js
1
2
3
4
5
6
7
8
9
10
11
// app.js: one job, use the task rules
import { createTask, activeOnly, STORAGE_KEY } from './tasks.js';

// Make two tasks and complete the first
const tasks = [createTask('Read Day 6'), createTask('  Refactor project  ')];
tasks[0].done = true;

// Use what tasks.js handed out
console.log(STORAGE_KEY);
console.log(activeOnly(tasks).length);
console.log(tasks[1].text);
OUTPUT   TERMINAL
➜ webbo3 node js/app.js
webbo3.tasks
1
Refactor project

Notice the import path: ./tasks.js. The dot and slash mean "in the same folder as this file", and the .js ending is required in the browser.

Type 2: Default Export

toLocaleString() writes a number with thousands separators, which the next example uses.

What this code doesWe want to see a default export: one main function per file, imported under a name the importing file chooses.
js/format.js
1
2
3
4
5
// format.js: one job, formatting money
// A default export: this file's one main thing
export default function formatNaira(amount) {
  return `N${amount.toLocaleString()}`;
}
js/app.js
1
2
3
4
// A default import can be given any name
import money from './format.js';

console.log(money(1500000));
OUTPUT   TERMINAL
➜ webbo3 node js/app.js
N1,500,000
Important Rule: Prefer Named Exports

A default export can be imported under any name, so the same function ends up called formatNaira in one file and money in another, and searching the codebase for where it is used stops working. Named exports keep one name everywhere.

Common Mistake: Importing Something That Was Never Exported

What this code doesWe want to see the error a module gives when another file asks for something it never exported.
js/tasks.js
1
2
3
4
5
6
7
8
9
// createTask is exported...
export function createTask(text) {
  return { text, done: false };
}

// ...but activeOnly is not
function activeOnly(tasks) {
  return tasks.filter((task) => !task.done);
}
js/app.js
1
2
3
4
// Ask for both
import { createTask, activeOnly } from './tasks.js';

console.log(activeOnly([createTask('x')]));
OUTPUT   TERMINAL
➜ webbo3 node js/app.js
SyntaxError: The requested module './tasks.js' does not provide an export named 'activeOnly'
Three Things That Stop Modules Working in the Browser

You forgot type="module" on the script tag. You left .js off the import path. Or you opened the file by double-clicking it (a file:// address), where browsers block modules: use VS Code's Live Server, which you installed last month.

What this code doesWe want the browser to load app.js as a module, so its import lines work.
index.html
1
2
<!-- type="module" lets app.js use import and export -->
<script type="module" src="js/app.js"></script>
NOTE   
Only the entry file needs a script tag. The browser follows its imports and loads tasks.js by itself.
Try This

Create a module that exports a function isPassing(score), and use it from another file.

What this code doesPractise: export one function and import it by name.
js/rules.js
1
2
3
4
// The passing rule, in its own file
export function isPassing(score) {
  return score >= 50;
}
js/app.js
1
2
3
4
5
// Use the rule from another file
import { isPassing } from './rules.js';

console.log(isPassing(72));
console.log(isPassing(31));
OUTPUT   TERMINAL
➜ webbo3 node js/app.js
true
false
Picture This

Picture the kitchen and its menu. Everything the kitchen can make stays in the kitchen until it is put on the menu with export. Other files order from the menu by name with import, and cannot walk into the kitchen and take what is not listed.

3. The Modern Syntax You Will Read Every Day

Definition

Since 2015, JavaScript has gained short ways to write things you already know how to do. None of them lets you do anything new; they let you say it with less noise, and every codebase you open uses them.

Real-Life Example

Abbreviations in a message: "ASAP" means the same as "as soon as possible". You need to recognise them to read other people's messages, even if you choose when to use them yourself.

Types

  1. Shorthand properties: write the name once when the key and the variable match.
  2. Computed keys: a property name taken from a variable.
  3. Methods: functions inside an object, without the word function.
  4. Rest: ... gathering several values into one.

Type 1: Shorthand Properties

What this code doesWe want to see that the shorthand builds exactly the same object with half the typing.
app.js
1
2
3
4
5
6
7
8
9
10
11
12
// Two variables
const name = 'Chidinma';
const score = 88;

// Longhand: the key and the variable say the same thing twice
const longhand = { name: name, score: score };

// Shorthand: write each name once
const student = { name, score };

console.log(longhand);
console.log(student);
OUTPUT   TERMINAL
➜ webbo3 node app.js
{ name: 'Chidinma', score: 88 }
{ name: 'Chidinma', score: 88 }

Type 2: Computed Keys

What this code doesWe want to create a property whose name comes from a variable, the way a form builder does.
app.js
1
2
3
4
5
6
7
// The property name is stored in a variable
const field = 'city';

// Square brackets use the variable's VALUE as the key
const record = { [field]: 'Lagos' };

console.log(record);
OUTPUT   TERMINAL
➜ webbo3 node app.js
{ city: 'Lagos' }

Type 3: Methods

A method is a function stored in an object. Inside it, this means "the object this method belongs to", so this.balance reads the object's own balance.

What this code doesWe want an object that carries its own behaviour: an account that can describe itself.
app.js
1
2
3
4
5
6
7
8
9
10
11
12
// An account with a method
const account = {
  balance: 5000,

  // A method: no word "function", no colon
  describe() {
    // this = the account object
    return `Balance: N${this.balance}`;
  }
};

console.log(account.describe());
OUTPUT   TERMINAL
➜ webbo3 node app.js
Balance: N5000

Type 4: Rest

In a parameter list, ...amounts gathers every remaining argument into one array, however many there are.

What this code doesWe want one function that totals any number of amounts, without the caller building an array.
app.js
1
2
3
4
5
6
7
8
9
10
// label is the first argument; ...amounts gathers everything after it
function total(label, ...amounts) {
  const sum = amounts.reduce((running, amount) => running + amount, 0);
  return `${label}: ${sum}`;
}

// Three amounts, one amount, no amounts
console.log(total('Week 1', 1500, 2000, 750));
console.log(total('Week 2', 900));
console.log(total('Week 3'));
OUTPUT   TERMINAL
➜ webbo3 node app.js
Week 1: 4250
Week 2: 900
Week 3: 0

Rest also works when unpacking: take out the properties you want by name and gather the rest.

What this code doesWe want to pull two properties out of a record and keep the remaining ones together.
app.js
1
2
3
4
5
6
7
8
// A student record
const student = { name: 'Musa', score: 54, city: 'Kano', paid: false };

// Take name and score out; gather everything else into other
const { name, score, ...other } = student;

console.log(name, score);
console.log(other);
OUTPUT   TERMINAL
➜ webbo3 node app.js
Musa 54
{ city: 'Kano', paid: false }
Spread and Rest: Same Three Dots

In a parameter list, or on the left of =, ... gathers many things into one (rest). Anywhere else it spreads one thing into many (Day 3). Where it sits decides which.

Overall Example

What this code doesWe want every piece of modern syntax from this section working together in one small function.
app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Build a receipt object using every shorthand from this section
function makeReceipt(customer, ...prices) {
  const total = prices.reduce((sum, price) => sum + price, 0);
  const field = 'items';

  return {
    customer,                 // shorthand property
    [field]: prices.length,   // computed key
    total,
    describe() {              // method
      return `${this.customer} bought ${this.items} items for N${this.total}`;
    }
  };
}

console.log(makeReceipt('Amaka', 1500, 700, 2300).describe());
OUTPUT   TERMINAL
➜ webbo3 node app.js
Amaka bought 3 items for N4500
Try This

Write a function average(...scores) that works for any number of scores.

What this code doesPractise: use rest to accept any number of values.
app.js
1
2
3
4
5
6
7
8
// Gather every score into one array
function average(...scores) {
  const total = scores.reduce((sum, score) => sum + score, 0);
  return total / scores.length;
}

console.log(average(70, 80));
console.log(average(50, 60, 70, 80));
OUTPUT   TERMINAL
➜ webbo3 node app.js
75
65
Picture This

Picture shorthand as a signature instead of your full name: same person, less ink. Rest is a bag at the end of the queue that takes everyone who did not get a named seat.

4. Code That Reads Like Sentences

Definition

Naming is choosing names for variables, functions and files that say what they hold or what they do, so code can be understood without its author in the room.

Real-Life Example

Two freezers. One has boxes labelled "stuff", "misc" and "box 2". The other has "chicken", "fish" and "stew, Sunday". Both hold the same food. Only one lets somebody else cook dinner.

Types of Bad Name, and Their Fixes

Instead ofWriteBecause
d, x, tempdaysLeft, student, draftNobody can search for x
datastudents, ordersdata describes nothing
check(u)isEligible(user)A yes-or-no function should read as a question
handleClick2deleteTaskName what it does, not when you wrote it
flaghasPaidA boolean should read as a yes or no
getStuff()fetchStudents()"get" suggests it is already here; "fetch" that it is coming from somewhere
What this code doesWe want to compare a badly named function with a well named one that does exactly the same job.
app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// The same rule, written twice
function c(s, a) {
  return s >= 50 && a >= 0.75;
}

function isEligibleForCertificate(averageScore, attendanceRate) {
  // Name each part of the rule
  const passedExams = averageScore >= 50;
  const attendedEnough = attendanceRate >= 0.75;
  return passedExams && attendedEnough;
}

// Both give the same answer
console.log(c(80, 0.9), isEligibleForCertificate(80, 0.9));
OUTPUT   TERMINAL
➜ webbo3 node app.js
true true

Both return true. Only one of them can be read by a colleague at 6pm on a Friday without asking you what a means.

Picture This

Picture the labelled freezer. A good name is a label that lets someone who has never met you find exactly what they need, first time.

5. One Job Per File

Definition

The test for whether a file should be split is not its length. It is this: can you describe what the file does in one sentence, without the word "and"? If you need "and", it is two files.

Real-Life Example

In a kitchen, the chef cooks and the cashier takes money. If one person does both, orders slow down and money goes missing. Each job gets its own person, and they pass things between them.

What this code doesWe want a folder layout where every file passes the one-sentence test.
folder structure
1
2
3
4
5
6
7
8
9
project/
  index.html
  css/
    style.css
  js/
    app.js       <- wires everything together, and nothing else
    tasks.js     <- what a task is, and the rules about tasks
    render.js    <- turning tasks into HTML
    storage.js   <- saving and loading (Day 11)
NOTE   
This is not code to run. It is the layout to aim for when you split your Day 5 project.
Do This Now

Refactor your Day 5 project into at least three modules along those lines. It must still work exactly as it did. Commit before you start, so you can get back if you break it: that habit is worth more than the refactor.

It Broke the Moment I Split It

Almost always one of three things. A function you moved used a variable that stayed behind, so it now needs that value passed in as a parameter. Or you forgot to export it. Or the import path is wrong: ./tasks.js from inside js/, not js/tasks.js. Open the Console: all three produce a specific error that names the thing it could not find. Read it before you change lines at random.

Checkpoint

Q1. Why are named exports preferred over a default export?

A default export can be imported under any name, so the same thing gets different names in different files and searching for its usages stops working.

Q2. import { createTask } from './tasks' fails in the browser. Why?

Browsers require the file extension: ./tasks.js. Bundlers let you leave it off, which is why the habit spreads.

Q3. What is the one-sentence test for splitting a file?

Describe what the file does in one sentence. If you need the word "and", it is doing more than one job and should be two files.

Q4. In function f(first, ...rest), what is rest when you call f(1, 2, 3)?

An array of everything after the first argument: [2, 3].

What this code doesCheck your answer: call it and print both parameters.
app.js
1
2
3
4
5
6
// Gather the arguments after the first
function f(first, ...rest) {
  console.log(first, rest);
}

f(1, 2, 3);
OUTPUT   TERMINAL
➜ webbo3 node app.js
1 [ 2, 3 ]

Tick Before You Move On

  • ☐ My Day 5 project is split into three or more modules and still works
  • ☐ Every script tag that loads a module carries type="module"
  • ☐ Every import path ends in .js
  • ☐ I used shorthand properties and rest at least once
  • ☐ I renamed at least three badly named things while I was in there
  • ☐ I committed before the refactor and again after it

Quick Recap

A module is private by default; only what you export escapes it. type="module", real file extensions and a local server make modules work in the browser. Prefer named exports, so names stay the same and searchable everywhere. Shorthand, computed keys and methods say familiar things with less noise. Three dots gather in a parameter list and spread everywhere else. One job per file, and the test is whether you need the word "and".

Final Scenario

A new developer joins your team and is asked to change how money is displayed. In the one-file version they would scroll through 800 lines hunting for it.

In the modular version they open the one file whose name says it formats money, change one function, and every file that imports it is updated at once.

What this code doesWe want three small files, each with one job, working together as one program.
js/format.js
1
2
3
4
// One job: how money looks
export function formatNaira(amount) {
  return `N${amount.toLocaleString()}`;
}
js/tasks.js
1
2
3
4
// One job: the rules about orders
export function orderTotal(prices) {
  return prices.reduce((sum, price) => sum + price, 0);
}
js/app.js
1
2
3
4
5
6
// One job: wire the pieces together
import { formatNaira } from './format.js';
import { orderTotal } from './tasks.js';

const total = orderTotal([12500, 45000, 8700]);
console.log(`Takings: ${formatNaira(total)}`);
OUTPUT   TERMINAL
➜ webbo3 node js/app.js
Takings: N66,200

Small files with clear names, sharing only what they must: that is what makes a codebase something a team can live in.

Tomorrow, Day 7: asynchronous JavaScript. What your code does while it waits for something slow, and why that changes how it is written.

Learn this with a real instructor

Free lessons take you a long way on your own. The full programmes add live classes, assignments, Skill Points and a certificate — built for African students.