Система типів мови TypeScript

Знайомство з TypeScript Compiler

Компілятор TypeScript, робота з tsc через CLI, детальний розбір tsconfig.json, перехід до Vite та концепція noEmit

Знайомство з TypeScript Compiler

🎯 Мета розділу

  • Зрозуміти, що таке компіляція TypeScript у JavaScript та навіщо вона потрібна.
  • Опанувати роботу з компілятором tsc через командний рядок.
  • Вивчити структуру та ключові параметри конфігураційного файлу tsconfig.json.
  • Усвідомити еволюцію від tsc-компіляції до сучасних інструментів збірки (Vite).
  • Зрозуміти концепцію noEmit та роль TypeScript у сучасних проєктах.

🔑 Ключові терміни

  • Компілятор (Compiler): програма, що перетворює вихідний код однієї мови на іншу.
  • TypeScript Compiler (tsc): офіційний компілятор TypeScript у JavaScript.
  • Транспіляція (Transpilation): перетворення коду з однієї мови на іншу схожого рівня абстракції.
  • Target: цільова версія JavaScript, у яку компілюється TypeScript (ES5, ES2015, ES2020 тощо).
  • Bundler: інструмент збірки, що об'єднує модулі у фінальні файли (Vite, Webpack, Rollup).

Проблематика динамічної типізації JavaScript

JavaScript був створений у 1995 році Бренданом Айком (Brendan Eich) за десять днів як мова сценаріїв для браузера Netscape Navigator. Його первинне призначення полягало у додаванні інтерактивності до статичних HTML-сторінок: валідація форм, анімації, обробка кліків. У той час ніхто не передбачав, що через два десятиліття JavaScript стане однією з найпопулярніших мов програмування у світі, на якій створюються складні серверні системи (Node.js), мобільні застосунки (React Native), настільні програми (Electron) та навіть вбудовані системи інтернету речей.

Однією з ключових характеристик JavaScript є динамічна типізація (dynamic typing). Це означає, що тип змінної визначається автоматично під час виконання програми (runtime), а не під час написання коду (compile time). Розробник може присвоїти змінній значення будь-якого типу, а потім змінити тип цього значення у будь-який момент:

let data = "Hello, World!";  // рядок (string)
console.log(data.toUpperCase()); // "HELLO, WORLD!"

data = 42; // тепер число (number)
console.log(data.toFixed(2)); // "42.00"

data = { name: "Alice", age: 28 }; // тепер об'єкт (object)
console.log(data.name); // "Alice"

Переваги динамічної типізації

Динамічна типізація має свої переваги, особливо на ранніх етапах розробки або для невеликих скриптів:

  • Швидкість прототипування: Не потрібно витрачати час на оголошення типів, можна швидко експериментувати з різними структурами даних.
  • Гнучкість коду: Функція може приймати аргументи різних типів та адаптувати поведінку залежно від переданого значення (duck typing: «якщо щось крякає як качка і плаває як качка — це качка»).
  • Мінімалістичний синтаксис: Код виглядає стисло, без додаткових анотацій типів, що може підвищувати читабельність для простих випадків.

Недоліки динамічної типізації у великих проєктах

Однак коли проєкт зростає у розмірах, коли над ним працює команда з десятків розробників, коли кодова база досягає сотень тисяч рядків, динамічна типізація перетворюється на джерело серйозних проблем:

Проблема 1: Помилки типів виявляються лише під час виконання

Уявімо функцію, що форматує дату публікації:

function formatPublishDate(post) {
  return post.publishedAt.toISOString();
}

const article = { title: "My Article", author: "Alice" };
console.log(formatPublishDate(article)); 
// TypeError: Cannot read property 'toISOString' of undefined

Ця помилка з'явиться лише тоді, коли функція буде викликана з об'єктом, що не має властивості publishedAt. Якщо цей виклик знаходиться у рідко використовуваній гілці коду (наприклад, адміністративна панель, доступна лише певним користувачам), помилка може залишитися непоміченою тижнями або місяцями після розгортання у продакшен. Користувачі побачать білий екран або повідомлення про помилку, що негативно вплине на репутацію продукту.

Проблема 2: Рефакторинг коду стає ризикованим

Припустімо, ви перейменували властивість author на authorName у моделі статті. У великому проєкті можуть існувати десятки або сотні місць, де використовується post.author. Без статичної типізації неможливо автоматично знайти всі ці місця — IDE не знає, що post має бути об'єктом певної структури. Розробник змушений вручну шукати всі входження через текстовий пошук (Find in Files), що призводить до пропущених місць та помилок:

// Файл 1: модель змінена
const post = { title: "Article", authorName: "Alice" };

