Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

Junior: питання на співбесіді з теми «Конфігурація й інструменти»

Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.

5 питань

tsconfig.json у корені проєкту каже компілятору TypeScript, які файли перевіряти і як це робити. Каталог з цим файлом вважається коренем проєкту.

{
  "extends": "@vue/tsconfig/tsconfig.dom.json",
  "compilerOptions": {
    "target": "es2022",
    "module": "esnext",
    "moduleResolution": "bundler",
    "strict": true,
    "noEmit": true,
    "types": ["vite/client"],
    "paths": { "@/*": ["./resources/js/*"] }
  },
  "include": ["resources/js/**/*.ts", "resources/js/**/*.vue"],
  "exclude": ["node_modules", "public"]
}

Основні розділи:

  • include / exclude / files - які файли входять у проєкт. include приймає шаблони, exclude відкидає частину з них, files - явний список;
  • compilerOptions - як перевіряти й що генерувати: строгість, цільова версія JavaScript, система модулів, шляхи, глобальні типи;
  • extends - успадкувати базовий конфіг (свій спільний або з пакета на кшталт @tsconfig/strictest, @vue/tsconfig);
  • references - посилання на інші проєкти в монорепозиторії.

Важливо розуміти, хто читає tsconfig.json:

  • tsc - повністю;
  • редактор (мовний сервер TypeScript) - для підказок і помилок;
  • збирачі (Vite, esbuild) - лише частину опцій, і не перевіряють типи;
  • Node.js з вбудованим зняттям типів - не читає взагалі.

noEmit: true - типова опція для застосунків на Vite: JavaScript генерує збирач, а tsc лише перевіряє типи.

Нові значення за замовчуванням (TypeScript 6.0+ і 7.0): strict увімкнено, module - esnext, target - останній стабільний стандарт ECMAScript, types - порожній список. Порожній tsconfig.json сьогодні - вже доволі строга конфігурація.

Практична порада: не копіювати чужий конфіг цілком, а починати з базового пресету фреймворку й змінювати лише те, що розумієте, - багато «магічних» опцій зі старих статей у TypeScript 7 уже видалено.

Докладніше в документації: Довідник tsconfig

strict - не одна перевірка, а набір строгих опцій, увімкнених разом:

  • noImplicitAny - помилка, якщо тип не можна вивести і він став би any (параметри функцій без типів);
  • strictNullChecks - null і undefined - окремі типи. string не може бути null, і компілятор змушує перевірити значення перед використанням. Найважливіша з усіх;
  • strictFunctionTypes - коректна (контраваріантна) перевірка параметрів функцій;
  • strictBindCallApply - перевірка аргументів bind, call, apply;
  • strictPropertyInitialization - поля класу мають бути ініціалізовані в конструкторі;
  • noImplicitThis - помилка на this з неявним типом any;
  • useUnknownInCatchVariables - змінна в catch має тип unknown, а не any;
  • alwaysStrict - режим 'use strict' у кожному файлі (у TypeScript 7 його вже не можна вимкнути).
function greet(user: { name: string } | null) {
  return user.name;   // з strictNullChecks: помилка 'user' is possibly 'null'
}

З TypeScript 6.0 strict увімкнено за замовчуванням. Проєкт, що покладався на старе значення false, має явно вказати "strict": false - і це сигнал, що варто планувати перехід.

Чому не вимикати:

  • без strictNullChecks TypeScript мовчить про найпоширеніші помилки виконання - Cannot read properties of undefined;
  • без noImplicitAny значна частина коду фактично не типізована;
  • нові опції строгості з'являються в наборі з новими версіями - проєкт зі strict: true отримує їх автоматично.

Міграція великого проєкту, де одразу тисячі помилок:

  • вмикати опції по одній (strict: false + окремі прапорці), починаючи з noImplicitAny і strictNullChecks;
  • або вмикати strict для нових каталогів через окремі конфіги чи інструменти поступової міграції.

Понад strict корисні noUncheckedIndexedAccess, exactOptionalPropertyTypes, noImplicitOverride, noFallthroughCasesInSwitch - вони в набір strict не входять і вмикаються окремо.

Докладніше в документації: Опція strict

Ці три опції часто плутають, хоча вони відповідають на різні питання.

target - у яку версію JavaScript перетворювати синтаксис.

const user = data?.user ?? guest;

З target: "es2019" компілятор перепише ?. і ?? старим синтаксисом, з es2020 і новіше - лишить як є. target змінює лише синтаксис, а не додає відсутні функції (поліфіли).

У TypeScript 6.0+ значення за замовчуванням - останній стабільний стандарт (зараз es2025), а es5 у TypeScript 7 видалено - найнижча ціль тепер es2015. Для старих браузерів TypeScript-код компілюють іншим інструментом.

lib - які вбудовані API існують у середовищі, для перевірки типів:

"lib": ["es2025", "dom", "dom.iterable"]

