TypeScript 7.1 вийшов у бета-версії 20 жовтня 2026 року, і головна фіча релізу – не продуктивність, а точніша типізація import attributes в ambient module declarations. Якщо коротко: тепер можна написати declare module "*" with { type: "css" } { ... } і компілятор нарешті розрізнятиме, який саме атрибут стоїть за імпортом файлу, а не просто дивитиметься на розширення. Встановити бету можна командою npm install -D typescript@beta.
Звучить як дрібниця, але якщо ти хоч раз імпортував CSS, JSON чи бінарні файли через бандлер з явним with { type: ... } – ти знаєш, наскільки криво це працювало раніше.
Чому ambient-декларації були неповноцінними
Import attributes – це метадані після ключового слова with, які кажуть рантайму чи бандлеру, як інтерпретувати імпорт:
import myData from "./data/someData.json" with { type: "json" };Крім type: "json", екосистема рухається в бік інших атрибутів: ECMAScript-proposal для type: "text" повертає вміст файлу як рядок, proposal для type: "bytes" – як Uint8Array, а браузери вже кілька років підтримують type: "css" для імпорту файлу одразу як об’єкта CSSStyleSheet.
Щоб типізувати такі імпорти, розробники писали wildcard ambient-декларації:
declare module "*.css" {
const content: CSSStyleSheet;
export default content;
}
declare module "*.md" {
const content: string;
export default content;
}Проблема в тому, що ця декларація прив’язана лише до розширення файлу і взагалі не бачить сам атрибут with { type: ... }. Звідси три конкретні болі:
- Якщо хтось забув дописати
with { type: "text" }, TypeScript все одно підставить тип зі старої декларації – помилку ти побачиш тільки в рантаймі. - Доводиться дублювати однакову декларацію для кожного розширення, навіть якщо вони обробляються однаково (
.txtі.mdобидва повертаютьtype: "text"). - Найгірше – бандлер і браузер можуть по-різному інтерпретувати той самий файл залежно від наявності атрибута. Наприклад, бандлер може за замовчуванням застосовувати CSS Modules до
.css-файлу, а явнийtype: "css"у імпорті – перевизначити цю поведінку і повернути сирийCSSStyleSheet. Стара wildcard-декларація просто не могла відрізнити ці два випадки.
Що змінює TypeScript 7.1
Тепер ambient module declarations можна писати з прив’язкою до конкретного import attribute:
declare module "*" with { type: "css" } {
const content: CSSStyleSheet;
export default content;
}
declare module "*" with { type: "text" } {
const content: string;
export default content;
}
declare module "*" with { type: "bytes" } {
const content: Uint8Array;
export default content;
}Тепер, якщо в проєкті з’явиться import readme from "../README.md"; без атрибута – ти отримаєш помилку компіляції, а не сюрприз під час збірки. А якщо додати with { type: "text" }, TypeScript правильно виведе тип string.
Приклад з практики по типу «до / після»:
// ❌ до 7.1: компілюється, але в рантаймі впаде,
// бо реальний рантайм очікує type-атрибут
import readme from "../README.md";
// ✅ після 7.1: без атрибута – помилка типізації,
// з атрибутом – компілятор знає точний тип
import readme from "../README.md" with { type: "text" };Чесні обмеження нової фічі
Перш ніж переписувати всі свої .d.ts-файли, варто знати три речі.
По-перше, імпорти, які підпадають під такі декларації, не звіряються з реальними файлами на диску. TypeScript довіряє wildcard-патерну, а не перевіряє, чи файл фізично існує – ця перевірка все одно лягає на рантайм або білд-процес бандлера. Тобто типобезпека тут про форму даних, а не про існування файлу.
По-друге, ці декларації не входять у built-in бібліотеки TypeScript. Команда прямо радить бібліотекам не публікувати такі декларації у своїх npm-пакетах – кінцевий користувач має оголосити їх сам, у своєму проєкті. Причина практична: різні проєкти по-різному налаштовують глобальну область видимості (хтось підключає lib.dom.d.ts, хтось @types/node), і якщо бібліотека нав’яже власну декларацію *.css, це може конфліктувати з тим, що вже є в проєкті.
По-третє, фіча корисна рівно настільки, наскільки твій рантайм/бандлер взагалі підтримує import attributes. Якщо твій стек ще не оперує with { type: ... } – нові ambient-декларації просто не застосовні, і це нормально.
Коли це варто брати
Якщо ти працюєш з інструментами, що вже підтримують import attributes – типізуй кожен type окремою декларацією замість wildcard по розширенню. Це найбільш відчутно для команд, які одночасно тягнуть CSS Modules через бандлер і браузерний type: "css" в одному кодбейсі – саме тут раніше губилася межа між двома різними типами.
Якщо ж твій проєкт імпортує нетипізовані файли рідко і без атрибутів – чекати на фінальний реліз і не поспішати з бетою цілком розумно.
Міні-чекліст перед тим як чіпати свої .d.ts:
- Чи бандлер або рантайм у проєкті вже підтримує синтаксис
with { type: ... }? - Чи є у проєкті місця, де один і той самий тип файлу імпортується з різними атрибутами (наприклад CSS Modules за замовчуванням і явний
type: "css")? - Чи публікуєш ти бібліотеку назовні – і якщо так, чи варто прибрати власні ambient-декларації з пакета, залишивши їх документацією для користувачів?
Окремо варто тримати в полі зору другий анонс з цього релізу – source phase imports, новий ECMAScript-proposal з ключовим словом import source для default-імпортів. Джерело поки описує лише синтаксис (import source SomeModule from "some-module";), а детальну семантику – що саме повертає такий імпорт – команда TypeScript обіцяє розкрити окремо. Поки конкретики мало, і додавати її з голови не варто – почекаємо на повніший опис.
Якщо тобі цікаво, як узагалі влаштований сучасний TypeScript-компілятор і чому команда взялася за нього так активно переписувати, варто глянути TypeScript 7.0: навіщо компілятор переписали на Go і що зміниться у твоєму проєкті – 7.1 логічно продовжує той самий цикл релізів.
Практичний крок на сьогодні простий: постав бету в окремій гілці, перенеси свої wildcard-декларації на варіант з with { type: ... } і подивись, скільки забутих атрибутів компілятор знайде в існуючому коді. Швидше за все – хоч одне місце знайдеться.