// Файл 2: забули оновити (помилка!)
function getAuthor(post) {
  return post.author; // undefined, але компілятор не попереджає
}

Проблема 3: Відсутність автодоповнення та інтелектуальних підказок

Коли ви пишете post. у редакторі, IDE не може запропонувати список доступних властивостей, оскільки не знає структуру об'єкта post. Розробник змушений або запам'ятовувати імена полів, або постійно звертатися до документації чи інших частин коду. Це сповільнює розробку та підвищує кількість помилок через друкарські помилки у назвах властивостей.

Проблема 4: Неявні контракти між модулями

Функція calculateTotal(price, quantity, discount) не документує, які типи аргументів вона очікує. Чи price має бути числом у копійках (ціле число) чи у гривнях (дробове число)? Чи discount — це відсоток (0-100) чи коефіцієнт (0-1)? Без коментарів або зовнішньої документації це залишається загадкою, що призводить до некоректного використання функції:

// Розробник A думає, що discount у відсотках:
calculateTotal(100, 2, 10); // очікує 10% знижки

// Розробник B думає, що discount — коефіцієнт:
calculateTotal(100, 2, 0.1); // очікує 10% знижки

// Яка реалізація правильна? Невідомо без читання коду!
Згідно з дослідженням Microsoft, близько 15% помилок у JavaScript-проєктах є помилками типів, які могли б бути виявлені статичним аналізатором на етапі компіляції. У великих командних проєктах цей відсоток може сягати 25-30%, що перетворює відлагодження у болісний процес пошуку помилок, які могли бути запобігнуті.

TypeScript як рішення проблеми

У 2012 році компанія Microsoft оголосила про розробку мови TypeScript — надмножини JavaScript, що додає статичну типізацію та інші можливості для підтримки великомасштабної розробки. Головний архітектор TypeScript, Андерс Хейлсберг (Anders Hejlsberg), раніше працював над мовами Turbo Pascal, Delphi та C#, тому TypeScript увібрав багато ідей зі статично типізованих мов корпоративного класу.

TypeScript як надмножина JavaScript

Ключова філософія TypeScript полягає у тому, що це надмножина (superset) JavaScript: будь-який валідний JavaScript-код є валідним TypeScript-кодом. Це означає, що ви можете взяти існуючий .js файл, перейменувати його у .ts, і він миттєво стане TypeScript-файлом без жодних змін. Немає потреби переписувати весь код з нуля — TypeScript можна впроваджувати поступово, файл за файлом, додаючи типізацію там, де це найбільш критично.

Loading diagram...
@startuml
skinparam style plain
skinparam backgroundColor #FFFFFF

rectangle "TypeScript" as TS #DBEAFE {
    rectangle "Анотації типів\nІнтерфейси\nЕнуми\nУзагальнення" as TSFeatures #93C5FD
    
    rectangle "JavaScript\n(ES3-ESNext)" as JS #DCFCE7 {
        note bottom
        Весь валідний JS-код
        є валідним TS-кодом
        end note
    }
}

TSFeatures -down-> JS : розширює

note right of TS
  TypeScript = JavaScript
  + статична типізація
  + інструменти розробки
end note
@enduml

TypeScript додає до JavaScript наступні можливості:

  • Анотації типів: Явне оголошення типів змінних, параметрів функцій, значень повернення.
  • Інтерфейси та псевдоніми типів: Опис структури об'єктів, контрактів між модулями.
  • Узагальнені типи (Generics): Створення функцій та класів, що працюють з різними типами даних, зберігаючи типобезпеку.
  • Перерахування (Enums): Визначення іменованих констант для обмеження можливих значень.
  • Розширений об'єктно-орієнтований синтаксис: Модифікатори доступу (private, protected, public), абстрактні класи.
  • Декоратори: Метапрограмування для модифікації класів, методів та властивостей.

При цьому TypeScript не виконується безпосередньо — він компілюється (transpiles) у звичайний JavaScript, що може працювати у будь-якому середовищі виконання (браузери, Node.js, Deno, Bun).

Переваги статичної типізації

Статична типізація TypeScript надає численні переваги, що кардинально змінюють досвід розробки:

1. Раннє виявлення помилок

Компілятор TypeScript аналізує код та повідомляє про типові невідповідності до запуску програми. Якщо ви спробуєте викликати метод на значенні undefined або передати рядок туди, де очікується число, компілятор видасть помилку на етапі написання коду:

type Post = {
  id: string;
  title: string;
  publishedAt: Date;
};

function formatPublishDate(post: Post): string {
  return post.publishedAt.toISOString();
}

