Файл .ts можно выполнить в Node.js без предварительной сборки, если выбранная версия поддерживает удаление типов. Проверки TypeScript при этом не будет. Запустим расчёт доставки, отдельно найдём ошибку типов и проверим границы этого способа.
Зафиксируйте способ запуска
Пример рассчитан на Node.js 24 и TypeScript 5.9.3. В этой ветке Node.js поддерживает запуск TypeScript с удалением типов без отдельного флага. Описанный путь использует только синтаксис, который можно стереть, сохранив JavaScript. Команды также можно проверить на более новой совместимой версии, но поведение старого Node.js по этому тексту угадывать нельзя.
Проверьте свою среду:
node --version
npm --version
Если у вас другая ветка Node.js, сначала сверяйте именно её поддержку TypeScript. Настроить среду нужно отдельно; для первого знакомства с терминалом пригодится материал «Как запускать JavaScript на Node.js».
Соберём локальную программу, которая считает стоимость доставки набора книг. Результат не зависит от сети, ключей или регистрации. Нужны базовые функции JavaScript, импорт модулей и понимание аннотации number.
Создайте отдельную папку node-ts-delivery. В ней будут package.json, tsconfig.json и папка src с тремя файлами. Все команды выполняйте из корня этой папки.
Запустите расчёт без сборки
Создайте package.json:
{
"name": "node-ts-delivery",
"private": true,
"type": "module",
"scripts": {
"start": "node src/main.ts",
"typecheck": "tsc --noEmit",
"check": "npm run typecheck && npm start"
},
"devDependencies": {
"typescript": "5.9.3"
}
}
Поле type делает выбранный способ работы с модулями явным. В этом проекте используем стандартные import и export.
Создайте папку src. В src/types.ts опишите входные данные:
export type Parcel = {
title: string;
weightGrams: number;
};
В src/delivery.ts запишите расчёт:
import type { Parcel } from './types.ts';
export function getDeliveryPrice(parcels: Parcel[]): number {
const totalWeight = parcels.reduce(
(sum, parcel) => sum + parcel.weightGrams,
0
);
return totalWeight <= 1000 ? 200 : 350;
}
Наши учебные правила фиксированы: до 1000 граммов включительно доставка стоит 200 рублей, выше — 350 рублей. Здесь важен воспроизводимый результат, поэтому тарифы вымышленные.
Добавьте src/main.ts:
import { getDeliveryPrice } from './delivery.ts';
import type { Parcel } from './types.ts';
const parcels: Parcel[] = [
{ title: 'Атлас интерфейсов', weightGrams: 600 },
{ title: 'Карманный справочник', weightGrams: 300 }
];
const price = getDeliveryPrice(parcels);
console.log(`Доставка: ${price} рублей`);
Запустите файл напрямую:
node src/main.ts
Ожидаемый вывод — Доставка: 200 рублей. В этом шаге TypeScript как пакет ещё не нужен. Node.js убирает поддерживаемые объявления типов, затем выполняет программу. Отдельная папка с полученным JavaScript не создаётся.
Поменяйте вес справочника с 300 на 500 и повторите запуск. Суммарный вес станет 1100 граммов, цена — 350 рублей. Верните исходное значение. Такое управляемое изменение проверяет, что вы действительно запускаете сохранённые файлы этого проекта.
Подключите проверку типов отдельной командой
В корне создайте tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022"],
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"noEmit": true,
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"erasableSyntaxOnly": true,
"skipLibCheck": true
},
"include": ["src"]
}
В программе используется console, поэтому проверяющему инструменту потребуются объявления среды Node.js. Добавьте пакет и установите зависимости:
npm install --save-dev --save-exact @types/node@24.0.0
npm run typecheck
Команда установки также установит TypeScript из package.json и запишет файл блокировки. Сохраните его вместе с учебным проектом, чтобы повторный запуск использовал согласованный набор пакетов.
tsc читает tsconfig.json и проверяет файлы из src. Благодаря noEmit команда не выпускает JavaScript. За выполнение отвечает node src/main.ts, за статическую согласованность — tsc --noEmit. В поле check они связаны через &&: запуск последует только после успешной проверки.
Конфигурация согласована с прямым запуском: NodeNext учитывает правила модулей Node.js, allowImportingTsExtensions разрешает показанные пути с .ts, а erasableSyntaxOnly ограничивает исходник стираемым синтаксисом. Эти параметры помогают поймать несовместимость до выполнения.
Сам Node.js не применяет tsconfig.json для такого запуска. Если изменить target в конфигурации, Node.js всё равно выполнит прежний исходник при запуске node src/main.ts.
Докажите, что запуск не проверяет типы
В src/main.ts временно измените первую посылку:
const parcels: Parcel[] = [
{ title: 'Атлас интерфейсов', weightGrams: '600' },
{ title: 'Карманный справочник', weightGrams: 300 }
];
Предскажите два результата до запуска: сможет ли Node.js выполнить файл и одобрит ли его tsc?
Сначала выполните:
npm start
Node.js сможет запустить этот исходник. Аннотация Parcel[] стёрта, поэтому запрета на строку во время выполнения нет. В нашем расчёте строка запускает конкатенацию: 0 + '600' даёт '0600', затем добавляется 300. Затем JavaScript сравнит с числом уже испорченную сумму, и вы увидите 350 рублей вместо 200.
Теперь вызовите проверку:
npm run typecheck
Она должна завершиться ошибкой возле weightGrams: строка несовместима с числом. Это полезный отрицательный опыт. Если tsc молчит, проверьте сохранность файла и список входов, прежде чем менять настройки строгости.
Верните weightGrams: 600 и запустите общий путь:
npm run check
После успешной проверки появится ожидаемая цена 200 рублей. Сохраняйте такую команду как рабочую точку входа: выполнение через npm start подходит для исследования, а check дополнительно проверяет типы.
Используйте import type для описаний
Импорты различаются. В main.ts функция getDeliveryPrice понадобится при выполнении, а тип Parcel нужен проверяющему инструменту и редактору, поэтому у второго импорта есть type:
import { getDeliveryPrice } from './delivery.ts';
import type { Parcel } from './types.ts';
При удалении типов импорт Parcel исчезает. Node.js не пытается получить одноимённое значение из types.ts. Обычный импорт без type может оставить запрос значения, которого в выполняемом модуле нет.
verbatimModuleSyntax помогает удерживать эту границу явной в tsc. Привычка особенно полезна при прямом запуске, где нет сборщика, который мог бы иначе обработать граф модулей.
Пути тоже должны быть пригодны для выполнения. В этом проекте указан файл ./delivery.ts, и он действительно существует. Относительный импорт ./delivery без расширения не следует копировать из настроек браузерного сборщика. Режим исполнения здесь другой.
Псевдоним вроде @/delivery, заданный только в paths внутри tsconfig.json, сам по себе тоже не становится понятным Node.js. Для нашего маленького скрипта относительный путь точнее и не требует дополнительного механизма разрешения импортов.
Проверьте границу стираемого синтаксиса
Аннотации параметров, интерфейсы и псевдонимы типов можно убрать, сохранив рабочую структуру JavaScript. Некоторые конструкции TypeScript требуют генерации нового JavaScript. Для показанного прямого запуска это граница.
Создайте временный src/status.ts:
export enum DeliveryStatus {
Created,
Sent
}
console.log(DeliveryStatus.Sent);
Вызовите Node.js отдельно:
node src/status.ts
В выбранном режиме удаления типов enum отклоняется как синтаксис, требующий преобразования. Теперь запустите npm run typecheck: параметр erasableSyntaxOnly также должен указать на проблему. Это другая ошибка, чем несовместимость строки и числа в прошлом опыте.
Для основного примера такой конструкции не требуется. Замените файл обычным объектом JavaScript с уточнением типа:
export const DeliveryStatus = {
Created: 'created',
Sent: 'sent'
} as const;
export type DeliveryStatusValue =
typeof DeliveryStatus[keyof typeof DeliveryStatus];
console.log(DeliveryStatus.Sent);
Повторный запуск напечатает sent. Объект существует во время выполнения, as const и описание DeliveryStatusValue нужны при проверке. Здесь стирание типов сохраняет рабочий JavaScript.
В существующем проекте с большим количеством требующих преобразования конструкций может быть разумнее отдельная сборка. Выбирайте способ по исходникам и требованиям запуска. Переписывать всё приложение ради исчезновения одного шага сборки необязательно.
В этот материал не входит настройка компиляции с выпуском .js: у неё другой результат, другие пути импортов и отдельная проверка выходной папки. Если она понадобится, зафиксируйте её как самостоятельную задачу, чтобы не смешать два способа запуска в одном start.
Проверяйте внешние данные во время выполнения
Даже строгий tsc не знает, что вернёт сеть или что пользователь запишет в JSON. Тип Parcel описывает договорённость исходников. Для данных снаружи нужны настоящие условия.
Добавьте в src/delivery.ts экспортируемую функцию:
export function parseWeight(value: unknown): number {
if (typeof value !== 'number'
|| !Number.isFinite(value)
|| value <= 0) {
throw new Error('Вес должен быть положительным числом');
}
return value;
}
В основном файле импортируйте её вместе с расчётом и проверьте два вызова по очереди:
import { getDeliveryPrice, parseWeight } from './delivery.ts';
console.log(parseWeight(600));
console.log(parseWeight('600'));
Первый напечатает число. Второй выбросит ошибку с сообщением о весе. Параметр unknown разрешает передать неизвестное значение, а тело функции сужает его до подходящего числа.
В этом опыте ошибка запуска ожидаема. Уберите второй вызов перед возвратом к рабочему скрипту. Если нужны числовые строки, отдельно опишите правила преобразования и проверьте пустую строку; без такой договорённости молчаливое приведение снова испортит расчёт.
Проверять только typeof value === 'number' тоже недостаточно: NaN имеет тип number. Условие Number.isFinite исключает его вместе с бесконечностями. Бизнес-правило value > 0 отсекает нулевой и отрицательный вес.
Сохраните таблицу выбора проверки
| Ситуация | Что происходит | Куда смотреть |
|---|---|---|
Node.js исполнил .ts |
Поддерживаемые типы стёрты, JavaScript выполнен | Отдельно запустить tsc |
tsc нашёл неверное присваивание |
Исходник нарушает статический договор | Исправить данные или тип по смыслу |
Node.js отклонил enum |
Конструкции нужно преобразование | Убрать её из этого способа запуска либо выбрать сборку |
| Импорт не найден | Путь не разрешается средой выполнения | Проверить расширение и реальное имя файла |
Parcel не экспортируется во время запуска |
Тип импортирован как значение | Записать import type |
Изменение target ничего не меняет |
Прямой запуск не читает этот параметр | Разделить настройки tsc и возможности Node.js |
| Прошла проверка, но JSON испортил расчёт | Внешние данные не проверены условиями | Проверять вход как unknown |
Для итоговой проверки оставьте рабочие файлы, выполните npm run check и убедитесь в цене 200 рублей. Затем измените общий вес на 1000 граммов: цена должна остаться 200. Для 1001 грамма ожидается 350. Так вы проверите границу условия, которая могла потеряться за обсуждением инструментов.
Самостоятельно добавьте параметр pickup: boolean в функцию расчёта: при самовывозе цена должна быть нулевой. Сначала проверьте оба значения булева параметра, затем намеренно передайте строку 'true'. Запуск и статическая проверка снова ответят на разные вопросы; завершите работу вариантом, который проходит check.
Вторая задача — дополнить parseWeight верхней границей в 20 000 граммов. Проверьте 20 000, 20 001 и NaN. Нужный результат задают условия функции, а подпись number остаётся прежней.
Если хотите глубже работать с типами и границами данных, посмотрите программу курса по TypeScript. Для следующего собственного скрипта запишите рядом две команды: чем вы его выполняете и чем проверяете типы.