Реальный проект — это не один файл, а десятки и сотни. Чтобы код одного файла мог пользоваться функциями другого, существуют модули. А чтобы подключать чужой код (библиотеки) и не копировать его вручную, существует npm — пакетный менеджер из мира Node.js. Разберём оба по порядку.
Зачем нужны модули
Если бы весь код жил в одной глобальной области, любые две переменные с одинаковым именем конфликтовали бы, а понять, откуда взялась функция, было бы невозможно. Модуль решает это просто: каждый файл — отдельное пространство имён. Внутри файла всё своё и приватное; наружу видно только то, что вы явно экспортировали, а взять чужое можно только через явный импорт.
Короткая формула: один файл — один модуль, и связи между файлами видны в строках import.
ES-модули: import и export
Современный стандарт — ES modules (ESM). Это синтаксис import/export, единый для браузера и для Node. Именно его вы пишете в TypeScript по умолчанию.
// file: math.ts
export function add(a: number, b: number): number {
return a + b;
}
export const PI = 3.14159;
// "export default" — одно главное значение на модуль
export default class Calculator {
// ...
}
// file: app.ts
import Calculator, { add, PI } from "./math"; // default + именованные
import { add as plus } from "./math"; // переименование при импорте
import * as math from "./math"; // всё пространство модуля разом
console.log(add(2, 3)); // 5
console.log(math.PI); // 3.14159
Что важно запомнить:
- Именованный экспорт (
export function add) импортируется в фигурных скобках по тому же имени. - Экспорт по умолчанию (
export default) — один на файл, имя при импорте выбираете сами. - Путь к своему файлу начинается с
./или../. Путь без точки (import { z } from "zod") — это пакет изnode_modules.
CommonJS: require и module.exports
До появления ESM в Node была своя система модулей — CommonJS (CJS). Её вы будете встречать в унаследованном коде и многих библиотеках:
// экспорт
function add(a, b) {
return a + b;
}
module.exports = { add };
// импорт
const { add } = require("./math");
Чем CommonJS отличается от ES-модулей на практике:
require()— обычный вызов функции, его можно поставить в середине файла или внутри условия.import— это объявление, которое поднимается наверх и выполняется до остального кода.- CommonJS грузит модули синхронно; ESM спроектирован под асинхронную загрузку и статический анализ (бандлеры умеют выкидывать неиспользуемый экспорт).
- ESM — это будущее и стандарт; CommonJS — наследие, которое ещё долго будет рядом.
В Node то, какую систему использует ваш .js-файл, определяет поле "type" в package.json: "type": "module" — это ESM, "type": "commonjs" (или отсутствие поля) — CommonJS. В TypeScript вы почти всегда пишете import/export, а уже компилятор и настройки решают, во что это превратится.
package.json — паспорт проекта
Любой Node-проект описывается файлом package.json в корне. Это обычный JSON с метаданными проекта и, главное, списком зависимостей. Создать его можно командой npm init (а npm init -y — со значениями по умолчанию).
{
"name": "my-service",
"version": "1.0.0",
"type": "module",
"scripts": {
"build": "tsc",
"start": "node dist/app.js",
"dev": "tsx watch src/app.ts",
"test": "vitest"
},
"dependencies": {
"express": "^4.19.2",
"zod": "^3.23.8"
},
"devDependencies": {
"typescript": "^5.4.5",
"tsx": "^4.10.0",
"vitest": "^1.6.0"
}
}
dependencies против devDependencies
Зависимости делятся на две группы, и разница не косметическая:
- dependencies — пакеты, без которых приложение не работает в продакшене. Например, веб-фреймворк
expressили валидаторzod. Они уезжают на сервер вместе с приложением. - devDependencies — инструменты, нужные только во время разработки и сборки: сам компилятор
typescript, тестовый раннер, линтеры. На рабочем сервере собранного приложения они не нужны.
Команды кладут пакет в нужную группу: npm install express — в dependencies, npm install --save-dev typescript (короче npm i -D typescript) — в devDependencies. Правильное разделение уменьшает размер продакшен-сборки и снижает поверхность для уязвимостей.
semver — версии вида 4.19.2
Номер версии пакета — это semver (semantic versioning), три числа MAJOR.MINOR.PATCH:
- MAJOR (4.x.x) — несовместимые изменения, старый код может сломаться.
- MINOR (x.19.x) — новые возможности без поломок.
- PATCH (x.x.2) — исправления, ничего не ломающие.
В package.json перед версией обычно стоит значок, который задаёт допустимый диапазон обновлений:
^4.19.2— «карет»: разрешено всё до следующего MAJOR (4.x.x, но не5.0.0). Самый частый вариант.~4.19.2— «тильда»: разрешены только PATCH-обновления (4.19.x).4.19.2— строго эта версия, без обновлений.
Точные версии, которые реально установились, фиксируются в файле package-lock.json (у npm). Его коммитят в репозиторий — он гарантирует, что у вас и у коллеги соберётся ровно одно и то же.
node_modules и установка
Команда npm install (без аргументов) читает package.json, скачивает все зависимости и складывает их в папку node_modules в корне проекта. Туда же подтягиваются зависимости ваших зависимостей — поэтому папка получается огромной.
Два правила про node_modules:
- Не коммитьте её в git — она восстанавливается из
package.json+ lock-файла одной командой. Добавьтеnode_modules/в.gitignore. - Если что-то «сломалось непонятно почему», частое лечение — удалить
node_modulesи lock-файл и поставить заново.
npm, pnpm и другие
npm — менеджер по умолчанию, идёт вместе с Node. Есть альтернативы с тем же package.json: pnpm (хранит пакеты в одном общем месте и делает ссылки — экономит диск и ставит быстрее) и yarn. Для начала достаточно npm; pnpm стоит знать, потому что его часто выбирают в новых проектах.
Скрипты npm
Блок scripts в package.json — это именованные команды проекта. Запускаются они через npm run <имя>:
npm run build # выполнит "tsc" — скомпилирует TypeScript
npm run dev # запустит dev-режим с автоперезагрузкой
npm test # для test/start/stop слово run можно опустить
Скрипты избавляют от запоминания длинных команд и документируют, как с проектом работать: открыв package.json, любой человек сразу видит, как его собрать, запустить и протестировать.
Коротко
- Модуль — это файл; наружу видно только
export, взять чужое можно только черезimport. - ES modules (
import/export) — современный стандарт; CommonJS (require) — наследие в Node-коде и старых библиотеках. - package.json — паспорт проекта: метаданные, зависимости и скрипты.
- dependencies нужны в продакшене, devDependencies — только для разработки и сборки.
- semver
MAJOR.MINOR.PATCH;^разрешает обновления до следующего MAJOR, lock-файл фиксирует точные версии. - node_modules восстанавливается из
package.json, в git не коммитится. - npm scripts запускаются через
npm run <имя>и документируют работу с проектом.
Что почитать дальше
- Основы JavaScript для TypeScript — язык, поверх которого всё это работает.
- Инструменты: компилятор и tsconfig — как
tscи настройки превращают ваши модули в исполняемый код. - Асинхронность и event loop — почему ESM грузится асинхронно и как Node это исполняет.