В архітектурі сучасних вебзастосунків файл tsconfig.json виконує роль кореневого маніфесту статичного аналізатора та компілятора. Некоректна конфігурація компілятора (tsc) здатна знецінити систему типів: пропускати звернення до неіснуючих властивостей об'єктів у рантаймі, генерувати некоректний ESM-код під час транспіляції та приховувати несумісності дуальних пакетів (CJS/ESM).
Історично опція "strict": true сприймалася як вичерпний прапорець максимальної суворості. Проте в корпоративних застосунках прапорець strict активує лише базову підмножину перевірок. Критично важливі прапорці типобезпеки — такі як noUncheckedIndexedAccess та exactOptionalPropertyTypes — свідомо виключені з парасолькового прапорця strict для зворотної сумісності з легасі-кодом.
Із переходом індустрії на сучасні бандлери (Vite, Turbopack, Rspack, Bun) і нативну модульну систему Node.js кардинально змінилася й модель резолвінгу модулів: застарілі стратегії (moduleResolution: "node", "node10") витіснені спеціалізованими алгоритмами moduleResolution: "bundler" та moduleResolution: "nodenext".
Взаємодію прапорців компілятора TypeScript можна розділити на три незалежні контури відповідальності: контур перевірки типів (Type Checking), контур розв'язання залежностей (Module Resolution) і контур генерації/емісії коду (Emit & Transpilation).
+-----------------------------------------------------------------------+
| АРХІТЕКТУРНІ КОНТУРИ TSCONFIG |
+-----------------------------------------------------------------------+
| 1. КОНТУР ТИПОБЕЗПЕКИ (Type Checking) |
| "strict": true |
| +-- strictNullChecks, noImplicitAny, strictFunctionTypes |
| Enterprise Hardening (Поза межами строгості): |
| +-- noUncheckedIndexedAccess (безпечні масиви та мапи) |
| +-- exactOptionalPropertyTypes (розрізнення відсутності та undef)|
+-----------------------------------------------------------------------+
|
v
+-----------------------------------------------------------------------+
| 2. КОНТУР РОЗВ'ЯЗАННЯ МОДУЛІВ (Module Resolution) |
| Node.js Runtime: Bundler / Modern Tooling: |
| "module": "nodenext" "module": "esnext" |
| "moduleResolution": "moduleResolution": "bundler" |
| "nodenext" (ESM strict) (Vite / Next.js App Router / Rspack) |
+-----------------------------------------------------------------------+
|
v
+-----------------------------------------------------------------------+
| 3. КОНТУР ТРАНСПІЛЯЦІЇ ТА ЕМІСІЇ (Transpilation & Emit) |
| "isolatedModules": true (гарантія компіляції через esbuild/SWC) |
| "verbatimModuleSyntax": true (суворий type-only імпорт та експорт) |
| "skipLibCheck": true (пропуск валідації .d.ts у node_modules)|
+-----------------------------------------------------------------------+TypeError: Cannot read properties of undefined: Увімкнення noUncheckedIndexedAccess усуває приховані збої під час читання динамічних ключів із JSON-відповідей API.@repo/tsconfig/base.json, @repo/tsconfig/nextjs.json, @repo/tsconfig/library.json) для усунення дублювання конфігурацій.isolatedModules: true та verbatimModuleSyntax: true гарантує, що однофайлові компілятори не спричинять помилок у рантаймі через неперетворені конструкції (const enum чи неявні реекспорти типів).skipLibCheck скорочує час виконання tsc --noEmit у 3–5 разів без втрати типобезпеки бізнес-логіки проєкту.Парасольковий прапорець "strict": true активує 8 ключових перевірок:
noImplicitAny: заборона неявного виведення типу any.strictNullChecks: розмежування null/undefined та конкретних типів значень.strictFunctionTypes: контраваріантне порівняння параметрів функцій.strictBindCallApply: сувора типізація методів bind, call, apply.strictPropertyInitialization: обов'язкова ініціалізація полів класів у конструкторі.noImplicitThis: заборона неявного типу this.alwaysStrict: генерація директиви "use strict" у кожному скомпільованому файлі.useUnknownInCatchVariables: приведення змінної в catch (err) до типу unknown замість any.Проте для досягнення безкомпромісної надійності ентерпрайз-рівня необхідні додаткові прапорці:
// Демонстрація ефекту прапорця noUncheckedIndexedAccess
interface MetricRegistry {
[metricName: string]: number;
}
function computeThroughput(registry: MetricRegistry, key: string): number {
const value = registry[key];
// ❌ Без noUncheckedIndexedAccess: тип value виводиться як number.
// У рантаймі відсутній ключ поверне undefined, що спричинить NaN:
// return value * 2;
// ✅ З noUncheckedIndexedAccess: тип value виводиться як number | undefined.
// Компілятор примушує розробника до явної захисної перевірки:
if (typeof value === 'undefined') {
throw new Error(`Метрика "${key}" відсутня в реєстрі`);
}
return value * 2;
}Прапорець exactOptionalPropertyTypes розв'язує фундаментальну колізію між відсутністю ключа в об'єкті та наявністю ключа зі значенням undefined:
// Демонстрація exactOptionalPropertyTypes
interface CacheConfig {
ttlMs?: number; // З прапорцем: ключ може бути відсутній або мати тип number, але НЕ undefined
}
// ❌ Помилка компіляції за exactOptionalPropertyTypes: true:
// Type 'undefined' is not assignable to type 'number' with 'exactOptionalPropertyTypes: true'.
const invalidConfig: CacheConfig = {
ttlMs: undefined,
};
// ✅ Коректні конфігурації:
const validConfig1: CacheConfig = {};
const validConfig2: CacheConfig = { ttlMs: 5000 };У TypeScript 5.0+ прапорець verbatimModuleSyntax встановив єдині та детерміновані правила елімінації типів.
До появи цього прапорця компілятор намагався вгадати, чи є імпортований ідентифікатор значенням або лише типом. Якщо ідентифікатор використовувався виключно в позиціях типів, tsc мовчки видаляв увесь import, що ламало імпорти з побічними ефектами (side-effects).
// З verbatimModuleSyntax: true
// ✅ Явний імпорт типів: гарантовано видаляється з фінального JS-бандла
import type { RequestPayload, UserSession } from '@/entities/session';
// ✅ Звичайний імпорт: гарантовано зберігається як нативний ESM import
import { initializeTracing } from '@/infrastructure/tracer';
// ❌ Помилка компіляції, якщо TokenValidator — це інтерфейс чи тип, а не клас/об'єкт:
// 'TokenValidator' is a type and must be imported using a type-only import when 'verbatimModuleSyntax' is enabled.
import { TokenValidator } from '@/infrastructure/auth';Зчитування кореневого конфігу, розв'язання ланцюжка extends, пошук файлів за glob-масками include/exclude.
Зіставлення імпортів за алгоритмом bundler або nodenext з аналізом полів exports у node_modules.
Обчислення типів вузлів AST, валідація strict-правил, перевірка обмежень noUncheckedIndexedAccess.
Генерація JavaScript-коду (.js), декларацій типів (.d.ts) та карт коду (.d.ts.map).
Нижче наведено production-конфігурацію для сучасних вебзастосунків (Next.js 15+, React 19, Vite, Node.js мікросервіси) з безкомпромісним рівнем суворості:
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Enterprise Strict Modern Base",
"compilerOptions": {
/* Базові параметри середовища та синтаксису */
"target": "ES2022",
"lib": ["DOM", "DOM.Iterable", "ES2022"],
"jsx": "react-jsx",
"module": "ESNext",
"moduleResolution": "bundler",
"moduleDetection": "force",
/* Повний стек суворості (Strict Type-Checking) */
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true,
"useUnknownInCatchVariables": true,
/* Розширений контроль надійності (Beyond Strict) */
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitOverride": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"allowUnreachableCode": false,
"allowUnusedLabels": false,
/* Архітектура модулів і сумісність із бандлерами */
"isolatedModules": true,
"verbatimModuleSyntax": true,
"resolveJsonModule": true,
"allowJs": false,
"checkJs": false,
/* Емісія та карти вихідного коду */
"noEmit": true,
"declaration": false,
"declarationMap": false,
"sourceMap": true,
/* Оптимізація продуктивності компілятора */
"skipLibCheck": true,
"incremental": true
}
}Розробники часто вважають, що прапорець "skipLibCheck": false гарантує повну безпеку стороннього коду. Насправді компілятор починає рекурсивно перевіряти внутрішню валідність усіх .d.ts файлів у каталозі node_modules. За наявності десятків залежностей неминуче виникають конфлікти дубльованих глобальних типів (наприклад, різних версій @types/node), що ламає збірку без жодної користі для прикладного коду.
// ❌ Антипатерн: сповільнює компіляцію в 4-10 разів і ламає білд на сторонніх типах
{
"compilerOptions": {
"skipLibCheck": false
}
}
// ✅ Правильне рішення: перевірка виключно власного вихідного коду
{
"compilerOptions": {
"skipLibCheck": true
}
}При конфігурації "node" компілятор TypeScript шукає пакети за правилами Node.js десятирічної давнини, ігноруючи поле "exports" у package.json сторонніх бібліотек. Як наслідок, TypeScript валідує імпорти, які падають під час збірки або виконання бандлером.
// ❌ Застарілий конфіг: не підтримує сучасні conditional exports
{
"compilerOptions": {
"module": "esnext",
"moduleResolution": "node"
}
}
// ✅ Сучасний стандарт для бандлерів (Next.js, Vite, Rspack)
{
"compilerOptions": {
"module": "esnext",
"moduleResolution": "bundler"
}
}Vite і Next.js транспілюють кожен файл ізольовано за допомогою esbuild або SWC без побудови повного дерева типів проєкту. Без прапорця isolatedModules: true TypeScript дозволить використання мовних конструкцій, що вимагають глобального аналізу всього графа (наприклад, const enum або реекспорт типів без export type), що призведе до прихованого збою на етапі бандлінгу.
Увімкнути прапорці для примусового використання type-only імпортів (import type), усуваючи циклічні витоки типів під час збірки.
Замінити застарілі значення node/classic на bundler для вебзастосунків або nodenext для бібліотек та сервісів Node.js.
Переконатися в активності strictNullChecks та noImplicitAny, виправивши явні невідповідності типів у бізнес-логіці.
Активувати суворий доступ за індексами. Обробити масиви та хеш-мапи через type guards або утиліти assert.
Розмежувати опціональні властивості та властивості зі значенням undefined у контрактах DTO та схемах форм.