Vite собрал приложение, хотя TypeScript подчёркивает ошибку в редакторе. Такое возможно: преобразовать файл в JavaScript и проверить типы — отдельные операции. Соберём небольшой заказ и добавим команду, которая остановит сборку на заведомо неверном типе.
Соберите стенд с одной ошибкой
Нужны Node.js 24, npm и знакомство с функциями TypeScript. Пример использует Vite 8.3.7 и TypeScript 5.9.3. Версии фиксируют стенд; установка Node.js и общий синтаксис языка остаются за рамками задачи. Если с типами пока трудно, начните с материала о TypeScript.
Работайте в отдельной папке vite-typecheck-lab. Сначала получим успешную сборку с неправильным исходником. Затем тот же исходник должна отклонить проверка типов. Эта пара результатов нужна: одна успешная команда ещё не доказывает, что защита работает.
Создайте package.json:
{
"name": "vite-typecheck-lab",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build:bundle": "vite build"
},
"devDependencies": {
"typescript": "5.9.3",
"vite": "8.3.7"
}
}
В корне добавьте index.html:
<!doctype html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Заказ книг</title>
</head>
<body>
<h1>Заказ книг</h1>
<output id="total"></output>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
Создайте папку src и файл src/order.ts:
export type Order = {
title: string;
price: number;
quantity: number;
};
export function getTotal(order: Order): number {
return order.price * order.quantity;
}
В src/main.ts намеренно передайте строку вместо количества:
import { getTotal } from './order';
import type { Order } from './order';
const order: Order = {
title: 'Атлас интерфейсов',
price: 700,
quantity: '2'
};
const output = document.querySelector('#total');
if (output) {
output.textContent = `Сумма: ${getTotal(order)} рублей`;
}
Сохраните файлы, установите пакеты и выполните сборку:
npm install
npm run build:bundle
Команда должна завершиться успешно и создать dist. Ошибка присваивания строкового значения числовому полю сама по себе не мешает Vite преобразовать этот модуль.
Проверьте приложение в разработке:
npm run dev -- --host 127.0.0.1 --port 5173 --strictPort
На http://127.0.0.1:5173 появится «Сумма: 1400 рублей». Число получилось из-за приведения строки '2' к числу в операции умножения JavaScript. Именно поэтому этот пример коварен: интерфейс выглядит исправным, хотя договорённость типа уже нарушена.
Поменяйте строку на 'две' и сохраните файл. Теперь результат будет NaN. Пока вы наблюдаете поведение JavaScript после преобразования, которое допускает разные типы операндов и выполняет приведение во время вычисления. Гарантии проверки типов ещё нет. Верните '2', чтобы дальше сравнивать один и тот же исходник. Затем остановите сервер разработки через Ctrl+C в терминале: следующие команды проверки выполняйте там же. К серверу вернёмся в разделе о наблюдении за типами.
Добавьте tsc без выпуска JavaScript
В корне создайте tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"noEmit": true,
"isolatedModules": true,
"verbatimModuleSyntax": true,
"skipLibCheck": true
},
"include": ["src"]
}
Теперь дополните scripts в package.json. Полный блок должен выглядеть так:
{
"dev": "vite",
"typecheck": "tsc --noEmit",
"typecheck:watch": "tsc --noEmit --watch",
"build:bundle": "vite build",
"build": "npm run typecheck && npm run build:bundle"
}
Это содержимое поля scripts, а не замена всего файла. Пакеты из предыдущего шага сохраните.
Запустите проверку:
npm run typecheck
В терминале ожидается диагностика возле quantity: Type 'string' is not assignable to type 'number'. Это значит, что строковое значение нельзя присвоить полю числового типа. Код завершения будет ненулевым. Команда проверяет исходники из include, но не создаёт рядом дополнительные файлы .js.
Исправьте src/main.ts, записав число без кавычек:
const order: Order = {
title: 'Атлас интерфейсов',
price: 700,
quantity: 2
};
Повторите команду. Успешный запуск tsc может вообще ничего не напечатать после строки npm. Здесь отсутствие диагностик и нулевой код завершения означают, что найденные проектом файлы прошли проверку.
Проверьте, что цепочка действительно останавливается
Верните quantity: '2'. Перед запуском предскажите результат npm run build: появится ли свежая сборка, если первая команда завершится с ошибкой?
npm run build
Оператор && запускает правую команду только при успешном завершении левой. После ошибки tsc цепочка останавливается. Верните quantity: 2 и повторите: теперь должны последовательно пройти проверка типов и сборка.
Старый dist при неудачной проверке может остаться от предыдущего запуска. Его наличие ничего не доказывает. Смотрите на код завершения всей команды и журнал запуска. В системе публикации нужен такой же принцип: размещать артефакт только после успешной команды, иначе старые файлы могут выдать себя за результат новой проверки.
Для локального опыта удалять папку не требуется. Если хотите убедиться на отдельном стенде, переименуйте прежний dist в dist-before-check, верните неверный тип и запустите build. Новая папка dist не появится: выполнение остановилось до Vite. Это наблюдаемое следствие порядка команд.
После проверки восстановите правильное число и ещё раз выполните успешную сборку. Сломанный вариант полезен как тест проверки, но основной пример дальше должен оставаться рабочим.
Разделите преобразование, типы и поведение
У показанного проекта есть разные проверки, каждая отвечает на свой вопрос:
| Действие | На какой вопрос отвечает | Чего результат не доказывает |
|---|---|---|
Vite преобразует .ts |
Может ли инструмент получить JavaScript из исходника | Что все присваивания соответствуют типам |
| Vite собирает приложение | Можно ли собрать граф модулей и ресурсы | Что логика заказа верна |
tsc --noEmit |
Согласуются ли статические типы выбранных файлов | Что данные снаружи соответствуют описанию |
| Запуск и тест поведения | Даёт ли конкретный сценарий ожидаемый результат | Что проверены все варианты входа |
Аннотации Order и number нужны во время разработки. В выполняемом JavaScript этих описаний нет. Поэтому браузер способен показать число даже при нарушенном типе и способен получить NaN при других данных.
Для такого стенда рекомендуем отдельную команду tsc. Её можно запустить из терминала, редактора и системы сборки с одним результатом. Плагин с сообщениями в браузере может дополнить рабочий процесс, но правило допуска к сборке всё равно должно быть видно по команде и её коду завершения.
noEmit оставляет выпуск файлов Vite. Это устраняет вторую копию JavaScript рядом с исходниками и не заставляет выбирать, какую папку потом публиковать.
Параметр strict включает набор более строгих проверок. isolatedModules помогает находить конструкции, которые неудобно преобразовывать по одному файлу. moduleResolution: 'Bundler' соответствует выбранному сценарию, где импорты разрешает сборщик. Копировать этот tsconfig.json в отдельный серверный проект на Node.js без разбора не нужно: правила запуска там другие.
skipLibCheck касается проверки файлов деклараций библиотек. Он не отменяет проверку нашего main.ts. Если ошибка возникла в ваших исходниках, этот параметр не должен превращаться в способ спрятать её.
Убедитесь, что tsc проверяет нужные файлы
Иногда команда проходит потому, что проблемный файл вообще не входит в проект. Начните с include, exclude и импортов. При подозрении посмотрите список файлов:
npm run typecheck -- --listFiles
В выводе должны быть src/main.ts и src/order.ts. Кроме них будут файлы стандартной библиотеки; это ожидаемо. Такой вывод помогает отличить «ошибок нет» от «не тот файл проверен».
Если нужно посмотреть итоговую конфигурацию после наследования, выполните:
npm run typecheck -- --showConfig
Команда полезна в проектах с extends, где строка из одного файла меняется настройкой из другого. Сверяйте фактическую конфигурацию, а затем правьте источник этой настройки.
Не запускайте для проверки проекта tsc src/main.ts. При передаче входных файлов напрямую tsc не использует tsconfig.json так же, как при проектном запуске. В результате вы можете получить другие настройки и решить, что ошибка исчезла. Для отдельного конфига используйте tsc --noEmit -p имя-файла.json.
В большом приложении конфиг браузерного кода и конфиг инструментов могут быть разными. Наш пример охватывает src. Если вы добавите vite.config.ts, явно включите этот файл в подходящую проверку с окружением Node.js. Одно зелёное имя команды не означает, что она покрывает весь репозиторий.
Оставьте наблюдение за типами отдельным процессом
Во время работы откройте два терминала в папке проекта. В первом запустите сервер разработки:
npm run dev
Во втором включите наблюдение:
npm run typecheck:watch
Снова измените количество на строку. Браузер может обновиться после сохранения, а терминал tsc покажет диагностику. Исправьте поле: наблюдение пересчитает результат. Не нужно вручную останавливать и запускать компилятор после каждого изменения.
Два процесса удобны для обратной связи, но перед выпуском используйте конечную команду npm run build. В режиме --watch tsc продолжает работать и ждать изменений; для системы сборки нужна команда, которая завершится с успешным или ошибочным кодом.
Для команды это ещё и общий договор: локальная проверка и проверка перед публикацией вызывают один и тот же typecheck. Иначе редактор одного разработчика будет проверять строже, чем сервер сборки, а у другого нужное расширение окажется выключено.
Не прячьте ошибку приведением типа
Вместо числового количества можно заставить tsc замолчать приведением через unknown. Это обходит исходный договор:
const quantity = 'две' as unknown as number;
В JavaScript переменная всё ещё содержит строку. Утверждение типа не выполняет преобразование. Такой приём сделал бы отрицательную проверку бесполезной: команда прошла, но причина ошибки сохранилась.
Для строки из формы нужно преобразование. Например, для положительного целого:
export function parseQuantity(value: string): number {
if (value.trim() === '') {
throw new Error('Количество не заполнено');
}
const quantity = Number(value);
if (!Number.isInteger(quantity) || quantity < 1) {
throw new Error('Нужно положительное целое количество');
}
return quantity;
}
Попробуйте parseQuantity('2'), parseQuantity('две') и пустую строку. Первый вызов вернёт число, остальные завершатся ошибкой с понятной причиной. Тип результата описывает успешный выход функции, а проверки тела обеспечивают это поведение на переданной строке.
Эта функция не проверяет любой неизвестный ответ API: её вход уже описан как строка. Данные из сети нужно сначала разобрать и проверить на границе программы. tsc не читает будущие ответы сервера.
Сохраните проверки для рабочего проекта
| Симптом | Что посмотреть первым | Что исправить |
|---|---|---|
| Vite собрал код с ошибкой типов | Есть ли tsc в фактической команде |
Добавить отдельный typecheck и связать через && |
tsc молчит при заведомой ошибке |
--listFiles, активный конфиг |
Включить нужный исходник в проект |
| Редактор ругается иначе, чем терминал | Версия TypeScript и выбранный проект | Использовать версию и конфиг репозитория |
После отказа проверки виден dist |
Время создания и журнал команд | Публиковать только артефакт успешной сборки |
| Команда зависла в системе непрерывной интеграции (CI) | Не запущен ли --watch |
Использовать конечную проверку без наблюдения |
Ошибка исчезла после as |
Изменились ли данные во время выполнения | Преобразовать и проверить вход |
Добавьте поле discount: number в тип Order, задайте скидку числом и учтите её в расчёте. Затем специально передайте строку и убедитесь, что build останавливается до Vite. После исправления сумма должна снова вычисляться.
Теперь проверьте границу охвата. Создайте src/check.ts с const count: number = 'ошибка' и убедитесь, что typecheck замечает файл даже без импорта из main.ts. Причина — наш include: ['src']. Удалите учебную ошибку и завершите опыт успешным npm run build.
Если хочется подробнее работать со сборкой и её настройками, посмотрите программу курса по Vite. Практическая точка входа в вашем проекте — одна команда, которая получает ненулевой код выхода на известной ошибке типов.