Модули
Чему вы научитесь:
- делить программу на модули через
exportиimportи отличать модуль от скрипта; - объяснять, что остаётся в JavaScript от
import typeи от обычного импорта; - настраивать компилятор для всего проекта через файл
tsconfig.json; - подключать типы JavaScript-библиотек: файлы
.d.tsи пакеты@types.

Представьте дом. Пока в нём одна комната, все вещи лежат на виду, и любую можно взять. Когда вещей становится много, дом делят на комнаты. На кухне своя посуда, в кабинете свои книги, и из кухни книг не видно. Если книга нужна на кухне, её выносят к двери кабинета, и кухня забирает её через эту дверь.
Программа растёт так же. Сначала весь код помещается в один файл, потом его делят на несколько. В TypeScript, как и в современном JavaScript, комнаты — это файлы-модули, а двери — слова export и import.
Но в доме есть и общий коридор, который видят все. Посмотрим, что случится с вещью, оставленной там. Вот файл hello.ts из двух строк:
const name = "Аня"; // ✗
console.log(name);error TS2451: Cannot redeclare block-scoped variable 'name'.Давайте разберём, что сказал tsc:
- Сообщение означает «нельзя второй раз объявить переменную
name», хотя в файле она объявлена один раз. - Это же сообщение
tscпечатает и для файлаlib.dom.d.ts. В нёмtscописывает всё, что есть в браузере, и там уже объявлено имяname— имя окна браузера. - Значит, наша
nameпопала туда же, где лежит браузерная, — в общий коридор.
Скрипт и модуль
TypeScript делит файлы на два вида по одному признаку: есть ли в файле import или export.
Скрипт
Файл без import и export — это скрипт. Его имена верхнего уровня, то есть объявленные вне функций и блоков, глобальные. Они общие для всех скриптов программы, и среди них уже есть имена самого браузера.
По умолчанию tsc считает, что программа может работать в браузере, и знает его глобальные имена: name, status, length, top и другие. Поэтому const name в скрипте сталкивается с браузерным name.
В картинке с домом скрипт оставляет свои вещи в общем коридоре. Их видят все жильцы, и двух вещей с одной биркой там быть не может.
Модуль
Файл, в котором есть хотя бы один import или export, — это модуль. Его имена верхнего уровня принадлежат только ему, а снаружи видно лишь то, что он экспортирует. Переменная name внутри модуля не мешает браузерной, как книга в кабинете не мешает книге в коридоре.
export и import
Разделим программу на два файла. В format.ts лежит функция, которая показывает цену с двумя знаками после точки:
export function formatPrice(price: number): string {
return price.toFixed(2);
}
function round(price: number): number {
return Math.round(price);
}Что здесь важно:
exportпередfunctionвыставляет функцию к двери: её смогут взять другие файлы.- У
roundсловаexportнет, поэтому эта функция видна только внутриformat.ts. - Из-за
exportфайл стал модулем, и его имена не попадают в общий коридор.
Файл main.ts забирает функцию строкой import { formatPrice } from "./format.js";. Разберём её по частям:
importзначит «взять из другого модуля».- В фигурных скобках — нужные имена. Их может быть несколько, через запятую.
- После
from— путь к модулю, а./значит «в той же папке». - В пути стоит
.js, а не.ts: так пишут в TypeScript, иtscсам найдёт рядомformat.ts.
После этой строки main.ts вызывает formatPrice как свою функцию. Пусть вторая строка файла — console.log(formatPrice(100));. Скомпилируем и запустим:
$ tsc main.ts
$ node main.js
100.00tsc сам идёт по импортам. Увидев import из ./format.js, он проверяет и format.ts, поэтому рядом появляются оба файла: main.js и format.js.
Типы экспортируют так же, как функции: export interface User { … } или export type Id = number;. Берут их той же строкой import.
Здесь аналогия с дверью перестаёт работать. Книги, вынесенной из кабинета, в кабинете больше нет. Экспортированная функция остаётся в своём модуле: main.ts получает доступ к той же самой функции, а не уносит её.
export {}
Вернёмся к hello.ts. Чтобы он стал модулем, хватит одной строки:
export {};
const name = "Аня";
console.log(name); // Аняexport {} ничего не экспортирует: фигурные скобки пустые. Но слово export в файле есть, значит, это модуль, и name в нём своя, а не глобальная.
В настоящем проекте почти в каждом файле есть import или export, и эта строка не нужна. Она выручает в маленьком файле для опытов, где их нет.
На собеседовании иногда спрашивают, почему переменная из одного файла конфликтует с переменной из другого файла или из браузера. Ответ: файл без import и export — скрипт, и его имена глобальные. Строка export {} делает его модулем.
Частая ошибка
Импортировали функцию, которую модуль не экспортирует. В format.ts у round нет export, а main.ts пытается её взять:
import { round } from "./format.js"; // ✗
console.log(round(99.6));error TS2459: Module '"./format.js"' declares 'round' locally, but it is not exported.tsc видит функцию round в format.ts, но она объявлена только для своего модуля: двери к ней нет. Добавьте export перед function round, и импорт заработает.
Главное
- Файл с
importилиexport— модуль: его имена свои, а снаружи видно только то, что он экспортирует. - Файл без них — скрипт: его имена глобальные и сталкиваются с именами браузера, как
nameс ошибкойTS2451. exportвыставляет функцию или тип наружу, аimport { … } from "./file.js"забирает их в другом файле.- Строка
export {}превращает скрипт в модуль, ничего не экспортируя.