Skip to content
Theory · A TypeScript project · lesson 1 of 10

Modules

What you'll learn

What you'll learn:

  • split a program into modules with export and import, and tell a module from a script;
  • explain what is left in JavaScript of import type and of an ordinary import;
  • configure the compiler for the whole project with a tsconfig.json file;
  • add types to JavaScript libraries: .d.ts files and @types packages.
A two-room house of modules joined by an export and import doorway, with a tsconfig rule board on the wall

Picture a house. While it has one room, everything lies in plain sight, and you can take anything. When there are many things, the house is split into rooms. The kitchen has its dishes, the study has its books, and from the kitchen you cannot see the books. If a book is needed in the kitchen, it is brought to the study door, and the kitchen takes it through that door.

A program grows the same way. At first all the code fits in one file, then it is split into several. In TypeScript, as in modern JavaScript, the rooms are module files and the doors are the words export and import.

But the house also has a shared hallway that everyone sees. Let's see what happens to a thing left there. Here is a file hello.ts of two lines:

TypeScript
const name = "Аня"; // ✗
console.log(name);
TEXT
error TS2451: Cannot redeclare block-scoped variable 'name'.

Let's take apart what tsc said:

  1. The message means "you cannot declare the variable name a second time", although the file declares it once.
  2. tsc prints the same message for the file lib.dom.d.ts too. In it tsc describes everything the browser has, and the name name is already declared there: the browser window's name.
  3. So our name ended up where the browser's one lives: in the shared hallway.

Script and module

TypeScript sorts files into two kinds by one sign: whether the file has an import or an export.

Script

A file with no import and no export is a script. Its top-level names, that is, the ones declared outside functions and blocks, are global. They are shared by all the scripts of the program, and the browser's own names are already among them.

By default tsc assumes the program may run in a browser and knows its global names: name, status, length, top and others. That is why const name in a script clashes with the browser's name.

In the house picture, a script leaves its things in the shared hallway. Every resident sees them, and there cannot be two things with the same tag there.

Module

A file with at least one import or export is a module. Its top-level names belong to it alone, and from outside you see only what it exports. A variable name inside a module does not get in the way of the browser's one. In the same way, a book in the study does not get in the way of a book in the hallway.

export and import

Let's split a program into two files. format.ts holds a function that shows a price with two digits after the point:

TypeScript
export function formatPrice(price: number): string {
    return price.toFixed(2);
}

function round(price: number): number {
    return Math.round(price);
}

What matters here:

  • export before function puts the function at the door: other files will be able to take it.
  • round has no export, so this function is visible only inside format.ts.
  • Because of export the file became a module, and its names do not go into the shared hallway.

The file main.ts takes the function with the line import { formatPrice } from "./format.js";. Let's take it apart:

  1. import means "take from another module".
  2. The braces hold the names you need. There may be several, separated by commas.
  3. After from comes the path to the module, and ./ means "in the same folder".
  4. The path ends in .js, not .ts: that is how it is written in TypeScript, and tsc finds format.ts next to it on its own.

After this line main.ts calls formatPrice as its own function. Let the second line of the file be console.log(formatPrice(100));. Let's compile and run it:

TEXT
$ tsc main.ts
$ node main.js
100.00

tsc follows the imports on its own. When it sees the import from ./format.js, it checks format.ts too, so both files appear next to the sources: main.js and format.js.

Types are exported the same way as functions: export interface User { … } or export type Id = number;. They are taken with the same import line.

Here the door picture stops working. A book carried out of the study is no longer in the study. An exported function stays in its module: main.ts gets access to the very same function rather than taking it away.

export {}

Let's go back to hello.ts. One line is enough to make it a module:

TypeScript
export {};

const name = "Аня";
console.log(name); // Аня

export {} exports nothing: the braces are empty. But the file has the word export, so it is a module, and name in it is its own rather than global.

In a real project almost every file has an import or an export, and this line is not needed. It helps in a small file for experiments that has neither.

Interviewers sometimes ask why a variable in one file clashes with a variable in another file or in the browser. The answer: a file with no import and no export is a script, and its names are global. The line export {} makes it a module.

Common mistake

Importing a function the module does not export. In format.ts, round has no export, while main.ts tries to take it:

TypeScript
import { round } from "./format.js"; // ✗

console.log(round(99.6));
TEXT
error TS2459: Module '"./format.js"' declares 'round' locally, but it is not exported.

tsc sees the function round in format.ts, but it is declared only for its own module: no door leads to it. Add export before function round, and the import works.

Key points

  • A file with import or export is a module: its names are its own, and from outside you see only what it exports.
  • A file with neither is a script: its names are global and clash with the browser's names, like name with error TS2451.
  • export puts a function or a type outside, and import { … } from "./file.js" takes them in another file.
  • The line export {} turns a script into a module without exporting anything.