Перейти к содержанию

Настройки компилятора

В предыдущих уроках настройки уже работали как часть договора проекта: nullable DOM требовал проверки, необязательное описание имело точную политику, типовые импорты отделялись от значений. Теперь разберём эти решения вместе, чтобы конфигурация не выглядела набором строк, скопированных из чужого проекта.

Каталог остаётся тем же, что в уроке о границах модулей. Новый результат — понятное разделение проверки и выпуска, явная среда браузера и предсказуемое наследование настроек. Полные файлы lesson-22 находятся в архиве продолжения. Здесь не запускались tsc, npm, сборка или пример; ожидаемое действие настроек разбирается по исходникам.

Сначала определим среду проекта

Наш каталог работает в браузере через обычные ES-модули. В нём есть DOM, fetch и Promise, но нет обращений к файловой системе Node.js. Исходники лежат в src, выходные JavaScript-файлы предназначены для dist, а JSON и учебные HTML-страницы остаются рядом с главным документом.

Из такого описания следует выбор библиотечных деклараций и формата модулей. Если проект перенести в Node.js, недостаточно поменять порт сервера: правила разрешения пакетов и доступные глобальные значения тоже потребуют пересмотра. Конфигурация относится к конкретной среде, а не ко всем приложениям TypeScript сразу.

В серии выбрана опубликованная TypeScript 6.0.2, как в первом уроке. Это фиксированная учебная версия, а не обещание использовать самую новую ветку. Версию можно сверить по package.json тега Microsoft. Установка зависимости и создание lockfile читателем пока только предусмотрены.

Основной файл конфигурации

Полный tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ES2022",
    "moduleResolution": "Bundler",
    "lib": [
      "ES2022",
      "DOM"
    ],
    "types": [],
    "rootDir": "src",
    "outDir": "dist",
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true,
    "noEmitOnError": true,
    "verbatimModuleSyntax": true
  },
  "include": [
    "src/**/*.ts"
  ]
}

target задаёт уровень синтаксиса выпускаемого JavaScript. Здесь ES2022 соответствует выбранной современной среде. Он не загружает полифиллы и не делает старый браузер обладателем любого метода. Различие между понижением синтаксиса и доступностью API описано в справочнике target.

lib перечисляет определения стандартной среды: ES2022 для языка и DOM для браузерных объектов. Поэтому инструмент знает document и HTMLFormElement. Если запустить тот же JavaScript вне браузера, декларация DOM не создаст настоящий document. Смысл списка библиотек разобран в справочнике lib.

module сохраняет ES-модули соответствующего уровня. moduleResolution: Bundler определяет правила, по которым TypeScript ищет импортируемые исходники и пакеты. Название настройки не означает, что в нашем снимке есть сборщик: tsc не объединяет эти файлы в один bundle. В справочнике moduleResolution описано, что этот режим допускает относительные импорты без расширений. Мы всё равно пишем .js, потому что браузеру нужны реальные адреса после выпуска.

Что именно делает strict

strict включает семейство более строгих проверок типов. В каталоге особенно заметен строгий учёт null: результат querySelector может отсутствовать, и перед передачей узла в представление выполняется условие instanceof. Параметр функции без достаточного контекста также не должен молча превращаться в неявный any.

Это семейство может расширяться в новых версиях TypeScript, поэтому обновление версии иногда обнаруживает дополнительные ошибки исходников. Такая возможность прямо указана в описании strict. Фиксация версии и явная конфигурация позволяют обсуждать изменение по конкретному проекту.

Однако strict не означает «включены абсолютно все проверки». Например, noUncheckedIndexedAccess и exactOptionalPropertyTypes здесь заданы отдельно. Не следует удалять эти строки, предполагая, что первая настройка полностью их заменяет. Каждая добавляет собственное требование к записи программы.

При включённом noUncheckedIndexedAccess чтение элемента по индексу учитывает возможность отсутствия значения. В singleText мы сначала получаем values[0], а затем проверяем число значений и typeof. Договор массива не превращает любой выбранный индекс в существующую строку.

exactOptionalPropertyTypes помогает различать отсутствующее поле и поле, которому явно присвоено undefined. В Course описание необязательно, но при наличии является строкой. Наш runtime-парсер нормализует отсутствие в объект без description. Чтение описания всё ещё требует учитывать отсутствие; настройка не делает его обязательным.

Типовые импорты и выходные файлы

