JavaScript Advanced: Closures, Async and Modules
Modules and Modern Tooling
In this lesson you will
- Split code into ES modules with import and export
- Explain the difference between named and default exports
- Describe what npm, bundlers and linters are for
Introduction
One enormous file is hard to read and hard to share. Real projects split code into many modules, each with one job. A module can export what it wants to share, and other files import it. You get organisation, reuse, and no name clashes.
The code in this lesson spans several files, so most examples are shown rather than run.
The simple explanation
A module is a file that keeps its own variables private and shares only what it exports.
// math.js
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
function secretHelper() {
// not exported, so other files cannot use it
}// app.js
import { add, PI } from "./math.js";
console.log(add(2, 3));
console.log(PI);Using modules in a page
Add type="module" to the script tag.
HTML
<script type="module" src="app.js"></script>Module scripts always run in strict mode, wait until the page is parsed, and have their own scope, so their variables never leak to the global scope.
Quick quiz
What must you add to a script tag to use import and export?
Named and default exports
- A file can have many named exports. You import them by name inside braces.
- A file can have one default export. You import it with any name you like, without braces.
// greet.js
export default function greet(name) {
return `Hello, ${name}!`;
}
export const version = "1.0";// app.js
import greet, { version } from "./greet.js";
import * as math from "./math.js";
console.log(greet("Sam"), version);
console.log(math.add(1, 2));Many teams prefer named exports because the names stay consistent across the codebase.
Dynamic import
import() loads a module only when you need it and returns a promise. It is great for splitting a large app into pieces that load on demand.
button.addEventListener("click", async () => {
const { drawChart } = await import("./chart.js");
drawChart();
});The same idea, without files
Before modules existed, people used closures to get the same effect. You can run this version here:
The function runs immediately (an IIFE). count is private, and only increment is exported through the returned object.
The tools of a real project
- npm is a package manager. It downloads libraries from a huge public registry and records them in
package.json. - A bundler (such as Vite or webpack) combines your modules and their dependencies into a few optimised files for the browser.
- A linter (such as ESLint) points out likely mistakes and style problems as you type.
- A formatter (such as Prettier) lays out your code consistently, so nobody argues about spaces.
- Version control (Git) records every change, so you can undo mistakes and collaborate.
You do not need all of these to learn JavaScript, but you will meet them in almost every job.
Quick quiz
How many default exports can a module have?
Try it yourself
Rewrite the module pattern above as a createGreeter(greeting) function that returns an object with a greet(name) method, keeping greeting private. Create two greeters with different greetings.
Summary
- Modules split code into files.
exportshares, andimportuses. - Use
type="module"in the browser. Named exports can be many, default only one. import()loads code on demand.- npm, bundlers, linters and formatters are the standard tools around modern JavaScript projects.
Lesson complete
+20 XP
Nice work. Keep going while it is fresh.
Sources and further reading