Без dom компілятор не знає про document і window; без свіжого es20xx - про Array.prototype.toSorted чи Object.groupBy. Якщо lib не вказано, він береться з target (плюс dom). Для коду в Node.js dom зайвий, натомість потрібен пакет @types/node.

Пастка: lib лише описує типи. Вказати es2025, а запускати код у браузері, який цих методів не має, - помилка виконання, яку TypeScript не впіймає.

module - яку систему модулів генерувати (і як трактувати import/export):

  • esnext / preserve - лишити ES-модулі (типово для коду, який збирає Vite);
  • nodenext - як Node.js: ES-модулі чи CommonJS залежно від "type" у package.json і розширень файлів;
  • commonjs - require / module.exports.

amd, umd, systemjs і none у TypeScript 7 вже не підтримуються.

Пов'язана опція moduleResolution - як знаходити файли за шляхом в import: bundler для Vite, nodenext для коду, що виконує Node.js.

Для типового Vite-застосунку: target і module мало впливають на результат (код генерує збирач), але lib визначає, які API бачить редактор, - його варто узгодити з браузерами, які ви підтримуєте.

Докладніше в документації: Опція target

Замість import Button from '../../../components/Button.vue' зручно писати import Button from '@/components/Button.vue'. Для цього налаштовують псевдоніми шляхів.

У tsconfig.json:

{
  "compilerOptions": {
    "paths": {
      "@/*": ["./resources/js/*"]
    }
  }
}

Важлива зміна: раніше разом з paths вказували baseUrl. У TypeScript 6.0 його оголосили застарілим, а в TypeScript 7 - видалено (помилка «Option 'baseUrl' has been removed»). Шляхи в paths тепер пишуть відносно каталогу з tsconfig.json, з явним префіксом ./.

Чому однієї опції paths замало: вона лише каже компілятору, де шукати типи. Код вона не змінює - у зібраному JavaScript лишиться import ... from '@/components/Button.vue', і хтось має перетворити цей шлях на справжній.

Тому той самий псевдонім треба налаштувати в інструменті, що виконує чи збирає код:

  • Vite - resolve.alias у vite.config.ts (у Laravel-проєктах @ часто вже налаштовано плагіном чи шаблоном стартового набору):
import { fileURLToPath, URL } from 'node:url';

export default defineConfig({
  resolve: {
    alias: { '@': fileURLToPath(new URL('./resources/js', import.meta.url)) },
  },
});
  • Vitest - бере resolve.alias з конфігурації Vite;
  • Node.js з вбудованим запуском TypeScript - не читає tsconfig.json і paths не підтримує. Альтернатива - subpath imports у package.json ("imports": { "#/*": "./src/*" }), які розуміють і Node.js, і TypeScript.

Типові помилки:

  • псевдонім є в tsconfig.json, але не у збирачі - редактор задоволений, а збірка падає з «Failed to resolve import»;
  • різні значення в двох місцях - редактор показує один файл, а в зібраному коді опиняється інший;
  • paths у бібліотеці, яку публікують на npm, - у споживачів ці шляхи не працюватимуть; у пакетах краще imports/exports з package.json.

Докладніше в документації: Опція paths

Глобальні змінні середовища - process, Buffer, describe, it, expect - TypeScript знає не сам, а з пакетів типів: @types/node, @types/jest тощо. Опція types визначає, які з таких пакетів підключати глобально, без явного імпорту.

Що змінилося. До TypeScript 5.9 включно значення за замовчуванням було «усі пакети з node_modules/@types». У великих проєктах це сотні пакетів, підтягнутих транзитивно, - повільна перевірка й випадкові глобальні типи.

З TypeScript 6.0 (і в 7.0) types за замовчуванням - порожній список []. Після оновлення проєкт, що покладався на автоматичне підключення, бачить:

error TS2591: Cannot find name 'process'. Do you need to install type definitions for node?
Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig.

Рішення - перелічити потрібні пакети явно:

{
  "compilerOptions": {
    "types": ["node", "vite/client"]
  }
}
  • node - для коду, що працює в Node.js (конфіги, скрипти, SSR);
  • vite/client - типи import.meta.env, import.meta.hot і імпорту ресурсів (.svg, .css) у Vite-застосунку;
  • vitest/globals - якщо тести використовують глобальні describe/it.

Повернути стару поведінку можна значенням ["*"], але документація TypeScript радить явний список: за їхніми даними, багато проєктів пришвидшили збірку на 20-50% лише завдяки цьому.

Що варто знати:

  • types стосується лише глобальних оголошень. Пакети, які ви імпортуєте (import express from 'express'), підключаються через імпорт і не потребують запису в types;
  • різні частини проєкту - різні глобальні типи: код браузера не повинен бачити process, а конфіги Vite - document. Тому шаблони Vite мають два конфіги (tsconfig.app.json і tsconfig.node.json) з різними types і lib;
  • пакет має бути встановлений: запис у types без @types/node у devDependencies дасть помилку «Cannot find type definition file».

Докладніше в документації: Опція types