const article = { title: "My Article", author: "Alice" };
formatPublishDate(article); 
// Error: Argument of type '{ title: string; author: string; }' 
// is not assignable to parameter of type 'Post'.
// Property 'id' is missing.

Помилка виявлена у редакторі до запуску коду. Не потрібно чекати, поки користувач натрапить на цю помилку у продакшені.

2. Автодоповнення та навігація у коді

Редактори коду (Visual Studio Code, WebStorm, Sublime Text з плагінами) використовують інформацію про типи для надання інтелектуальних підказок. Коли ви пишете post., IDE миттєво показує список доступних властивостей (id, title, publishedAt) та їхні типи. Натискання Ctrl+клік (Cmd+клік на macOS) на ім'я функції переносить вас до її оголошення:

3. Безпечний рефакторинг

Перейменування властивості через команду "Rename Symbol" у VS Code автоматично оновлює всі використання цієї властивості у всьому проєкті. Компілятор TypeScript гарантує, що жодне місце не буде пропущено. Якщо ви видалите параметр функції, компілятор покаже помилки у всіх місцях виклику цієї функції, де ще передається старий параметр.

4. Самодокументований код

Сигнатура функції calculateTotal(price: number, quantity: number, discount: number): number чітко вказує, які аргументи приймає функція (всі числа) та що вона повертає (число). Немає потреби шукати документацію або досліджувати тіло функції, щоб зрозуміти її контракт. Типи є вбудованою документацією, що завжди актуальна (на відміну від коментарів, які часто застарівають).

5. Полегшення командної розробки

Коли над проєктом працює кілька розробників, типізація діє як формальна специфікація інтерфейсів між модулями. Розробник, що працює над модулем оплати, може покладатися на те, що модуль користувачів надає об'єкт певної структури з гарантованими полями, і компілятор забезпечить дотримання цього контракту. Немає «сюрпризів» у вигляді неочікуваних undefined чи некоректних типів даних.

Поширена помилка полягає у думці, що TypeScript «сповільнює розробку» через необхідність писати анотації типів. Насправді ця початкова інвестиція часу багаторазово окупається зменшенням часу на відлагодження, рефакторинг та онбординг нових членів команди. За оцінками Airbnb, впровадження TypeScript у їхній кодовій базі скоротило кількість продакшн-помилок на 38% та прискорило розробку нових функцій на 15% завдяки покращеній підтримці IDE та впевненості розробників у коректності коду.

Історичний контекст: чому TypeScript з'явився саме у 2012 році

До 2012 року JavaScript активно використовувався для створення складних веб-застосунків (Gmail, Google Maps, Facebook), але розробники стикалися з критичними проблемами масштабування. Google створив власну мову Dart (2011), що мала статичну типізацію та компілювалася у JavaScript. Facebook розробляв Flow — статичний аналізатор типів для JavaScript. Microsoft, маючи досвід створення корпоративних мов (C#, Visual Basic), бачив можливість створити рішення, що поєднує сумісність з JavaScript та потужність статичної типізації.

Ключовою відмінністю TypeScript від конкурентів стала поступова типізація (gradual typing): можна починати з чистого JavaScript та додавати типи поступово, файл за файлом. Це зробило міграцію існуючих проєктів практичною — не потрібно переписувати мільйони рядків коду за одну ніч.

Сьогодні TypeScript використовують:

  • Великі технологічні компанії: Microsoft (весь VS Code написаний на TS), Google (Angular повністю на TS), Airbnb, Slack, Shopify.
  • Популярні фреймворки: Angular (обов'язковий TS), Vue 3 (написаний на TS), NestJS (Node.js фреймворк).
  • Опенсорс-проєкти: Redux Toolkit, RxJS, TypeORM, Prisma.

За даними State of JS 2023, 78% JavaScript-розробників використовують TypeScript у своїх проєктах, і лише 9% планують від нього відмовитися. TypeScript став де-факто стандартом для серйозної веброзробки.


Чому TypeScript потребує компіляції

JavaScript є інтерпретованою мовою: браузери та середовище Node.js виконують JavaScript-код безпосередньо, рядок за рядком, без попереднього перетворення. TypeScript, навпаки, не може бути виконаний безпосередньо — жоден рушій JavaScript (V8 у Chrome/Node.js, SpiderMonkey у Firefox, JavaScriptCore у Safari) не розуміє синтаксису TypeScript.

TypeScript є надбудовою (superset) JavaScript: будь-який валідний JavaScript-код є валідним TypeScript-кодом, але TypeScript додає додаткові конструкції — анотації типів, інтерфейси, енуми, узагальнення тощо. Ці конструкції існують лише на етапі розробки для перевірки коректності коду. Перед виконанням вони мають бути видалені, а сам код — перетворений на чистий JavaScript.

Процес перетворення TypeScript у JavaScript називається компіляцією (або точніше — транспіляцією, оскільки обидві мови знаходяться на схожому рівні абстракції). Цей процес виконує TypeScript Compiler — програма tsc (TypeScript Compiler).

Loading diagram...
@startuml
skinparam style plain
skinparam backgroundColor #FFFFFF
skinparam componentStyle rectangle

rectangle "Вихідний TypeScript\n(example.ts)" as TS #DBEAFE {
    note bottom
    let message: string = "Hello";
    function greet(name: string): string {
      return `Hello, ${name}!`;
    }
    end note
}

rectangle "TypeScript Compiler\n(tsc)" as TSC #FCD34D {
    note bottom
    1. Парсинг коду
    2. Перевірка типів
    3. Видалення анотацій
    4. Трансформація синтаксису
    end note
}

