
Я большой фанат TypeScript. Каждый свой новый проект я предпочитаю писать на нём, а не на чистом JavaScript. В данной статье я не буду рассматривать причины выбора TypeScript или о его преимуществах и недостатках. Я хочу, чтобы данный пост стал своего рода шпаргалкой для тех, кто хочет понять, как настраивать tsconfig, разложить по полочкам его многочисленные флаги и, возможно, узнать некоторые полезные трюки.
Итак, в данной статье я хочу предоставить переработанную и упорядоченную выжимку документации, которая, я уверен, будет полезна тем, кто только начинает свой путь в мире TypeScript или тем, кто до этого момента не нашёл времени и сил, чтобы разобраться в деталях и теперь хочет закрыть этот пробел.
Если открыть официальный референс tsconfig, то там будет полный список всех настроек, разделённых по группам. Однако, это не даёт понимания, с чего начать и что из данного обширного списка опций обязательно, а на что можно не обращать внимания (по крайней мере до поры до времени). Плюс, иногда опции сгруппированы по некому техническому, а не логическому смыслу. Например, некоторые флаги проверок можно найти в группе Strict Checks, некоторые в Linter Checks, а другие в Advanced. Это не всегда удобно для понимания.
Все опции, как и саму статью, я разделил на две группы – базовые и "проверки". В первой части статьи разговор пойдёт про базовые настройки, а во второй уже про различные проверки, т. е. про тюнинг строгости компилятора.
Структура tsconfig
Рассмотрим структуру и некоторые особенности конфига.
tsconfig.jsonсостоит из двух частей. Какие-то опции необходимо указывать вroot, а какие-то вcompilerOptionstsconfig.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 и .d.ts. А если включен флаг allowJs, то ещё .js и .jsx.
Следующие форматы записей делают одно и тоже src, ./src, src/**/*. Я предпочитаю вариант ./src.
Технически, используя опции include и exclude, TypeScript сгенерирует список всех подходящих файлов и поместит их в files. Это можно наблюдать если выполнить команду tsc --showConfig.
exclude
Type: string[], default: ["node_modules", "bower_components", "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 или 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, findIndex и т. д.).
Конечно, правильным выбором будет подключать тайпинги только той функциональности, для которой установлены полифилы.
Для --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.tsoutFile
Type: string, default: none.
Судя по описанию, данная опция позволяет объединить все файлы в один. Кажется, что бандлеры вроде webpack больше не нужны… Однако, опция работает только если значение module указано None, System или AMD. К огромному сожалению, опция не будет работать с модулями CommonJS или 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
// пытаемся импортировать её как ES6
import Moment from 'moment';
// без флага esModuleInterop результат undefined
console.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:
// код es6
const str = 'Hello!';
for (const s of str) {
console.log(s);
}// код es5 без downlevelIteration
var 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 файлы. Ничего дополнительно устанавливать не требуется.
// необходимо указывать расширение .json
import config from './config.json'jsx
Type: string, default: none.
Если проект использует React, необходимо включить поддержку jsx. В подавляющем большинстве случаев будет достаточно опций react или react-native. Так же есть возможность оставить jsx-код нетронутым с помощью опции preserve или использовать кастомные преобразователи react-jsx и react-jsxdev.
Завершение первой части
В этой статье я расписал самые важные флаги и опции, которые могут понадобиться в подавляющем большинстве проектов. В следующей же части я расскажу про настройку строгости компилятора.
UPD: Здесь можно прочитать вторую часть статьи.
