Контрольный список для tsconfig.json¶
Версия: TypeScript 5.7
Чтобы чувствовать себя более уверенно в своем tsconfig.json
, я решил просмотреть документацию по tsconfig.json
, собрать все часто используемые опции и описать их ниже:
- Вы можете присоединиться ко мне и пройтись по опциям вместе со мной. После этого вы должны быть в состоянии понять весь ваш
tsconfig
. - Или вы можете перейти к краткому описанию в конце.
- Я также привожу рекомендации по
tsconfig.json
от нескольких других людей.
Мне интересно, каков ваш опыт работы с tsconfig.json
: Согласны ли вы с моими рекомендациями? Может быть, я что-то упустил?
Функции, не вошедшие в эту статью¶
В этой статье описывается только создание проектов, локальные модули которых состоят из ESM. Однако в ней даются советы по импорту CommonJS.
Здесь не объясняется:
- Импорт и проверка типа обычного JavaScript в вашей кодовой базе, а именно опции
allowJs
иcheckJs
. - Как настроить JSX. См. раздел «JSX» в TypeScript Handbook.
- «Проекты» (полезно для монорепов): опция
composite
и т. д. Дополнительную информацию по этой теме см:- Глава «Ссылки на проекты» в TypeScript Handbook.
- Заметка в моем блоге «Простые монорепозитории с помощью рабочих пространств npm и ссылок на проекты TypeScript»
Нотация¶
Для отображения выводимых типов в исходном коде я использую пакет npm ts-expect
- например:
1 2 |
|
Я часто использую запятые в конце JSON, потому что это поддерживается в tsconfig.json
и потому что это помогает при перестановке, копировании и т. д.
Расширение базовых файлов с помощью extends
¶
Эта опция позволяет нам ссылаться на существующий tsconfig.json
через спецификатор модуля (как если бы мы импортировали JSON-файл). Этот файл становится базой, которую расширяет наш tsconfig
. Это означает, что наш tsconfig
имеет все опции базы, но может переопределять любые из них и добавлять опции, не упомянутые в базе.
В репозитории GitHub tsconfig/bases
перечислены базы, которые доступны в пространстве имен npm @tsconfig
и могут быть использованы подобным образом (после их локальной установки через npm):
1 2 3 |
|
Увы, ни один из этих файлов не подходит для моих нужд. Но они могут послужить вдохновением для вашего tsconfig
.
Где находятся входные файлы?¶
1 2 3 |
|
С одной стороны, мы должны указать TypeScript, какие файлы будут использоваться для ввода. Вот доступные варианты:
files
: исчерпывающий массив всех входных файловinclude
: Указывает входные файлы через массив шаблонов с подстановочными знаками, которые интерпретируются как относительные кtsconfig.json
.exlude
: Указывает, какие файлы должны быть исключены из набора включаемых файлов - с помощью массива шаблонов.
Каков результат?¶
Куда записываются выходные файлы?¶
1 2 3 4 5 6 |
|
Как TypeScript определяет, куда записать выходной файл:
- Берется входной путь (относительно
tsconfig.json
), - удаляет префикс, указанный в
rootDir
, и - «добавляет» результат в
outDir
.
По умолчанию значение rootDir
- это самый длинный общий префикс относительных путей входных файлов.
В качестве примера рассмотрим следующий tsconfig.json
:
1 2 3 4 5 6 7 |
|
Это файловая структура проекта:
1 2 3 4 5 6 |
|
Компилятор TypeScript выдает такой результат:
1 2 3 4 5 6 |
|
Если мы удалим rootDir
из tsconfig.json
, то результат будет таким же, потому что его значение по умолчанию - «.
».
Однако результат будет другим, если мы также изменим include
:
1 2 3 4 5 6 |
|
Теперь значение по умолчанию для rootDir
равно src
, и вывод будет таким:
1 2 3 |
|
Поскольку значение rootDir
по умолчанию меняется в зависимости от include
, я предпочитаю указывать его явно в tsconfig.json
. Но вы можете не указывать его, если вас устраивает то, как работает значение по умолчанию.
Карты исходного кода¶
1 2 3 4 5 |
|
sourceMap
создает файлы карты исходных текстов, которые указывают на исходный TypeScript из транспилированного JavaScript. Это помогает при отладке и обычно является хорошей идеей.
Создание файлов .d.ts
(например, для библиотек)¶
Если мы хотим, чтобы код TypeScript потреблял наш транспилированный код TypeScript, нам обычно нужно включать файлы .d.ts
:
1 2 3 4 5 6 |
|
В качестве опции мы можем включить исходный код TypeScript в наш пакет npm и активировать declarationMap
. Импортеры могут, например, щелкнуть на типах или перейти к определению значения, а их редактор отправит их к исходному коду.
Опция declarationDir
¶
По умолчанию каждый файл .d.ts
помещается рядом с файлом .js
. Если вы хотите изменить это, вы можете использовать опцию declarationDir
.
Тонкая настройка выдаваемых файлов¶
1 2 3 4 5 6 |
|
Значения, указанные выше, являются значениями по умолчанию.
newLine
настраивает окончания строк для выдаваемых файлов. Допустимыми значениями являются:"lf": "\n"
(Unix)"crlf": "\r\n"
(Windows)
removeComments
: Если активна, все комментарии в файлах TypeScript будут опущены в транслируемых файлах JavaScript. Я слабо склоняюсь к тому, чтобы оставить значение по умолчанию и не удалять комментарии:- Это помогает при чтении транслированного JavaScript - особенно если исходный код TypeScript не включен.
- Сборщики удаляют комментарии.
- На Node.js дополнительное бремя не имеет большого значения.
Особенности языка и платформы¶
1 2 3 4 5 6 7 |
|
target
¶
target
определяет, какой новый синтаксис JavaScript будет транспонирован в старый синтаксис. Например, если target - "ES5"
, то стрелочная функция () => {}
будет транспонирована в функцию выражения function () {}
.
- Рекомендуемые настройки для различных платформ можно посмотреть в репозитории GitHub
tsconfig/bases
. - Значение
"ESNext"
означает «наивысшая версия, поддерживаемая установленным TypeScript». Поскольку это значение меняется между версиями TypeScript, оно может вызвать проблемы при обновлении.
Я задаюсь вопросом, должна ли быть опция, позволяющая никогда не транспонировать JavaScript-функции. С другой стороны, возможность писать современный JavaScript на потенциально старых браузерах весьма удобна.
Как выбрать хорошую целевую платформу¶
Мы должны выбрать версию ECMAScript, которая подходит для наших целевых платформ. Есть две таблицы, которые дают хороший обзор:
- Для браузеров: compat-table.github.io
- Для Node.js: node.green
Мы также можем посмотреть официальные базы tsconfig, которые предоставляют значения для target
.
lib
¶
lib
определяет, какие типы для встроенных API доступны - например, Math
или методы встроенных типов:
- В документации TypeScript описано, какие значения можно добавлять в массив. Полный их список можно найти в репозитории TypeScript. Если вы ищете тип, то ищите его в этих файлах!
- Там есть такие категории, как
"ES2024"
и"DOM"
, и такие подкатегории, как"DOM.Iterable"
и"ES2024.Promise"
. - Значения не зависят от регистра: Предложения автозаполнения Visual Studio Code содержат много заглавных букв, а имена файлов - ни одной. Значения
lib
могут быть записаны в любом случае.
Когда TypeScript поддерживает тот или иной API? Он должен быть «доступен без префиксов/флагов как минимум в 2 браузерных движках (то есть не только в 2 браузерах chromium)» (источник).
Настройка lib
через target
¶
target
определяет значение lib
по умолчанию: Если последний параметр опущен, а target
- "ES20YY"
, то будет использоваться "ES20YY.Full"
. Однако это не то значение, которое мы можем использовать сами. Если мы хотим воспроизвести то, что делает удаление lib
, мы должны сами перечислить содержимое (например, es2024.full.d.ts
):
1 2 3 4 5 6 |
|
В этом файле мы можем наблюдать интересное явление:
- Категория
"ES20YY"
обычно включает все свои подкатегории. - Категория
"DOM"
не включает - например, подкатегория"DOM.Iterable"
еще не является ее частью.
Помимо прочего, "DOM.Iterable"
позволяет выполнять итерацию над списками NodeLists - например:
1 2 |
|
Типы для встроенных API Node.js¶
Типы для API Node.js должны быть установлены с помощью пакета npm:
1 |
|
Система модулей¶
module
: Как TypeScript будет искать импортированные модули?¶
Эти опции влияют на то, как TypeScript ищет импортированные модули:
1 2 3 4 5 6 |
|
Опция module
¶
С помощью этой опции мы определяем системы для работы с модулями. Если мы правильно настроим ее, то позаботимся и о связанной с ней опции moduleResolution
, для которой она предоставляет хорошие значения по умолчанию. Документация TypeScript рекомендует использовать одно из двух следующих значений:
- Node.js:
"NodeNext"
поддерживает как CommonJS, так и последние возможности ESM.- Подразумевает
"moduleResolution": "NodeNext"
.
- Подразумевает
- Bundlers:
"Preserve"
поддерживает как CommonJS, так и последние возможности ESM. Это поведение соответствует тому, что делает большинство бандлеров.- Подразумевает
"moduleResolution": "bundler"
.
- Подразумевает
Учитывая, что бандлеры в основном имитируют работу Node.js, я всегда использую "NodeNext"
и не сталкивался с какими-либо проблемами.
Обратите внимание, что в обоих случаях TypeScript заставляет нас указывать полные имена локальных модулей, которые мы импортируем. Мы не можем опускать расширения имен файлов, как это часто делалось, когда Node.js компилировался только в CommonJS. Новый подход отражает то, как работает ESM на чистом JavaScript.
Опция noUncheckedSideEffectImports
¶
По умолчанию TypeScript не жалуется, если пустой импорт не существует. Причина такого поведения заключается в том, что это шаблон, поддерживаемый некоторыми бандлерами для связывания артефактов, не относящихся к TypeScript, с модулями. А TypeScript видит только файлы TypeScript. Вот как выглядит такой импорт:
1 |
|
Интересно, что TypeScript нормально воспринимает несуществующие файлы TypeScript, импортированные в пустоту. Он жалуется только в том случае, если мы импортируем что-то из несуществующего файла.
1 |
|
Установка значения noUncheckedSideEffectImports
в true
изменит это. Позже я расскажу об альтернативе для импорта артефактов, не относящихся к TypeScript.
Запуск TypeScript напрямую (без генерации JS-файлов)¶
Большинство небраузерных JavaScript-платформ теперь могут запускать код TypeScript напрямую, без его транспонирования.
1 2 3 4 5 6 7 |
|
allowImportingTsExtensions
: Эта опция позволяет нам при импорте ссылаться на TypeScript-версию модуля, а не на его транслируемую версию.rewriteRelativeImportExtensions
: С помощью этой опции мы также можем транслировать код TypeScript, предназначенный для непосредственного выполнения. По умолчанию TypeScript не изменяет спецификаторы модулей при импорте. Эта опция имеет несколько оговорок:- Переписываются только относительные пути.
- Они переписываются «наивно» - без учета опций
baseUrl
иpaths
(которые выходят за рамки этой статьи). - Пути, проложенные через свойства
"exports"
и"imports"
, не выглядят как относительные пути и поэтому также не переписываются.
Если вы хотите использовать tsc
для проверки типов (только), то обратите внимание на раздел, посвященный опции noEmit
.
Для получения дополнительной информации о встроенной поддержке TypeScript в Node вы можете прочитать мою статью в блоге.
Импортирование JSON¶
1 2 3 4 5 |
|
Опция resolveJsonModule
позволяет импортировать файлы JSON:
1 2 |
|
Импорт других артефактов, не относящихся к TypeScript¶
Всякий раз, когда мы импортируем файл basename.ext
, расширение которого ext
TypeScript не знает, он ищет файл basename.d.ext.ts
. Если он его не находит, то выдает ошибку. В документации по TypeScript есть хороший пример того, как может выглядеть такой файл.
Есть два способа предотвратить появление в TypeScript ошибок, связанных с неизвестным импортом.
Во-первых, мы можем использовать опцию allowArbitraryExtensions
, чтобы предотвратить любое сообщение об ошибке в этом случае.
Во-вторых, мы можем создать объявление окружающего модуля со спецификатором wildcard - файл .d.ts
, который должен находиться где-то среди файлов, о которых знает TypeScript. Следующий пример подавляет ошибки для всех импортов с расширением имени файла .css
:
1 2 |
|
Проверка типов¶
1 2 3 4 5 6 7 8 9 10 11 |
|
strict
является обязательным, на мой взгляд. С остальными настройками вы должны сами решить, нужна ли вам дополнительная строгость для вашего кода. Вы можете начать с добавления всех из них и посмотреть, какие из них вызывают слишком много проблем на ваш вкус. В этом разделе мы будем игнорировать настройки, которые покрываются strict
(например, noImplicitAny
).
noFallthroughCasesInSwitch
: Еслиtrue
, то непустые случаиswitch
должны заканчиватьсяbreak
,return
илиthrow
.noImplicitOverride
: Еслиtrue
, то методы, переопределяющие методы суперкласса, должны иметь модификаторoverride
.noImplicitReturns
: Еслиtrue
, то «неявный возврат» (завершение функции или метода) разрешен только в том случае, если тип возврата -void
.
exactOptionalPropertyTypes
¶
Если true
, то .colorTheme
может быть только опущен, а не установлен в значение undefined
, как в следующем примере:
1 2 3 4 5 6 |
|
noPropertyAccessFromIndexSignature
¶
Если true
, то для типов, подобных следующему, мы не можем использовать точечную нотацию для неизвестных свойств, только для известных:
1 2 3 4 5 6 7 8 9 |
|
noUncheckedIndexedAccess
¶
Если true
, то тип неизвестного свойства является объединением undefined
и типа индексной подписи:
1 2 3 4 5 6 7 |
|
noUncheckedIndexedAccess
и массивы¶
Опция noUncheckedIndexedAccess
также влияет на работу с массивами:
1 2 3 |
|
Если этот параметр равен false
, то elem
имеет тип string
.
Одним из распространенных шаблонов для массивов является проверка длины перед обращением к элементу. Однако эта схема становится неудобной при использовании noUncheckedIndexedAccess
:
1 2 3 4 5 6 7 |
|
Поэтому разумнее использовать другой шаблон:
1 2 3 4 5 6 7 |
|
Я размышляю над этим вариантом: С одной стороны, новый шаблон отражает, что массивы могут содержать дыры. С другой стороны, дыры встречаются редко, и, начиная с ES6, JavaScript делает вид, что это элементы, имеющие значение undefined
:
1 2 |
|
Параметры проверки типов, которые имеют хорошие значения по умолчанию¶
По умолчанию следующие опции выдают предупреждения в редакторах, но мы также можем выбрать выдачу ошибок компилятора или игнорирование проблем:
allowUnreachableCode
allowUnusedLabels
noUnusedLocals
noUnusedParameters
Совместимость: помощь внешним инструментам в компиляции TypeScript в JavaScript и декларации¶
1 2 3 4 5 6 |
|
Компилятор TypeScript выполняет три задачи:
- Проверка типов
- Эмиссия файлов JavaScript
- Эмиссия файлов деклараций
В настоящее время стали популярны внешние инструменты, которые выполняют задачи #2 и #3 гораздо быстрее. У них есть два требования:
- Выдача выходного файла не должна требовать поиска информации в файлах, импортированных входным файлом.
- Он также не должен требовать семантического анализа; только синтаксический анализ.
Есть две настройки, которые обеспечивают статическое соблюдение этих ограничений - они вызывают ошибки компилятора, но не изменяют способ вывода JavaScript и деклараций:
verbatimModuleSyntax
помогает при компиляции TypeScript в JavaScript.isolatedDeclarations
помогает компилировать TypeScript в декларации.
verbatimModuleSyntax
: компиляция TypeScript в JavaScript¶
Большинство частей файла TypeScript, не относящихся к JavaScript, легко обнаружить. Исключением являются импорты: Без (относительно простого) семантического анализа мы не знаем, является ли импорт типом (TypeScript) или значением (JavaScript).
Если активен verbatimModuleSyntax
, мы вынуждены добавлять ключевое слово type
к импортам, относящимся только к типам - например:
1 2 3 4 5 |
|
Обратите внимание, что класс - это и значение, и тип. В этом случае ключевое слово type
не нужно, потому что эта часть синтаксиса может оставаться в обычном JavaScript.
Нам также нужно добавить type
, если мы упоминаем тип в предложении экспорта:
1 2 3 4 5 |
|
isolatedModules
¶
Активация verbatimModuleSyntax
также активирует isolatedModules
, поэтому нам нужна только первая настройка. Вторая не позволяет нам использовать некоторые более непонятные функции, которые также являются проблематичными.
Кроме того, эта опция позволяет esbuild компилировать TypeScript в JavaScript (исходный текст).
isolatedDeclarations
: компиляция TypeScript в декларации¶
isolatedDeclarations
в основном заставляет нас добавлять аннотации возвращаемого типа к экспортируемым функциям и методам. Это означает, что внешним инструментам не придется выводить возвращаемые типы.
Дополнительное чтение: В примечаниях к выпуску TypeScript 5.5 есть обширный раздел, посвященный изолированным объявлениям.
noEmit
: использование tsc только для проверки типов¶
Иногда мы хотим использовать tsc только для проверки типов - например, если мы запускаем TypeScript напрямую или используем внешние инструменты для компиляции файлов TypeScript (в файлы JavaScript, файлы деклараций и т. д.):
1 2 3 4 5 |
|
noEmit
: Если значение равноtrue
, мы можем запуститьtsc
, и он будет проверять только код TypeScript, но не будет выдавать никаких файлов.
Нужно ли дополнительно удалять опции, связанные с выводом, зависит от того, какие из них используются вашими внешними инструментами.
Импорт CommonJS из ESM¶
Одна из ключевых проблем связана с импортом модуля CommonJS из модуля ESM:
- В ESM экспорт по умолчанию - это свойство
.default
объекта пространства имен модуля. - В CommonJS экспортом по умолчанию является объект модуля - например, многие модули CommonJS устанавливают
module.exports
в функцию.
Давайте рассмотрим две опции, которые помогут вам в этом.
allowSyntheticDefaultImports
: проверка типов импорта по умолчанию модулей CommonJS¶
Эта опция влияет только на проверку типов, но не на JavaScript-код, создаваемый TypeScript: Если она активна, импорт по умолчанию модуля CommonJS будет ссылаться на module.exports
(а не на module.exports.default
) - но только если нет module.exports.default
.
Это отражает то, как Node.js обрабатывает импорт по умолчанию модулей CommonJS (источник): «При импорте модулей CommonJS объект module.exports
предоставляется в качестве экспорта по умолчанию. Могут быть доступны именованные экспорты, предоставляемые статическим анализом в качестве удобства для лучшей совместимости с экосистемой.»
Нужна ли нам эта опция? Да, но она автоматически активируется, если moduleResolution
имеет значение "bundler"
или если module
имеет значение "NodeNext"
(что активирует esModuleInterop
, который активирует allowSyntheticDefaultImports
).
esModuleInterop
: улучшенная компиляция TypeScript в код CommonJS¶
Эта опция влияет на эмулируемый код CommonJS:
- Если
false
:import * as m from 'm'
компилируется вconst m = require('m')
.import m from 'm'
(приблизительно) компилируется вconst m = require('m')
, а каждое обращение кm
компилируется вm.default
.
- Если
true
:import * as m from 'm'
присваиваетm
новый объект, который имеет те же свойства, что иmodule.exports
, плюс свойство.default
, которое ссылается наmodule.exports
.import m from 'm'
присваивает новый объектm
, который имеет единственное свойство.default
, ссылающееся наmodule.exports
. Каждое обращение кm
компилируется вm.default
.
- Если модуль CommonJS имеет маркерное свойство
.__esModule
, то он всегда импортируется так, как если быesModuleInterop
был выключен.
Нужна ли нам эта опция? Нет, так как мы создаем только модули ESM.
Другие опции с хорошими значениями по умолчанию¶
Обычно мы можем игнорировать эти опции:
moduleDetection
: Эта опция определяет, как TypeScript определяет, является ли файл скриптом или модулем. Обычно его можно не указывать, потому что его значение по умолчанию"auto"
хорошо работает в большинстве случаев. Вам нужно явно установить значение"force"
, только если в вашей кодовой базе есть модуль, у которого нет ни импорта, ни экспорта. Еслиmodule
имеет значение"NodeNext"
и вpackage.json
есть"type": "module"
, то даже эти файлы будут интерпретироваться как модули.skipLibCheck
: Если вы не делаете ничего сложного с файлами деклараций, вы можете проигнорировать эту опцию и просто использовать настройку по умолчанию. Согласно обсуждению на GitHub, установка этого параметра вtrue
(по умолчаниюfalse
) имеет много минусов.
Параметры package.json
, учитываемые TypeScript¶
TypeScript учитывает несколько свойств package.json
:
-
type
: Это важный параметр. Если вы компилируете в модули ESM, вашpackage.json
всегда должен содержать:1 2 3
{ "type": "module" }
-
exports
определяет, какие файлы пакета будут публично видны, и ремапирует пути (чтобы то, что видят импортеры, отличалось от реальных внутренних путей). Все эти настройки могут применяться условно - в зависимости от среды импорта (браузеры, Node.js и т. д.). Более подробную информацию можно найти в моем блоге в статье «TypeScript и нативный ESM на Node.js». Одна из интересных особенностей экспорта пакетов заключается в том, что мы можем ссылаться на свой собственный пакет через «голый» импорт, и при этом будут применяться правила экспорта пакетов. Это полезно для модульных тестов. -
imports
позволяет нам определять сокращения, такие как#util
, для внутренних модулей и внешних пакетов. Более подробную информацию можно найти в главе «Пакеты: Единицы JavaScript для распространения программного обеспечения» моей книги „Shell scripting with Node.js“.
Visual Studio Code¶
Если вас не устраивают спецификаторы модулей для локального импорта в автоматически создаваемых импортах, то вы можете воспользоваться следующими двумя настройками:
1 2 |
|
По умолчанию VSC должен быть достаточно умным, чтобы добавлять расширения имен файлов там, где это необходимо.
Резюме¶
После проведения исследований для этой статьи в блоге, это «база», которую я использую в настоящее время. В следующих подразделах объясняется, как адаптировать ее для различных случаев использования.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
|
Примечания:
- Для получения дополнительной информации о выборе хорошего
target
см. раздел, посвященный этой теме, ранее в этом блоге. verbatimModuleSyntax
: Мне нравятся ограничения, которые он накладывает на мой код: tsc работает без них, но они необходимы для многих инструментов, которые компилируют TypeScript в JavaScript.
npm package (библиотеки и т. д.)¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Примечание: Если ваша библиотека использует DOM, вам следует убрать "lib"
.
Я бы с удовольствием всегда использовал isolatedDeclarations
, но TypeScript позволяет это делать только в том случае, если активна опция declaration
или опция composite
. Джейк Бейли объясняет, почему это так:
На уровне реализации диагностика isolatedDeclarations
- это дополнительная диагностика декларации, производимая трансформатором декларации, которую мы запускаем только тогда, когда declaration
включена.
Теоретически можно реализовать так, что isolatedDeclarations
включает эти проверки (диагностика на самом деле происходит от того, что мы запускаем трансформатор и затем отбрасываем полученный AST), но это изменение по сравнению с первоначальным дизайном.
Приложение Node.js¶
1 2 3 4 5 6 7 |
|
Веб-приложение¶
"module":"NodeNext"
должен хорошо работать и для бандлеров. Но вы можете перейти на более специфичный для бандлеров "module":"preserve"
.
Запуск TypeScript напрямую¶
1 2 3 4 5 6 7 |
|
Дополнительную информацию см. в разделе «Выполнение TypeScript напрямую».
Использование tsc только для проверки типов¶
1 2 3 4 5 |
|
Дополнительные сведения см. в разделе «Использование tsc только для проверки типов».
Рекомендации по использованию tsconfig.json
от других людей¶
- Мэтт Покок «Шпаргалка по TSConfig»
base.json
Пелле Вессманаtsconfig.json
Синдре Сорхуса
Источники этой статьи в блоге¶
Некоторые источники уже были упомянуты ранее. Это дополнительные источники, которые я использовал:
- Официальная документация TSConfig
- Раздел «Path Rewriting for Relative Paths» в анонсе TypeScript 5.7.
- В документации по esbuild содержатся интересные наблюдения о компиляции TypeScript.
Источник — https://2ality.com/2025/01/tsconfig-json.html