rectangle "Згенерований JavaScript\n(example.js)" as JS #DCFCE7 {
    note bottom
    var message = "Hello";
    function greet(name) {
      return "Hello, " + name + "!";
    }
    end note
}

rectangle "Виконання\n(Node.js / Browser)" as Runtime #E2E8F0

TS -right-> TSC : npx tsc example.ts
TSC -right-> JS : генерація
JS -right-> Runtime : node example.js

note bottom of Runtime
  TypeScript зникає повністю —
  виконується чистий JavaScript
end note
@enduml
TypeScript не додає жодних runtime-бібліотек чи залежностей до згенерованого коду (на відміну від Babel з полі-філами). Результатом компіляції завжди є чистий JavaScript, який може виконуватися у будь-якому середовищі без додаткових залежностей.

Встановлення TypeScript Compiler

Перед початком роботи необхідно встановити Node.js (версія LTS, наприклад 20.x) та менеджер пакетів npm. Перевірте встановлення:

node --version   # v20.11.0
npm --version    # 10.4.0

Створіть новий каталог для експериментів та ініціалізуйте npm-проєкт:

mkdir typescript-basics
cd typescript-basics
npm init -y

Встановіть TypeScript як залежність розробки:

npm install --save-dev typescript

Перевірте встановлення компілятора:

npx tsc --version
npx tsc --version
$ npx tsc --version
Version 5.3.3

Команда npx запускає локально встановлений виконуваний файл з node_modules/.bin/, що дозволяє уникнути глобального встановлення.


Перша компіляція: робота з tsc через CLI

Створимо найпростіший TypeScript-файл та скомпілюємо його вручну через командний рядок.

Приклад 1: Базова компіляція

Створіть файл hello.ts:

let message: string = "Hello, TypeScript!";
console.log(message.toUpperCase());

Скомпілюйте файл командою:

npx tsc hello.ts

Компілятор створить файл hello.js у тому ж каталозі:

var message = "Hello, TypeScript!";
console.log(message.toUpperCase());

Зверніть увагу на зміни:

  1. Анотація типу : string видалена — JavaScript не знає про типи.
  2. let замінено на var — за замовчуванням tsc компілює у ES3/ES5 для максимальної сумісності.

Запустіть згенерований JavaScript:

node hello.js
node hello.js
$ node hello.js
HELLO, TYPESCRIPT!

Приклад 2: Функції з типізацією

Створіть файл math.ts:

function add(a: number, b: number): number {
  return a + b;
}

function multiply(x: number, y: number): number {
  return x * y;
}

let result: number = add(5, 3);
console.log(`5 + 3 = ${result}`);
console.log(`5 * 3 = ${multiply(5, 3)}`);

Скомпілюйте:

npx tsc math.ts

Згенерований math.js:

function add(a, b) {
    return a + b;
}
function multiply(x, y) {
    return x * y;
}
var result = add(5, 3);
console.log("5 + 3 = " + result);
console.log("5 * 3 = " + multiply(5, 3));

Зміни:

  1. Всі анотації типів видалені (: number).
  2. Template literal перетворено на конкатенацію — це поведінка для ES5.
  3. let замінено на var.
За замовчуванням tsc використовує target: ES3, що є надзвичайно старою версією JavaScript (1999 рік). Це призводить до громіздкого коду. У реальних проєктах завжди налаштовуйте target через параметри CLI або tsconfig.json.

Приклад 3: Вказування target через CLI

Скомпілюємо той же файл у сучасніший JavaScript (ES2020):

npx tsc math.ts --target ES2020

Згенерований math.js (ES2020):

