Отдельный запуск tsc для проверки типов

Vite изначально проектировался как инструмент быстрой разработки, ориентированный на современный стек JavaScript и TypeScript. Однако одна из ключевых особенностей архитектуры Vite заключается в том, что он не выполняет полноценную проверку типов TypeScript во время разработки.

Во время запуска dev-сервера Vite использует сверхбыструю трансформацию через esbuild, которая отвечает только за:

  • транспиляцию TypeScript в JavaScript;
  • удаление типов;
  • преобразование модулей;
  • ускорение HMR.

Проверка типов (type checking) в этот процесс не входит.

Именно поэтому в крупных проектах почти всегда используется отдельный запуск tsc исключительно для статического анализа типов.


Почему Vite не проверяет типы

Классический компилятор TypeScript (tsc) выполняет:

  • построение графа зависимостей;
  • анализ типов;
  • вычисление generic-типов;
  • проверку совместимости интерфейсов;
  • диагностику ошибок;
  • генерацию declaration-файлов;
  • инкрементальную сборку.

Все эти операции относительно тяжёлые.

Vite делает ставку на скорость dev-сервера, поэтому TypeScript в Vite работает иначе:

// app.ts
const value: number = 'hello'

Даже при наличии ошибки приложение может успешно запускаться.

esbuild просто удалит типы:

const value = 'hello'

Ошибку типов покажет только:

  • IDE;
  • отдельный запуск tsc;
  • CI/CD;
  • линтер;
  • специализированные плагины.

Разделение транспиляции и проверки типов

В экосистеме Vite принято разделять две задачи:

Задача Инструмент
Трансформация TS → JS esbuild
Проверка типов tsc

Такое разделение даёт несколько преимуществ:

Высокая скорость HMR

Проверка типов не блокирует обновление модулей.

Независимость проверки

Типизация может выполняться:

  • отдельно;
  • параллельно;
  • только в CI;
  • только перед production-сборкой.

Масштабируемость

Крупные проекты с тысячами файлов работают быстрее.


Отдельный запуск tsc

Наиболее распространённый вариант:

{
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build"
  }
}

Здесь:

  1. tsc выполняет проверку типов.
  2. vite build выполняет production-сборку.

Если TypeScript обнаружит ошибку:

const userId: number = 'admin'

сборка остановится:

Type 'string' is not assignable to type 'number'

Проверка без генерации файлов

Обычно Vite не использует tsc для генерации JavaScript.

Поэтому применяется режим:

tsc --noEmit

Он означает:

  • не создавать JS-файлы;
  • не генерировать .d.ts;
  • только проверять типы.

Пример:

{
  "scripts": {
    "typecheck": "tsc --noEmit"
  }
}

Почему noEmit считается стандартом

Vite уже выполняет сборку самостоятельно.

Если не использовать noEmit, TypeScript начнёт генерировать:

  • .js;
  • .map;
  • .d.ts.

Это приводит к:

  • дублированию сборки;
  • конфликтам output-директорий;
  • лишним файлам;
  • замедлению процесса.

Поэтому типичная конфигурация Vite-проекта выглядит так:

{
  "compilerOptions": {
    "noEmit": true
  }
}

Проверка типов во время разработки

Отдельная проверка типов часто запускается параллельно с dev-сервером.

Например:

{
  "scripts": {
    "dev": "vite",
    "typecheck": "tsc --noEmit --watch"
  }
}

Тогда:

  • Vite отвечает за HMR;
  • tsc непрерывно следит за типами.

Режим watch

Команда:

tsc --watch

или:

tsc -w

запускает компилятор в режиме отслеживания изменений.

При изменении файлов TypeScript автоматически:

  • пересчитывает зависимости;
  • выполняет повторную проверку;
  • показывает новые ошибки.

Пример вывода:

Found 0 errors. Watching for file changes.

После ошибки:

src/api.ts:12:7 - error TS2322

Параллельный запуск Vite и tsc

Часто используется пакет concurrently.

Установка:

npm install concurrently -D

Скрипт:

{
  "scripts": {
    "dev": "concurrently \"vite\" \"tsc --noEmit --watch\""
  }
}

Теперь:

  • терминал запускает оба процесса;
  • ошибки типов появляются сразу;
  • HMR остаётся быстрым.

Проверка типов только перед production-сборкой

Иногда разработчики отключают постоянную проверку типов ради максимальной скорости разработки.

Тогда используется схема:

{
  "scripts": {
    "build": "tsc --noEmit && vite build"
  }
}

Ошибки появляются только при production build.


Разница между vite build и tsc

Это фундаментальное различие.

vite build

Отвечает за:

  • bundling;
  • tree shaking;
  • минификацию;
  • chunk splitting;
  • оптимизацию ассетов;
  • production output.

tsc

Отвечает за:

  • типизацию;
  • анализ типов;
  • declaration files;
  • диагностику.

Почему vite build может успешно завершиться при ошибках типов

Пример:

function sum(a: number, b: number) {
  return a + b
}

sum('1', '2')

Vite соберёт проект успешно.

Причина:

  • ошибка существует только на уровне типов;
  • JavaScript остаётся валидным.

Использование vue-tsc

В проектах на Vue стандартный tsc не способен корректно анализировать .vue-файлы.

Поэтому используется:

vue-tsc --noEmit

Пример:

{
  "scripts": {
    "typecheck": "vue-tsc --noEmit"
  }
}

Проверка типов React-проектов

В React-проектах обычно достаточно стандартного:

tsc --noEmit

Поскольку:

  • JSX поддерживается TypeScript напрямую;
  • .tsx анализируется нативно.

Incremental Type Checking

TypeScript поддерживает инкрементальную проверку.

Настройка:

{
  "compilerOptions": {
    "incremental": true
  }
}

