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 ES6 features you will read in every codebase you ever open.
By the end of today you can
- Split a project into modules with
exportandimport - Explain why one 800-line file is a problem before it becomes one
- Use default parameters, spread, rest and shorthand 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 folders that make sense
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, and every team you 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.
1. The problem modules solve
One file means everything shares one namespace. Two functions called render, written six weeks apart, silently overwrite each other. A variable called total near the top is visible to code four hundred lines below that has no business touching it. And nobody can work on the file at the same time as you without a merge conflict on every save.
A module fixes all three at once. Everything inside a module file is private by default. The only things another file can reach are the ones you deliberately export.
2. export and import
// tasks.js - one job: the task data and the rules about it
export const STORAGE_KEY = 'webbo3.tasks';
export function createTask(text) {
return { id: Date.now(), text: text.trim(), done: false };
}
export function activeOnly(tasks) {
return tasks.filter((task) => !task.done);
}// app.js - one job: wire the page to the data
import { createTask, activeOnly } from './tasks.js';
const tasks = [createTask('Read Day 6'), createTask('Refactor project')];
tasks[0].done = true;
console.log(activeOnly(tasks).length);<!-- type="module" is what makes import work in a browser -->
<script type="module" src="js/app.js"></script>Three things that stop modules working, in order of likelihood
You forgot type="module" on the script tag. You left the .js off the end of the import path, which browsers require even though bundlers do not. Or you opened the file with file:// by double-clicking it: modules are blocked on that protocol, so you need a local server. VS Code's Live Server, which you installed last month, is a local server.
Named and default exports
| Kind | Writing it | Importing it |
|---|---|---|
| Named | export function a() {} | `import { a } from './file.js' |
| Several named | export { a, b } | `import { a, b } from './file.js' |
| Renamed | export { a } | `import { a as first } from './file.js' |
| Default, one per file | export default class {} | `import Anything from './file.js' |
| Everything | export ... | `import * as tasks from './file.js' |
Prefer named exports. A default export can be imported under any name at all, so the same thing ends up called Task in one file and t in another, and searching the codebase for where it is used stops working.
3. The ES6 you will read every day
// shorthand: when the key and the variable share a name
const name = 'Chidinma';
const score = 88;
const student = { name, score };
console.log(student);
// computed keys
const field = 'city';
const record = { [field]: 'Lagos' };
console.log(record);
// methods, without the word function
const account = {
balance: 5000,
describe() { return `Balance: N${this.balance}`; }
};
console.log(account.describe());Rest: gather what is left
function total(label, ...amounts) { // ...amounts collects the rest
return `${label}: ${amounts.reduce((sum, n) => sum + n, 0)}`;
}
console.log(total('Week 1', 1500, 2000, 750));
console.log(total('Week 2', 900));
console.log(total('Week 3'));const student = { name: 'Musa', score: 54, city: 'Kano', paid: false };
// pull two out, gather the rest into `other`
const { name, score, ...other } = student;
console.log(name, score);
console.log(other);Spread and rest are the same three dots, read in opposite directions
In a parameter list or on the left of an =, ... gathers many things into one. Anywhere else, it spreads one thing into many. Same symbol, and which one it means is decided entirely by where it sits.
4. Code that reads like sentences
This is the part of today that will still matter in ten years. The syntax above is learnable in an afternoon; naming is a skill you build over a career, and Week 4 marks you on it.
| Instead of | Write | Because |
|---|---|---|
d, x, temp | daysLeft, student, draft | Nobody can grep for x |
data | students, orders, response | data describes nothing |
check(u) | isEligible(user) | A function returning true or false should read as a question |
handleClick2 | deleteTask | Name what it does, not when you wrote it |
flag | hasPaid | A boolean should read as a yes or no |
getStuff() | fetchStudents() | get means "already here", fetch means "going to get it" |
// the same rule, written twice
function c(s, a) {
return s >= 50 && a >= 0.75;
}
function isEligibleForCertificate(averageScore, attendanceRate) {
const passedExams = averageScore >= 50;
const attendedEnough = attendanceRate >= 0.75;
return passedExams && attendedEnough;
}
console.log(c(80, 0.9), isEligibleForCertificate(80, 0.9));Both return true. Only one of them can be read by a colleague at 6pm on a Friday without asking you what a is.
5. One job per file
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.
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
storage.js # saving and loading, Day 11
render.js # turning tasks into HTMLDo 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. What went wrong?
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 clear, specific error message that names the thing it could not find. Read it before you start changing 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. A named export has to be imported by its real name.
Q2. `import { createTask } from './tasks'` fails in the browser. Why?
Browsers require the file extension. It must be './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 cannot do it without the word "and", it is doing more than one job and should be two files.
Tick before you move on
- ☐ My Day 5 project is split into three or more modules and still works
- ☐ Every script tag that imports carries
type="module" - ☐ 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 · Named exports over default exports, so names stay searchable · 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"
Tomorrow, Day 7: waiting. What your code does while it is waiting for something slow, and why that changes how it is written.