function add(a, b) {
    return a + b;
}
function multiply(x, y) {
    return x * y;
}
let result = add(5, 3);
console.log(`5 + 3 = ${result}`);
console.log(`5 * 3 = ${multiply(5, 3)}`);

Тепер код зберігає let та template literals, оскільки ES2020 їх підтримує.

Команда npx tsc --help виводить повний список доступних параметрів компілятора. Експериментуйте з різними комбінаціями для розуміння їхнього впливу на вихідний код.

Проблеми компіляції через CLI

Компіляція через командний рядок підходить для швидких експериментів, але має серйозні недоліки для реальних проєктів:

Проблема 1: Багатослівність команд

Кожного разу доводиться вручну вказувати всі параметри:

npx tsc src/index.ts --target ES2020 --module ESNext --outDir dist --strict --esModuleInterop

Якщо проєкт має 20 файлів, команда стає неймовірно довгою. Немає способу зберегти налаштування для повторного використання.

Проблема 2: Відсутність консистентності

Різні члени команди можуть використовувати різні параметри компіляції, що призводить до несумісного коду. Один розробник компілює у ES5, інший у ES2020 — результати відрізняються.

Проблема 3: Немає автоматичної компіляції всіх файлів

Для компіляції всього проєкту потрібно або перелічити всі файли вручну, або використовувати glob-паттерни, що не завжди працюють однаково на різних ОС:

npx tsc src/**/*.ts --outDir dist

Проблема 4: Складність налаштування IDE

Редактори коду (VS Code, WebStorm) потребують знати параметри компіляції для коректного аналізу та підказок. Передавати їм параметри через CLI неможливо.

Loading diagram...
@startuml
skinparam style plain
skinparam backgroundColor #FFFFFF

actor "Розробник 1" as Dev1 #DBEAFE
actor "Розробник 2" as Dev2 #DCFCE7

rectangle "CLI-команди" as CLI #FEF3C7 {
    note bottom
    tsc file.ts --target ES5
    vs
    tsc file.ts --target ES2020
    end note
}

rectangle "Різні результати" as Results #FCA5A5 {
    note bottom
    var message = "...";
    vs
    let message = "...";
    end note
}

Dev1 --> CLI : різні параметри
Dev2 --> CLI : різні параметри
CLI --> Results : несумісний код

note bottom of Results
  Код працює по-різному,
  важко налагоджувати,
  конфлікти при злитті
end note
@enduml

Рішення: Всі параметри компіляції виносяться у конфігураційний файл tsconfig.json, який стає єдиним джерелом істини для проєкту.


Конфігураційний файл tsconfig.json

Файл tsconfig.json — це JSON-конфігурація, що описує параметри компіляції TypeScript-проєкту. Він розміщується у корені проєкту та автоматично зчитується компілятором tsc, а також редакторами коду для аналізу.

Створення базового tsconfig.json

Згенеруйте файл командою:

npx tsc --init

Ця команда створює tsconfig.json із детальними коментарями:

{
  "compilerOptions": {
    "target": "ES2016",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  }
}

Тепер для компіляції всього проєкту достатньо виконати:

npx tsc

Компілятор автоматично знайде tsconfig.json, прочитає налаштування та скомпілює всі .ts файли відповідно до конфігурації.

Якщо у каталозі є tsconfig.json, команда npx tsc без аргументів компілює весь проєкт згідно з конфігурацією. Якщо вказати конкретний файл (npx tsc file.ts), tsconfig.jsonігнорується, і використовуються параметри за замовчуванням або параметри CLI.

Структура tsconfig.json

Файл складається з кількох основних секцій:

{
  "compilerOptions": {
    // Параметри компілятора
  },
  "include": [
    // Які файли компілювати
  ],
  "exclude": [
    // Які файли ігнорувати
  ],
  "files": [
    // Конкретні файли (рідко використовується)
  ]
}

Найважливішою є секція compilerOptions, що містить десятки параметрів. Розглянемо найкритичніші.


Детальний розбір ключових параметрів compilerOptions

Параметр target: цільова версія JavaScript

Параметр target визначає, у яку версію ECMAScript компілюватиметься TypeScript-код.

Доступні значення:

  • ES3 (1999) — Internet Explorer 6-8
  • ES5 (2009) — IE 9-11, старі Android-браузери
  • ES6 / ES2015 (2015) — класи, стрілкові функції, let/const, модулі
  • ES2016 — оператор ** (піднесення до степеня)
  • ES2017async/await
  • ES2020 — optional chaining (?.), nullish coalescing (??), BigInt
  • ES2022 — class fields, top-level await
  • ESNext — найновіші можливості (експериментальні)