После первого запуска создаётся:

.tsbuildinfo

Он хранит:

  • граф модулей;
  • кэш зависимостей;
  • информацию о предыдущей проверке.

Повторные проверки становятся значительно быстрее.


Composite Projects

В монорепозиториях часто используется:

{
  "compilerOptions": {
    "composite": true
  }
}

Это необходимо для:

  • project references;
  • раздельной проверки пакетов;
  • инкрементальной сборки библиотек.

Project References

Крупные Vite-проекты могут состоять из нескольких TypeScript-пакетов.

Структура:

packages/
  ui/
  core/
  api/

Каждый пакет содержит собственный tsconfig.json.

Главный конфиг:

{
  "references": [
    { "path": "./packages/ui" },
    { "path": "./packages/core" },
    { "path": "./packages/api" }
  ]
}

Проверка:

tsc --build

Режим --build

Флаг:

tsc --build

или:

tsc -b

предназначен для:

  • monorepo;
  • project references;
  • incremental compilation.

TypeScript анализирует зависимости между пакетами и пересобирает только изменённые части.


Проверка declaration-файлов

При разработке библиотек часто требуется:

{
  "compilerOptions": {
    "declaration": true
  }
}

Тогда TypeScript создаёт:

dist/index.d.ts

Однако даже в этом случае Vite обычно отвечает только за JS-сборку.


Комбинация Vite и tsc при разработке библиотек

Типичная схема:

{
  "scripts": {
    "build": "tsc && vite build"
  }
}

Где:

  • tsc генерирует типы;
  • Vite собирает JavaScript bundle.

Проверка типов в CI/CD

Во многих проектах типизация проверяется отдельно от сборки.

Пример GitHub Actions:

- run: npm run typecheck
- run: npm run build

Это позволяет:

  • раньше обнаруживать ошибки;
  • разделять этапы pipeline;
  • упрощать диагностику.

Типичные ошибки при отсутствии tsc

Ложное ощущение корректности

Dev-сервер работает, хотя типизация уже сломана.

Ошибки generic-типов

Например:

type ApiResponse<T> = {
  data: T
}

const response: ApiResponse<number> = {
  data: 'error'
}

Без tsc ошибка может остаться незамеченной.

Поломка публичного API библиотеки

Особенно критично для:

  • SDK;
  • npm-пакетов;
  • UI-kit;
  • shared libraries.

Использование vite-plugin-checker

Существует плагин:

npm install vite-plugin-checker -D

Конфигурация:

import checker from 'vite-plugin-checker'

export default defineConfig({
  plugins: [
    checker({
      typescript: true
    })
  ]
})

Плагин:

  • запускает tsc параллельно;
  • показывает overlay в браузере;
  • отображает ошибки типов прямо во время разработки.

Как работает vite-plugin-checker

Архитектурно плагин:

  1. создаёт отдельный worker process;
  2. запускает TypeScript checker;
  3. отправляет результаты в Vite overlay.

Главное преимущество:

  • проверка типов не блокирует HMR.

Почему Vite не встроил type checking по умолчанию

Основная философия Vite:

  • минимальная задержка dev-сервера;
  • максимально быстрый cold start;
  • независимость инструментов;
  • модульность.

Полноценный tsc значительно ухудшил бы скорость старта крупных проектов.


Производительность esbuild против tsc

esbuild написан на Go и оптимизирован под:

  • extremely fast parsing;
  • parallel compilation;
  • быстрые AST-преобразования.

TypeScript compiler написан на TypeScript и ориентирован прежде всего на:

  • корректность анализа;
  • сложную систему типов;
  • точную диагностику.

Оптимальная структура scripts

Распространённый вариант:

{
  "scripts": {
    "dev": "vite",
    "typecheck": "tsc --noEmit",
    "typecheck:watch": "tsc --noEmit --watch",
    "build": "tsc --noEmit && vite build"
  }
}

Использование отдельного tsconfig для проверки

Иногда создаётся:

tsconfig.typecheck.json

Пример:

{
  "extends": "./tsconfig.json",
  "include": ["src"]
}

Запуск:

tsc --project tsconfig.typecheck.json --noEmit

Это позволяет:

  • отделять build-конфигурацию;
  • исключать тесты;
  • ускорять проверку.

Исключение файлов из type checking

Для ускорения проверки используются:

{
  "exclude": [
    "dist",
    "node_modules",
    "coverage"
  ]
}

Также иногда исключаются:

  • e2e tests;
  • storybook;
  • generated files.

Проверка JavaScript через TypeScript

Даже JS-проект Vite может использовать type checking.

Настройка:

{
  "compilerOptions": {
    "checkJs": true,
    "allowJs": true
  }
}

Теперь TypeScript анализирует:

/**
 * @param {number} id
 */
function load(id) {}

load('admin')

Диагностика через CLI

TypeScript предоставляет подробные диагностические сообщения.

Например:

tsc --pretty

или:

tsc --extendedDiagnostics

Последний режим показывает:

  • время анализа;
  • объём памяти;
  • скорость проверки;
  • статистику файлов.

Использование skipLibCheck

Для ускорения проектов часто включается:

{
  "compilerOptions": {
    "skipLibCheck": true
  }
}

Это отключает проверку типов внутри declaration-файлов зависимостей.

Преимущества:

  • ускорение проверки;
  • уменьшение нагрузки на CPU;
  • сокращение времени CI.

Недостаток:

  • некоторые ошибки библиотек могут остаться скрытыми.

Типичная production-схема Vite + TypeScript

Наиболее распространённая архитектура выглядит так:

Во время разработки

Vite → transpilation
IDE → type hints
tsc --watch → type checking

Во время production build

tsc --noEmit
↓
vite build

В CI

lint
↓
typecheck
↓
tests
↓
build