verbatimModuleSyntax делает намерение импорта явным: имена с type относятся к сведениям для проверки, обычные импорты сохраняются как значения. Поэтому app импортирует функции и отдельно Course. Такая запись уменьшает зависимость выпуска от догадок о том, зачем использовано имя.

types: [] не допускает автоматическое подключение всех видимых пакетов @types в глобальную среду проекта. Это не запрет импортировать типы нужной зависимости через её модуль и не отключение DOM: библиотечный список lib задаётся отдельно. Договор среды остаётся небольшим и явным; назначение списка описано в справочнике types.

include выбирает исходники проекта. rootDir задаёт основу структуры при выпуске, а outDir — место назначения. RootDir сам не является фильтром файлов: импорт из другой папки способен добавить исходник в программу и вызвать несогласованность выбранной выходной структуры. В нашем снимке активные TypeScript-исходники расположены внутри src. Связь входных и выходных папок описана в справочнике rootDir.

Tsc не копирует автоматически data/courses.json и courses/*.html только потому, что приложение использует их в fetch или href. Это отдельные runtime-файлы рядом с dist. Из корректной проверки типов нельзя вывести, что весь набор ресурсов уже подготовлен для браузера.

Проверять без выпуска

Новый полный tsconfig.check.json наследует основную конфигурацию:

{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "noEmit": true
  }
}

extends сначала получает базовые настройки, затем применяет уточнения. Относительные пути разрешаются от того конфигурационного файла, где они объявлены. Если в наследнике добавить собственный include, он заменит базовый список, а не незаметно дополнит его. Эти правила описаны в справочнике extends.

Настройка noEmit выключает запись выходных файлов. Она полезна для отдельной проверки исходников: договоры анализируются, но новый dist не создаётся. Конфигурация проверки при этом использует те же strict, target и include, что основной проект.

Полный package.json связывает два действия с разными командами:

{
  "name": "professorweb-type-lab",
  "version": "1.0.0",
  "private": true,
  "type": "module",
  "scripts": {
    "check": "tsc -p tsconfig.check.json",
    "build": "tsc"
  },
  "devDependencies": {
    "typescript": "6.0.2"
  }
}

Команда check в этом снимке явно выбирает tsconfig.check.json. Build использует обычный tsconfig.json и должен выпускать JavaScript, если исходники допустимы. Это только инструкции для будущего выполнения читателем. Нельзя объявить, что check прошёл, на основании наличия строки в package.json.

В основном файле noEmitOnError запрещает новый выпуск при обнаруженных ошибках. Он не удаляет старый dist. Поэтому оставшийся app.js способен быть результатом прежней удачной компиляции. Старый файл нельзя принимать за доказательство, что сегодняшние изменения проверены и выпущены.

Не смешивайте конфигурационный проект с запуском tsc для отдельно названного файла. При передаче файлов прямо командной строке нельзя автоматически предполагать применение всего tsconfig.json. В нашем package check явно выбирает проект через -p, а build без отдельных имён использует конфигурацию текущей папки. Это сохраняет одну область исходников и одинаковые исходные правила, согласно описанию tsconfig.json.

Отдельный check-файл также удобен редакторскому обсуждению: видно, что отличается только выпуск. Если позже понадобится иная область проверки, её следует записать явно и учитывать замену include при наследовании. Иначе два действия могли бы проверять разные исходники, хотя назывались бы проверкой одного каталога.

Конфигурация поддерживает договор, но не исполняет его

Ожидаемые данные не изменились: четыре курса, 60 уроков, отбор frontend/16 даёт 2/36. Настройки должны помогать заметить небезопасное чтение DOM или неподходящую форму объекта, но не вычисляют истинность этих результатов вместо браузера.

Так же успешная проверка не доказывает наличие JSON по нужному адресу, MIME-тип ответа или работу нативной формы. Внешние значения проходят runtime-парсер, ссылки требуют настоящих файлов, а поведение интерфейса относится к отдельному этапу исполнения. Эти границы полезно сохранять даже у маленькой учебной программы.

Когда захотите усилить правило проекта, найдите конкретное место, где оно помогает выразить намерение. Здесь отсутствие узла, отсутствие description и чтение массива уже имеют такие места. Это понятнее, чем включать произвольные флаги, а затем подавлять их последствия в каждом исходнике.

В следующем уроке добавим небольшую JavaScript-зависимость и опишем её публичную функцию через декларацию, сохранив различие между настоящей реализацией и сведениями для проверки типов.

Оглавление курса.