Приклад впливу target:

const greet = (name: string): string => {
  return `Hello, ${name}!`;
};

class User {
  constructor(public name: string) {}
}

console.log(greet("Alice"));

Як обрати target:

  • Node.js 18+: ES2022 або ESNext
  • Сучасні браузери (Chrome/Firefox/Safari останніх 2 версій): ES2020
  • Потрібна підтримка IE11: ES5
  • Невідоме середовище: ES2015 (безпечний баланс)
tsc з різними target
$ npx tsc --target ES5 example.ts
Generated: example.js (ES5 syntax)
$ npx tsc --target ES2020 example.ts
Generated: example.js (modern syntax)

Параметр module: система модулів

Параметр module визначає, яку систему модулів використовувати у згенерованому JavaScript-коді.

Доступні значення:

  • CommonJS — Node.js-модулі (require/module.exports)
  • ES2015 / ES6 / ESNext — стандарт ECMAScript (import/export)
  • AMD — Asynchronous Module Definition (застарілий)
  • UMD — Universal Module Definition (сумісний з усіма системами)
  • None — без модулів (всі файли у глобальній області видимості)

Приклад впливу module:

export function calculateSum(a: number, b: number): number {
  return a + b;
}

import { calculateSum } from './math';
console.log(calculateSum(5, 3));

Як обрати module:

  • Node.js проєкт: CommonJS (або ES2015 якщо package.json має "type": "module")
  • Браузерний проєкт з бандлером (Vite/Webpack): ESNext
  • Бібліотека для npm: CommonJS або гібридний підхід

Параметри outDir та rootDir: організація файлів

  • rootDir: Кореневий каталог вихідних .ts файлів (зазвичай ./src)
  • outDir: Каталог для згенерованих .js файлів (зазвичай ./dist або ./build)
{
  "compilerOptions": {
    "rootDir": "./src",
    "outDir": "./dist"
  }
}

До компіляції:

project/
├── src/
│   ├── index.ts
│   └── utils.ts
└── tsconfig.json

Після npx tsc:

project/
├── src/
│   ├── index.ts
│   └── utils.ts
├── dist/           # <-- згенеровано
│   ├── index.js
│   └── utils.js
└── tsconfig.json
Якщо не вказати rootDir, компілятор визначить його автоматично як найглибший спільний каталог усіх вхідних файлів. Це може призвести до неочікуваної структури dist/.

Параметр strict: суворий режим типізації

Параметр strict: true вмикає всі сув

орі перевірки типів одночасно. Це найважливіший параметр для якісного коду.

{
  "compilerOptions": {
    "strict": true
  }
}

Еквівалентно вмиканню:

  • strictNullChecksnull та undefined окремі типи
  • strictFunctionTypes — сувора перевірка типів функцій
  • strictBindCallApply — перевірка типів для bind/call/apply
  • strictPropertyInitialization — властивості класів мають бути ініціалізовані
  • noImplicitAny — заборона неявного any
  • noImplicitThis — заборона неявного this
  • alwaysStrict — генерувати "use strict" у JS

Приклад:

// Без strict
let value: string;
value = null; // Дозволено (небезпечно!)

// З strict: true
let value: string;
value = null; // Error: Type 'null' is not assignable to type 'string'
Завжди використовуйте strict: true для нових проєктів. Це запобігає 70-80% типових помилок на ранніх етапах розробки.

Продовжити з наступною секцією про еволюцію до Vite?

Еволюція від tsc до сучасних інструментів збірки

Обмеження класичної tsc-компіляції

Компілятор tsc чудово справляється з перевіркою типів та трансформацією TypeScript у JavaScript, але має кілька фундаментальних обмежень для сучасної веброзробки:

1. Відсутність бандлінгу (bundling):

tsc компілює кожен .ts файл у окремий .js файл, зберігаючи структуру каталогів. Це означає, що якщо ваш проєкт має 50 файлів, згенерується 50 JavaScript-файлів. Браузер повинен завантажити всі ці файли окремими HTTP-запитами:

src/
├── index.ts
├── utils/
│   ├── math.ts
│   ├── string.ts
│   └── validation.ts
└── components/
    ├── button.ts
    └── card.ts

↓ npx tsc

dist/
├── index.js
├── utils/
│   ├── math.js
│   ├── string.js
│   └── validation.js
└── components/
    ├── button.js
    └── card.js

Кожен файл — окремий HTTP-запит. Для великих проєктів це сотні запитів, що критично сповільнює завантаження.

2. Немає оптимізацій коду:

tsc не мініфікує код, не видаляє невикористані змінні (tree shaking), не оптимізує імпорти. Згенерований JavaScript містить коментарі, пробіли, повні назви змінних.

