Я большой фанат TypeScript. Каждый свой новый проект я
предпочитаю писать на нём, а не на чистом JavaScript. В данной
статье я не буду рассматривать причины выбора TypeScript или о его
преимуществах и недостатках. Я хочу, чтобы данный пост стал своего
рода шпаргалкой для тех, кто хочет понять, как настраивать
tsconfig
, разложить по полочкам его многочисленные
флаги и, возможно, узнать некоторые полезные трюки.
Итак, в данной статье я хочу предоставить переработанную и упорядоченную выжимку документации, которая, я уверен, будет полезна тем, кто только начинает свой путь в мире TypeScript или тем, кто до этого момента не нашёл времени и сил, чтобы разобраться в деталях и теперь хочет закрыть этот пробел.
Если открыть официальный референс tsconfig
, то там
будет полный список всех настроек, разделённых по группам. Однако,
это не даёт понимания, с чего начать и что из данного обширного
списка опций обязательно, а на что можно не обращать внимания (по
крайней мере до поры до времени). Плюс, иногда опции сгруппированы
по некому техническому, а не логическому смыслу. Например,
некоторые флаги проверок можно найти в группе Strict
Checks
, некоторые в Linter Checks
, а другие в
Advanced
. Это не всегда удобно для понимания.
Все опции, как и саму статью, я разделил на две группы базовые и "проверки". В первой части статьи разговор пойдёт про базовые настройки, а во второй уже про различные проверки, т. е. про тюнинг строгости компилятора.
Структура tsconfig
Рассмотрим структуру и некоторые особенности конфига.
-
tsconfig.json
состоит из двух частей. Какие-то опции необходимо указывать вroot
, а какие-то вcompilerOptions
-
tsconfig.json
поддерживает комментарии. Такие IDE как WebStorm и Visual Studio Code знают об этом не выделяют комментарии как синтаксическую ошибку -
tsconfig.json
поддерживает наследование. Опции можно разделить по некоторому принципу, описать их в разных файлах и объединить с помощью специальной директивы
Это болванка нашего tsconfig.json
:
{ // extends позволяет обогатить опции другими опциями из указанного файла // файлом tsconfig-checks.json займёмся во второй части статьи "extends": "./tsconfig-checks.json", // в корне конфига находятся project-specific опции "compilerOptions": { // здесь все настройки, связанные с компилятором }}
К root
опциям относится только следующие:
extends
, files
, include
,
exclude
, references
,
typeAcquisition
. Из них мы будем рассматривать первые
4. Все остальные опции размещаются в
compilerOptions
.
Иногда в root
секции конфига можно
встретить такие опции как compileOnSave
и ts-node
. Эти опции не являются
официальными и используются IDE для своих целей.
Секция root
extends
Type: string | false, default: false.
Указывает путь к файлу из которого нужно унаследовать опции. По
большей части, служит инструментом упорядочивания. Можно разделить
опции по некой логике, чтобы они не смешивались. Например, вынести
настройки строгости в отдельный файл, как в примере болванки
конфига. Однако, учитывая поддержку комментариев в
tsconfig.json
это можно сделать проще:
{ "compilerOptions": { // блок базовых настроек // блок настроек строгости }}
Рассмотрим другой use-case, где комментариями отделаться не
получится. Если необходимо создать production и development
конфиги. Так бы мог выглядеть tsconfig-dev.json
версия
конфига:
{ "extends": "./tsconfig.json", "compilerOptions": { // переопределяем настройки, которые нужны только для dev режима "sourceMap": true, "watch": true }}
В целом, я рекомендую пользоваться extends
. Однако,
сильно дробить настройки не рекомендую. Это может привести к
запутыванию. В том числе по причине того, что множественное
наследование не поддерживается.
Если вы решите использовать эту опцию. То увидеть итоговую,
объединённую версию конфига поможет команда tsc
--showConfig
.
files
Type: string[] | false, default: false, связана
с include
.
Указать список конкретных файлов для компиляции можно использовав данную опцию.
{ "compilerOptions": {}, "files": [ "core.ts", "app.ts" ]}
Данная опция подходит лишь для совсем маленьких проектов из нескольких файлов.
include
Type string[], default: зависит от значения
files
, связана с
exclude
.
Если опция files
не указана, то TypeScript будет
использовать эту директиву для поиска компилируемых файлов. Если
include
так же не указана, то её значение будет неявно
объявлено как ["**/*"]
. Это означает, что поиск файлов
будет осуществляться во всех папках и их подпапках. Такое поведение
не оптимально, поэтому в целях производительности лучше всегда
указывать конкретные пути. Можно прописывать как пути к конкретным
файлам, так и паттерны путей.
{ "compilerOptions": {}, "include": [ "src/**/*", "tests/**/*" ]}
Если паттерны не указывают конкретных расширений, то TypeScript
будет искать файлы с расширениями .ts
,
.tsx
, and .d.ts
. А если включен флаг
allowJs
, то ещё .js
и
.jsx
.
Следующие форматы записей делают одно и тоже
src
, ./src
,
src/**/*
. Я предпочитаю вариант
./src
.
Технически, используя опции include
и
exclude
, TypeScript сгенерирует список всех
подходящих файлов и поместит их в files
. Это
можно наблюдать если выполнить команду tsc
--showConfig
.
exclude
Type: string[], default: ["nodemodules", "bowercomponents", "jspm_packages"].
Директива служит для того, чтобы исключать некоторые лишние пути
или файлы, которые включились директивой include
. По
умолчанию, опция имеет значение путей пакетных менеджеров
npm
, bower
и jspm
, так как
модули в них уже собраны. Помимо этого, TypeScript будет так же
игнорировать папку из опции outDir
, если она указана.
Это папка, куда помещаются собранные артефакты сборки. Логично, что
их нужно исключить. Если добавить свои значения в эту опцию, то
необходимо не забыть восстановить умолчания. Так как
пользовательские значения не объединяются со значениями по
умолчанию. Другими словами, необходимо вручную указать корень
модулей своего пакетного менеджера.
{ "compilerOptions": {}, "exclude": [ "node_modules", "./src/**/*.spec.ts" ]}
Опция exclude
не может исключить
файлы, указанные через files
.
Опция exclude
не может исключить
файлы, если они импортируются в других файлах, которые не
исключены.
Секция compilerOptions
target
Type: string, default:
ES3
, влияет на опции
lib
,
module
.
Версия стандарта ECMAScript, в которую будет скомпилирован код.
Здесь большой выбор: ES3
, ES5
,
ES6
(он же ES2015
), ES2016
,
ES2017
, ES2018
, ES2019
,
ES2020
, ESNext
. Для backend
приложений/пакетов подойдёт ES6
, если рассчитываете
только на современные версии Node.js
и
ES5
, если хотите поддержать более старые версии. На
данный момент стандарт ES6
, с небольшими оговорками,
поддерживается 97.29% браузеров. Так что для
frontend приложений ситуация аналогичная.
module
Type: string, default: зависит от
target
, влияет на опцию
moduleResolution
.
Модульная система, которую будет использовать ваше собранное
приложение. На выбор: None
, CommonJS
,
AMD
, System
, UMD
,
ES6
, ES2015
, ES2020
or
ESNext
. Для backend приложений/пакетов подойдёт
ES6
или CommonJS
в зависимости от версий
Node.js
, которые хотите поддерживать. Для frontend
приложений под современные браузеры также подходит
ES6
. А для поддержки более старых браузеров и для
изоморфных приложений, определённо стоит выбрать
UMD
.
Если ваша ситуация не такая простая или хотите знать все тонкости модульных систем, тогда придётся всё-таки изучить подробную документацию.
moduleResolution
Type: string, default: зависит от
module
.
Стратегия, которая будет использоваться для импорта модулей.
Здесь всего две опции: node
и classic
.
При этом classic
в 99% не будет использоваться, так
как это legacy. Однако, я специально упомянул этот флаг, так как он
меняется в зависимости от предыдущего флага. При изменении значения
module
режим moduleResolution
может
переключиться на classic
и в консоли начнут появляться
сообщения об ошибках на строчках с импортами.
Во избежание описанной ситуации, я рекомендую всегда явно
указывать значение node
для данного флага.
lib
Type: string[], default: зависит от
target
.
В зависимости от того какой target
установлен в
конфиге, TypeScript подключает тайпинги (*.d.ts-файлы
)
для поддержки соответствующих спецификаций. Например, если ваш
target
установлен в ES6
, то TypeScript
подключит поддержку array.find
и прочих вещей, которые
есть в стандарте. Но если target
стоит
ES5
, то использовать метод массива find
нельзя, так как его не существует в этой версии JavaScript. Можно
подключить полифилы. Однако, для того, чтобы TypeScript понял, что
теперь данную функциональность можно использовать, необходимо
подключить необходимые тайпинги в секции lib
. При
этом, можно подключить как весь стандарт ES2015
, так и
его часть ES2015.Core
(только методы
find
, findInex
и т.д.).
Конечно, правильным выбором будет подключать тайпинги только той функциональности, для которой установлены полифилы.
Для --target ES5 подключаются: DOM, ES5, ScriptHostДля --target ES6: DOM, ES6, DOM.Iterable, ScriptHost
Как только вы что-либо добавляете в lib
умолчания сбрасываются. Необходимо руками добавить то, что
нужно, например DOM
:
{ "compilerOptions": { "target": "ES5", "lib": [ "DOM", "ES2015.Core" ] }}
outDir
Type: string, default: равняется корневой директории.
Конечная папка, куда будут помещаться собранные артефакты. К ним
относятся: .js
, .d.ts
, и
.js.map
файлы. Если оставить не указывать значение для
данной опции, то все вышеуказанные файлы будут повторять структуру
исходных файлов в корне вашего проекта. В таком случае будет сложно
удалять предыдущие билды и описывать .gitignore
файлы.
Да и кодовая база будет похожа на свалку. Советую складывать все
артефакты в одну папку, которую легко удалить или заигнорировать
системой контроля версий.
Если оставить опцию outDir
пустой:
module core.js core.ts index.js index.ts
Если указать outDir
:
dist module | core.js index.js module core.ts index.ts
outFile
Type: string, default: none.
Судя по описанию, данная опция позволяет объединить все файлы в
один. Кажется, что бандлеры вроде webpack
больше не
нужны Однако, опция работает только если значение
module
указано None
, System
,
or AMD
. К огромному сожалению, опция не будет работать
с модулями CommonJS
or ES6
. Поэтому
скорее всего использовать outFile
не придётся. Так как
опция выглядит максимально привлекательно, но работает не так как
ожидается, я решил предупредить вас об этом гигантском подводном
камне.
allowSyntheticDefaultImports
Type: boolean, default: зависит от
module
или
esModuleInterop
.
Если какая-либо библиотека не имеет default import
,
лоадеры вроде ts-loader
или babel-loader
автоматически создают их. Однако, d.ts-файлы
библиотеки об этом не знают. Данный флаг говорит компилятору, что
можно писать следующим образом:
// вместо такого импортаimport * as React from 'react';// можно писать такойimport React from 'react';
Опция включена по умолчанию, если включен флаг
esModuleInterop
или module
=== "system".
esModuleInterop
Type: boolean, default: false.
За счёт добавления болерплейта в выходной код, позволяет
импортировать CommonJS
пакеты как
ES6
.
// библиотека moment экспортируется только как CommonJS// пытаемся импортировать её как ES6import Moment from 'moment';// без флага esModuleInterop результат undefinedconsole.log(Moment);// c флагом результат [object Object]console.log(Moment);
Данный флаг по зависимости активирует
allowSyntheticDefaultImports
. Вместе они помогают
избавиться от зоопарка разных импортов и писать их единообразно по
всему проекту.
alwaysStrict
Type: boolean, default: зависит от
strict
.
Компилятор будет парсить код в strict mode
и
добавлять use strict
в выходные файлы.
По умолчанию false, но если включен флаг
strict
, то true.
downlevelIteration
Type: boolean, default: false.
Спецификация ES6
добавила новый синтаксис для
итерирования: цикл for / of
, array
spread
, arguments spread
. Если код проекта
преобразовывается в ES5
, то конструкция с циклом
for / of
будет преобразована в обычный
for
:
// код es6const str = 'Hello!';for (const s of str) { console.log(s);}
// код es5 без downlevelIterationvar str = "Hello!";for (var _i = 0, str_1 = str; _i < str_1.length; _i++) { var s = str_1[_i]; console.log(s);}
Однако, некоторые символы, такие как emoji
кодируются с помощью двух символов. Т. е. такое преобразование в
некоторых местах будет работать не так, как ожидается. Включенный
флаг downlevelIteration
генерирует более многословный
и более "правильный", но менее производительный код. Код получается
действительно очень большим, поэтому не буду занимать место на
экране. Если интересно посмотреть пример, то перейдите в playground и выберете в
настройках target -> es5
, downlevelIteration
-> true
.
Для работы данного флага, необходимо, чтобы в браузере была
реализация Symbol.iterator
. В противном
случае необходимо установить полифил.
forceConsistentCasingInFileNames
Type: boolean, default: false.
Включает режим чувствительности к регистру (case-sensitive) для
импорта файлов. Таким образом, даже в case-insensitive файловых
системах при попытке сделать импорт import FileManager from
'./FileManager.ts'
, если файл в действительности называется
fileManager.ts
, приведёт к ошибке. Перестраховаться
лишний раз не повредит. TypeScript - это про строгость.
Опции секции compilerOptions, которые нужны не в каждом проекте
declaration
Type: boolean, default: false.
С помощью включения данного флага, помимо JavaScript файлов, к
ним будут генерироваться файлы-аннотации, известные как
d.ts
-файлы или тайпинги. Благодаря тайпингам
становится возможным определение типов для уже скомпилированных js
файлов. Другими словами код попадает в js
, а типы в
d.ts
-файлы. Это полезно в случае, например, если вы
публикуете свой пакет в npm
. Такой библиотекой смогут
пользоваться разработчики, которые пишут как на чистом JavaScript,
так и на TypeScript.
declarationDir
Type: string, default: none, связан с
declaration
.
По умолчанию тайпинги генерируются рядом с
js
-файлами. Используя данную опцию можно перенаправить
все d.ts
-файлы в отдельную папку.
emitDeclarationOnly
Type: boolean, default: false, связан с
declaration
.
Если по какой-то причине вам нужны только
d.ts
-файлы, то включение данного флага предотвратит
генерацию js
-файлов.
allowJs
Type: boolean, default: false.
Портировать ваш JavaScript проект на TypeScript поможет данный
флаг. Активировав allowJs
TypeScript компилятор будет
обрабатывать не только ts
файлы, но и js
.
Нет нужды полностью мигрировать проект, прежде чем продолжить его
разработку. Можно это делать файл за файлом, просто меня расширение
и добавляя типизацию. А новый функционал сразу можно писать на
TypeScript.
checkJs
Type: boolean, default: false, связан с
allowJs
.
TypeScript будет проверять ошибки не только в ts
,
но и в js
-файлах. Помимо встроенных тайпингов для
языковых конструкций JavaScript, TS-компилятор так же умеет
использовать jsDoc для анализа файлов. Я предпочитаю не
использовать этот флаг, а наводить порядок в коде в момент его
типизации. Однако, если в вашем проекте хорошее покрытие кода
jsDoc, стоит попробовать.
С версии 4.1 при включении checkJs
,
флаг allowJs
включается
автоматически.
experimentalDecorators и emitDecoratorMetadata
Type: boolean, default: false.
Декоратор
- это стандартный паттерн из мира ООП и
его можно реализовывать классическим образом, создавая классы или
функции-обёртки. Однако, с помощью двух вышеперечисленных флагов
можно включить экспериментальный синтаксис декораторов. Данный
синтаксис позволяет декорировать классы, их методы и свойства,
модификаторы доступа, а так же аргументы функций используя простой
и распространённый во многих языках программирования синтаксис
@
.
Флаг experimentalDecorators
просто активирует
синтаксис, а emitDecoratorMetadata
в рантайме
предоставляет декораторам дополнительные мета-данные, с помощью
которых можно значительно расширить области применения данной
фичи.
Для работы emitDecoratorMetadata
необходимо подтянуть в проект библиотеку reflect-metadata.
resolveJsonModule
Type: boolean, default: false.
Флаг позволяет включить возможность импортировать
*.json
файлы. Ничего дополнительно устанавливать не
требуется.
// необходимо указывать расширение .jsonimport config from './config.json'
jsx
Type: string, default: none.
Если проект использует React, необходимо включить поддержку
jsx
. В подавляющем большинстве случаев будет
достаточно опций react
или react-native
.
Так же есть возможность оставить jsx-код
нетронутым с
помощью опции preserve
или использовать кастомные
преобразователи react-jsx
и
react-jsx
.
Завершение первой части
В этой статье я расписал самые важные флаги и опции, которые могут понадобиться в подавляющем большинстве проектов. В следующей же части я расскажу про настройку строгости компилятора. Ссылку добавлю после выхода статьи.