3. Відсутність Hot Module Replacement (HMR):

При зміні файлу доводиться перекомпілювати весь проєкт командою npx tsc --watch та вручну перезавантажувати браузер. Немає автоматичного оновлення змін у реальному часі.

4. Повільна компіляція великих проєктів:

tsc написаний на TypeScript та виконується у Node.js. Для проєктів з тисячами файлів компіляція може займати хвилини.

Loading diagram...
@startuml
skinparam style plain
skinparam backgroundColor #FFFFFF

rectangle "tsc компіляція" as TSC #FEF3C7 {
    rectangle "50 .ts файлів" as TS
    rectangle "50 .js файлів" as JS
}

rectangle "Браузер" as Browser #FCA5A5 {
    note bottom
    50 HTTP-запитів!
    Немає мініфікації
    Немає tree shaking
    Повільне завантаження
    end note
}

TS -down-> JS : npx tsc
JS -right-> Browser : завантаження

note right of TSC
  tsc НЕ об'єднує файли,
  НЕ оптимізує код,
  НЕ мініфікує вихід
end note
@enduml

Рішення: Бандлери та інструменти збірки

Для вирішення цих проблем виникли бандлери (bundlers) — інструменти, що об'єднують кілька модулів у один або кілька оптимізованих файлів:

  • Webpack (2012) — потужний, але складний у налаштуванні
  • Rollup (2015) — спеціалізований для бібліотек, чудовий tree shaking
  • Parcel (2017) — zero-config, автоматичні налаштування
  • esbuild (2020) — надшвидкий, написаний на Go
  • Vite (2020) — сучасний, заснований на esbuild та Rollup

Vite: швидкість та простота

Vite (від французького «швидкий») — це інструмент збірки нового покоління, створений Еваном Ю (автором Vue.js). Vite використовує нативні ES-модулі браузера під час розробки та Rollup для продакшн-збірки.

Ключові переваги Vite:

  1. Миттєвий запуск dev-сервера — не потрібно збирати весь проєкт перед стартом.
  2. Hot Module Replacement (HMR) — зміни з'являються у браузері за мілісекунди.
  3. Вбудована підтримка TypeScript — не потрібно налаштовувати tsc.
  4. Оптимізація для продакшену — автоматична мініфікація, tree shaking, code splitting.

Встановлення проєкту з Vite

Створіть новий проєкт:

npm create vite@latest my-app -- --template vanilla-ts
cd my-app
npm install

Структура згенерованого проєкту:

Запустіть dev-сервер:

npm run dev
npm run dev
$ npm run dev
VITE v5.0.10 ready in 324 ms
Local: http://localhost:5173/
Network: use --host to expose

Відкрийте http://localhost:5173/ — застосунок працює! Змініть src/main.ts, збережіть файл — зміни миттєво з'являться у браузері без перезавантаження сторінки.


Концепція noEmit: TypeScript без компіляції

Подивимося на tsconfig.json, згенерований Vite:

{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "module": "ESNext",
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "skipLibCheck": true,

    /* Bundler mode */
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,  // <-- КЛЮЧОВА ОПЦІЯ!

    /* Linting */
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true
  },
  "include": ["src"]
}

Зверніть увагу на параметр noEmit: true. Що це означає?

Що таке noEmit?

noEmit: true вказує компілятору TypeScript не генерувати JavaScript-файли. tsc виконує лише перевірку типів (type checking) без створення вихідних .js файлів.

Чому це потрібно у Vite-проєкті?

Vite має власний транспілятор (esbuild), що перетворює TypeScript у JavaScript на льоту під час розробки та через Rollup при збірці для продакшену. Це набагато швидше, ніж tsc.

Роль TypeScript у Vite-проєкті:

  1. Перевірка типів — знаходження помилок типізації.
  2. Інтелектуальні підказки IDE — автодоповнення, навігація по коду.
  3. Статичний аналіз — виявлення потенційних помилок до виконання.

Роль Vite:

  1. Транспіляція TypeScript → JavaScript (через esbuild).
  2. Бандлінг модулів (об'єднання файлів).
  3. Оптимізація коду (мініфікація, tree shaking).
  4. Hot Module Replacement (миттєве оновлення у браузері).
Loading diagram...
@startuml
skinparam style plain
skinparam backgroundColor #FFFFFF

rectangle "TypeScript (tsc)" as TSC #DBEAFE {
    rectangle "Перевірка типів" as Check
    rectangle "Аналіз коду" as Analysis
    rectangle "IDE-підтримка" as IDE
    
    note bottom
    noEmit: true
    НЕ генерує .js файли
    end note
}

rectangle "Vite (esbuild + Rollup)" as Vite #DCFCE7 {
    rectangle "Транспіляція TS→JS" as Trans
    rectangle "Бандлінг" as Bundle
    rectangle "Оптимізація" as Opt
    rectangle "HMR" as HMR
}

TSC -down-> Vite : передає перевірені типи
Vite -down-> "Браузер" : оптимізований код

note right of TSC
  TypeScript відповідає
  за коректність типів
end note

note right of Vite
  Vite відповідає за
  швидкість та оптимізацію
end note
@enduml

Перевірка типів у Vite-проєкті

Vite не перевіряє типи під час dev-сервера для максимальної швидкості. Помилки типізації з'являються лише у редакторі коду (VS Code) або при запуску tsc вручну.

Додайте скрипт для перевірки типів у package.json:

{
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",  // Перевірка типів перед збіркою
    "preview": "vite preview",
    "typecheck": "tsc --noEmit"    // Лише перевірка типів
  }
}

Тепер можна запустити:

npm run typecheck
npm run typecheck
$ npm run typecheck
src/main.ts:5:7 - error TS2322: Type 'number' is not assignable to type 'string'.
5 let name: string = 42;
~~~~
Found 1 error.
У CI/CD-конвеєрах завжди додавайте крок npm run typecheck перед збіркою. Це гарантує, що код без помилок типізації потрапить у продакшен, навіть якщо розробник пропустив помилку у редакторі.

Порівняння підходів: tsc vs Vite

Критерійtsc (класичний)Vite + noEmit
Транспіляціяtsc перетворює TS→JSesbuild (швидше у 10-100 разів)
Перевірка типівТак, під час компіляціїОкремо через tsc --noEmit
БандлінгНі (кожен файл окремо)Так (Rollup для продакшену)
HMRНіТак (миттєво)
Швидкість dev-сервераПовільно (перекомпіляція)Миттєво (на льоту)
Оптимізація продакшенуНіТак (мініфікація, tree shaking)
Складність налаштуванняПростаПроста (zero-config)
Коли використовуватиNode.js-бібліотекиБраузерні застосунки
Для Node.js-проєктів (серверів, CLI-утиліт, бібліотек) tsc залишається стандартним вибором, оскільки там не потрібні бандлінг та HMR. Для браузерних застосунків Vite (або інші бандлери) є де-факто стандартом.

Практичний приклад: TypeScript + Vite

Створимо простий калькулятор з типізацією:

Запустіть dev-сервер:

npm run dev

Відкрийте консоль браузера — побачите результати. Спробуйте розкоментувати останній рядок з помилкою — VS Code миттєво покаже червоне підкреслення, а npm run typecheck виведе помилку компіляції.


Підсумок та ключові висновки

TypeScript Compiler (tsc) є фундаментом екосистеми TypeScript, але його роль еволюціонувала з часом. У сучасних проєктах tsc часто виконує лише перевірку типів, тоді як транспіляція та оптимізація делегуються швидшим інструментам.

Ключові тези розділу:

  • tsc через CLI підходить для експериментів, але незручний для реальних проєктів через багатослівність команд.
  • tsconfig.json є єдиним джерелом істини для налаштувань компіляції, забезпечуючи консистентність у команді.
  • target визначає версію JavaScript (ES5, ES2020, ESNext), module — систему модулів (CommonJS, ESNext).
  • strict: true вмикає найсуворіші перевірки типів та рекомендується для всіх нових проєктів.
  • Vite — сучасний інструмент збірки, що використовує esbuild для транспіляції та Rollup для продакшен-збірки.
  • noEmit: true у Vite-проєктах означає, що tsc лише перевіряє типи, а не генерує JavaScript.
  • Розділення відповідальності: TypeScript — перевірка типів, Vite — транспіляція та оптимізація.

Контрольні питання для самоперевірки

  1. Чому TypeScript не може виконуватися безпосередньо у браузері або Node.js?
  2. Яка різниця між компіляцією через npx tsc file.ts та npx tsc (без аргументів)?
  3. Як параметр target впливає на згенерований JavaScript-код? Наведіть приклад.
  4. Чому strict: true є найважливішим параметром для нових проєктів?
  5. У чому полягають обмеження класичної tsc-компіляції для браузерних проєктів?
  6. Що означає параметр noEmit: true у Vite-проєкті?
  7. Яка роль TypeScript у сучасних проєктах з бандлерами (Vite, Webpack)?
  8. Коли варто використовувати tsc без бандлера, а коли обов'язково потрібен Vite?
Copyright © 2026