Узнайте обо всех новых функциях TypeScript с полезными примерами для дополнительного контекста.

1. Декораторы

Если вы понимаете, что такое декораторы, смело переходите к проблеме №1. Если вы не знаете, что такое декораторы, или хотите быстро освежиться, то вот краткий обзор…

"Декораторы — это будущая функция ECMAScript, которая позволяет нам повторно использовать классы и их элементы".

class Pokemon {
    name: string;
    constructor(name: string) {
        this.name = name;
    }

    pokedex() {
        console.log(`This Pokemon is called ${this.name}!`);
    }
}

const pikachu = new Pokemon("Pikachu");
pikachu.pokedex();

// Output: 
// 
// This Pokemon is called Pikachu!

Приведенный выше код является примером очень простого компонента класса с именем Pokemon, который имеет один метод с именем pokedex(), который просто записывает в консоль имя покемона.

Теперь давайте добавим некоторые функции ведения журнала в этот компонент класса, чтобы облегчить отладку:

class Pokemon {
    name: string;
    constructor(name: string) {
        this.name = name;
    }

    pokedex() {
        console.log("LOG: Entering method.");

        console.log(`This Pokemon is called ${this.name}!`);

        console.log("LOG: Exiting method.")
    }
}

const pikachu = new Pokemon("Pikachu");
pikachu.pokedex();

// Output: 
// 
// LOG: Entering method.
// This Pokemon is called Pikachu!
// LOG: Exiting method.

Теперь представьте, что у вас есть много компонентов класса с множеством различных методов, все они еще более сложны, чем в приведенном выше примере, и мы хотим добавить эту функцию ведения журнала ко всем из них для целей отладки. Здесь сияют декораторы.

Добавление декоратора метода к методу pokedex нашего компонента класса будет выглядеть примерно так:

function LoggingDecorator(target: any, methodName: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;

  descriptor.value = function(...args: any[]) {
    console.log(`Calling ${methodName}`);
    const result = originalMethod.apply(this, args);
    console.log(`Exiting ${methodName}`);
    return result;
  }

  return descriptor;
}

Это выглядит ужасно, правда? Что ж, на самом деле все не так плохо, как кажется.

Приведенная выше функция в основном делает то же, что и «функция регистрации» в предыдущем примере, только теперь мы извлекли ее в отдельную функцию, поэтому мы можем повторно использовать ее в нескольких методах класса.

class Pokemon {
    name: string;
    constructor(name: string) {
        this.name = name;
    }
    @LoggingDecorator
    pokedex() {
        console.log(`This Pokemon is called ${this.name}!`);
    }
}

К счастью, использовать декораторы намного проще, чем писать их! Как вы можете видеть в приведенном выше примере, мы используем декораторы, добавляя @ перед именем декоратора (LoggingDecorator становится @LoggingDecorator ), а затем мы добавляем функцию декоратора непосредственно перед методом, к которому мы хотим ее применить.

Мы добавили его прямо над методом pokedex(), так как именно здесь мы хотим применить логику декоратора.

Для получения дополнительной информации обо всех типах декораторов см. этот симпатичный обзор.

Проблема №1
До TypeScript 5.0 реализация декораторов в TypeScript была экспериментальной и требовала от вас согласия с флаг компилятора --experimentalDecorators

Из-за этой ограниченной поддержки декораторов аннотации типов не сохранялись, что означало, что при применении декоратора к классу или методу аннотации типов для параметров и возвращаемого значения терялись. strong>, что затрудняет обеспечение безопасности типов.

Решение №1
В TypeScript 5.0 значительно улучшена реализация декоратора, что означает, что декораторы теперь намного лучше работают с системой типов. Эти улучшения включают в себя:

  • улучшена поддержка аннотаций типов
  • улучшено отражение метаданных
  • более согласованное поведение на разных платформах
  • лучшая производительность

2. параметры константного типа

Проблема №2
TypeScript слишком общий, когда он выводит объект, и нам нужно, чтобы он был более конкретным.

В приведенном ниже примере у нас есть тип с именем Songs, который должен быть массивом строк. Обратите внимание, что это readonly, так как мы не хотим изменять его позже:

type Songs = { songs: readonly string[] };

Затем мы создаем функцию с именем returnSongs, которая делает именно то, о чем говорит: она возвращает массив песен, введенный в качестве аргумента:

type Songs = { songs: readonly string[] };

function returnSongs<T extends Songs>(arg: T): T["songs"] {
  return arg.songs;
}

Затем мы запускаем эту функцию с нашим массивом песен и присваиваем результат переменной amazingTunes:

type Songs = { songs: readonly string[] };

function returnSongs<T extends Songs>(arg: T): T["songs"] {
  return arg.songs;
}

const amazingTunes= returnSongs({
  songs: ["Babooshka", "Running Up That Hill", "Wuthering Heights"],
});

Здесь все хорошо, пока мы не наведем курсор на amazingTunes :

При выводе типа объекта TypeScript обычно выбирает тип, который должен быть общим. Вот почему мы видим const amazingTunes: string[], а не более конкретный тип.

TypeScript здесь имеет большое значение, так как он допускает возможность того, что мы можем захотеть мутировать этот объект позже.

Однако иногда мы знаем лучше, чем TypeScript*хлопает по рукам*и в этом случае мы знаем, что не хотим, чтобы значение amazingTunes менялось. Вот почему мы добавили тег readonly

Раньше мы могли использовать as const для решения этой проблемы, но теперь есть еще более изящное решение…

Решение №2
В TypeScript 5.0 мы можем добавить const перед T в нашем типе функции:

type Songs = { songs: readonly string[] };

function returnSongs<const T extends Songs>(arg: T): T["songs"] {
  return arg.songs;
}

const amazingTunes = returnSongs({
  songs: ["Babooshka", "Running Up That Hill", "Wuthering Heights"],
});

И теперь, когда мы наводим курсор на amazingTunes:

Мы также получаем ошибку, когда пытаемся мутировать amazingTunes, чего раньше не было:

3. Поддержка нескольких файлов конфигурации в расширениях

Проблема №3
Допустим, мы работаем с TypeScript и хотим, чтобы все наши проекты использовали некоторые общие конфигурации. Мы можем сделать это, используя extends и указав, из какого файла мы хотим получить конфигурации. Например:

// common.json
{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "noImplicitReturns": true
  },
  "formatOptions": {
    "tabSize": 2,
    "singleQuote": true
  }
}

// frontend.json
{
  "extends": ["./common.json"],
  "compilerOptions": {
    "target": "es2020"
  }
}

В приведенном выше примере у нас есть файл common.js с общей конфигурацией, которой мы хотим поделиться с другими проектами. Файл frontend.json использует эту конфигурацию с “extends”: [“./common.json”], а также добавляет файл target.

Это все хорошо, но если бы мы хотели, чтобы наши проекты использовали конфигурацию из нескольких файлов конфигурации, это было бы намного сложнее…

Решение №3
TypeScript 5.0 значительно упростил этот процесс, позволив нам указать несколько файлов конфигурации в параметре extends.

Это означает, что мы можем создать набор общих файлов конфигурации, которые можно легко использовать в разных проектах. Например:

// common.json
{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "noImplicitReturns": true
  },
  "formatOptions": {
    "tabSize": 2,
    "singleQuote": true
  }
}

// tsconfig1.json
{
    "compilerOptions": {
        "strictNullChecks": true
    }
}

// frontend.json
{
    "extends": ["./common.json", "./tsconfig1.json"],
    "files": ["./index.ts"]
}

В приведенном выше примере frontend.json теперь расширяет конфигурации common.json и tsconfig1.json, что позволяет нам комбинировать и переопределять настройки по мере необходимости.

ПРИМЕЧАНИЕ. Последний файл в массиве extends будет расширять предыдущий файл и так далее. В приведенном выше примере это означает, что файл tsconfig1.json расширяет файл common.json. Если между файлами конфигурации есть какие-либо конфликты, то последний файл в массиве имеет приоритет.

4. Все enum являются объединением enum

Итак, сначала коротко о перечислениях. Если вы думаете, что знаете enum-ph, можете сразу переходить к вопросу №4 (даже для меня это было натянуто).

Числовые перечисления

В TypeScript перечисления обычно используются для определения набора именованных констант.

В приведенном ниже примере у нас есть перечисление под названием Pokemon, которое содержит имена трех покемонов и соответствующие им номера эволюции.

enum Pokemon {
  Bulbasaur = 1,
  Charmander = 4,
  Squirtle = 7,
}

Мы можем использовать перечисление Pokemon, чтобы убедиться, что определенным переменным можно присвоить только определенное значение. В этом примере любой переменной с типом перечисления Pokemon можно присвоить только 1 4 или 7.

enum Pokemon {
  Bulbasaur = 1,
  Charmander = 4,
  Squirtle = 7,
}

const bulbasaurNumber: Pokemon = 1;
const charmanderNumber: Pokemon = 4;
const squirtleNumber: Pokemon = 7;

Если мы попытаемся присвоить любое другое значение переменной этого типа, то получим ошибку:

Проблема № 4
Проблема с перечислениями возникает с литеральными типами перечислений. Это когда:
(1) каждый элемент перечисления имеет свой тип и
(2) само перечисление является объединением каждого типа элемента.

В нашем примере членами перечисления Pokemon являются:
bulbasaur
charmander
squirtle

Их соответствующие типы:

bulbasaur = 1
charmander = 4
squirtle = 7

а перечисление Pokemon представляет собой объединение каждого типа члена:

Pokemon = bulbasaur | charmander | squirtle

Назначение каждому элементу перечисления собственного типа (bulbasaur = 1) означает, что этот тип связан с значением элемента. В данном случае значение равно 1.

Это проблема, потому что не всегда возможно вычислить это значение. Допустим, мы хотим получить случайное число покемонов, поэтому мы добавляем функцию в наше перечисление, чтобы сделать это:

enum Pokemon {
  Bulbasaur = 1,
  Charmander = 4,
  Squirtle = 7,
  randomPokemon = Math.floor(Math.random() * 151),
}

Проблема здесь в том, что значение не инициализируется до тех пор, пока не будет вызвана функция.

Решение №4
В TypeScript 5.0 все перечисления обрабатываются как объединения их типов членов. уникальный тип вычисляется для каждого члена перечисления, и это значение зависит от того, имеет ли он константу или непостоянный инициализатор. Пример кода и соответствующий текст ниже дают отличное объяснение этому:

enum E {
    A = 10 * 10,  // Numeric literal enum member
    B = "foo",    // String literal enum member
    C = bar(42),   // Opaque computed enum member
}

В результате повышается безопасность типов и упрощается работа с перечислениями. Ву!

5. Полное завершение switch/case

Проблема № 5
Представим, что у нас есть довольно обширный тип объединения:

type Pokemon =
  | "Bulbasaur"
  | "Ivysaur"
  | "Venusaur"
  | "Charmander"
  | "Charmeleon"
  | "Charizard"
  | "Squirtle"
  | "Wartortle"
  | "Blastoise";

… и мы хотим создать функцию, которая возвращает что-то другое в зависимости от члена союза. В настоящее время у нас есть 9 типов, которые составляют тип объединения Pokemon, а это значит, что нам нужно вернуть 9 отдельных значений.

Лучше всего это написать с помощью оператора switch. Например:

function gottaCatchEmAll(pokemon: Pokemon): string {
  switch (pokemon) {
    case "Bulbasaur":
      console.log("Bulbasaur is a grass type!");
    case "Ivysaur":
      console.log("Ivysaur is a grass type!");
    case "Venusaur":
      console.log("Venusaur is a grass type!");
    case "Charmander":
      console.log("Charmander is a fire type!");
    case "Charmeleon":
      console.log("Charmeleon is a fire type!");
    case "Charizard":
      console.log("Charizard is a fire type!");
    case "Squirtle":
      console.log("Squirtle is a water type!");
    case "Wartortle":
      console.log("Wartortle is a water type!");
    case "Blastoise":
      console.log("Blastoise is a water type!");
      return "Gotta catch 'em all!";
  }
}

gottaCatchEmAll("Squirtle") // this will return: "Squirtle is a water type!"

Как видите, нам нужно пару строк кода для каждого case, и по мере роста этого типа объединения нам становится легче пропустить одну из этих cases в нашем выражении switch/case.

Решение №5
Теперь в TypeScript 5.0 добавлено исчерпывающее завершение регистра для предоставления всех возможных cases, которых еще нет в выражении switch.

Проще говоря, это означает, что когда мы вводим наше первое case внутри выражения switch, TypeScript автоматически добавит все остальные возможные случаи. Это гарантирует, что мы не забудем добавить case.

6. Поддержка export type *

Проблема №6
Представим, что у нас есть файл со списком типов. Например:

// types.ts
export interface Pokemon {
  name: string;
  level: number;
}

export interface Pokedex {
  id: number;
  name: string;
  description: string;
}

Мы хотим экспортировать эти типы для использования в другом месте нашего кода. Чтобы сделать это в TypeScript, нам нужно создать файл index.ts и вручную перечислить все экспорты по отдельности. Например:

// types.ts
export interface Pokemon {
  name: string;
  level: number;
}

export interface Pokedex {
  id: number;
  name: string;
  description: string;
}

// index.ts
export { Pokemon } from './types';
export { Pokedex } from './types';

Теперь представьте, что в файле types.ts есть сотни типов, которые мы хотим экспортировать. Уф, сколько экспортных заявлений нам нужно написать и потенциально напортачить…

Решение № 6
Как и в JavaScript, TypeScript 5.0 теперь позволяет нам экспортировать все эти типы из файла index.ts с помощью export type *. Например:

// types.ts
export interface Pokemon {
  name: string;
  level: number;
}

export interface Pokedex {
  id: number;
  name: string;
  description: string;
}

// index.ts
export type * from './types';

Как видите, теперь мы можем экспортировать все типы из модуля с помощью одной строки кода.

В TypeScript 5.0 у нас также больше контроля над тем, какие типы экспортируются из модуля. Как мы видели выше, export type * будет экспортировать все типы из модуля, но мы также можем выборочно экспортировать определенные типы. Например:

export type { Foo, Bar } from './types'

// This will only export the Foo and Bar types from the module.

7. Сортировка импорта без учета регистра в редакторах

Проблема №7
У нас есть список импортируемых данных, которые TypeScript автоматически сортирует для нас:

import {
    Toggle,
    freeze,
    toBoolean,
} from "./utils";

Можно ли сказать, что этот список отсортирован правильно?

Я бы сказал нет, потому чтоэто не в алфавитном порядке. Но…

…если мы заботимся о чувствительности к регистру, то этот список отсортирован правильно, поскольку строки в верхнем регистре идут перед строчными в некоторых языках программирования, таких как JavaScript.

Это связано с тем, что строка организована по размеру байта, а прописные буквы имеют больший размер байта, чем строчные.

Большая проблема возникает, когда мы используем такой инструмент, как ESLint.
ESLintне учитывает регистр при сортировке импорта, поэтому TypeScript и ESLint в конечном итоге спорят о том, как лучше сортировать импорт. На самом деле немного по-детски.

Решение №7
Простое решение: в TypeScript 5.0 порядок сортировки при импорте нечувствителен к регистру. Это означает, что TypeScript и такие инструменты, как ESLint, теперь могут прекрасно работать вместе. Такой рост.

--moduleResolution bundler

Если вы не знакомы с параметрами --module и --moduleResolution в TypeScript, следующее может помочь дать некоторый контекст для следующей проблемы и решения:

Параметр --module указывает стратегию генерации кода модуля, которую TypeScript должен использовать при выводе JavaScript. Он определяет синтаксис модуля, используемый в созданном JavaScript, например, CommonJS ("commonjs"), модули ES ("es2020") или AMD ("amd") и другие.

Параметр --moduleResolution определяет стратегию, которую TypeScript использует для разрешения зависимостей модулей. Он указывает, как TypeScript находит импортированные модули в исходном коде.

Проблема № 8
Допустим, мы используем сборщик, такой как Webpack, для компиляции нашего кода TypeScript, но когда мы запускаем наш код, мы получаем ошибки времени выполнения.

Это может быть связано с параметрами node16 и nodenext, которые TypeScript 4.7 представил для параметров --module и --moduleResolution.

Эти параметры изначально были введены для лучшего моделирования точных правил поиска для модулей ECMAScript в Node.js. Однако они вводят ограничения, которых нет у других инструментов, что приводит к ошибкам во время выполнения.

Решение №8
В TypeScript 5.0 появилось новое значение параметра --moduleResolution, которое называется bundler. Этот параметр предназначен для сборщиков и сред выполнения, которые имеют ряд функций разрешения, подобных Node, и синтаксис ESM, но не применяют строгие правила разрешения, которые сопровождают модули ES в Node или в браузере. ¹

Вы можете включить эту опцию, добавив следующий код в файл конфигурации:

{
  "compilerOptions": {
    "moduleResolution": "bundler"
  }
}

Флаги настройки разрешения

Проблема № 9
Эта проблема на самом деле связана с функцией--moduleResolution bundler, которую мы только что рассмотрели:

Поскольку TypeScript 5.0 теперь предлагает «гибридные» правила разрешения, другие инструменты могут немного отличаться в своей поддержке, поэтому может потребоваться дополнительная настройка, чтобы убедиться, что все работает.

Решение №9
В TypeScript 5.0 добавлены следующие флаги, чтобы вы могли включать или отключать определенные функции, которые могут работать или не работать с вашей конфигурацией. Эти флаги позволяют лучше контролировать разрешение модулей, что полезно при настройке процесса сборки:

  • --allowImportingTsExtensions: позволяет импортировать файлы TypeScript с расширениями, характерными для TypeScript, такими как .ts, .mts или .tsx.
  • --resolvePackageJsonExports: заставляет TypeScript обращаться к полю экспорта файлов package.json при чтении из пакета в node_modules.
  • --resolvePackageJsonImports: заставляет TypeScript обращаться к полю импорта файлов package.json при выполнении поиска, начинающегося с #.
  • --allowArbitraryExtensions: Позволяет импортировать файлы с неизвестными расширениями, ища файл объявления в форме {базовое имя файла}.d.{расширение}.ts.
  • --customConditions: принимает список дополнительных условий, которые необходимо учитывать при разрешении TypeScript из поля экспорта или импорта файла package.json.²

--verbatimModuleSyntax

Проблема №10
Представьте, что у нас есть следующий оператор импорта:

import { Car } from "./car";

export function drive(car: Car) {
    // ...
}

TypeScript достаточно умен, чтобы понять, что мы просто используем Car в качестве типа, поэтому, когда он компилируется в JavaScript, он полностью отбрасывает импорт, и код будет выглядеть так:

export function drive(car) {
    // ...
}

Отбрасывание импорта называется удалением импорта.

Это кажется безобидной и полезной вещью, которую делает TypeScript, и это частично правильно.

Это полезно, потому что, если Car не является значением, экспортированным из ./car, мы фактически получим ошибку времени выполнения. Итак, спасибо TypeScript!

Но безвредно? Не так много. Хотя TypeScript достаточно умен, чтобы знать, когда следует отказаться от оператора импорта, не все компиляторы могут это сделать.

TypeScript имеет модификатор type для импорта и экспорта, который полезен в таких ситуациях. Например:

// The named import/export 'Car' can be dropped in JS output
import { type Car } from "./car";
export { type Car } from "./car";

Однако существуют проблемы с модификатором type, такие как удаление модуля, которые могут привести к отбрасыванию импорта, а также тот факт, что вы не обязаны делать различие между type и простым импортом и экспортом.

TypeScript имеет флаг --importsNotUsedAsValues, который гарантирует, что мы используем модификатор type.

Существует также флаг --preserveValueImports, который предотвращает некоторое поведение модуля.

А флаг --isolatedModules гарантирует, что код работает должным образом в разных компиляторах.

Однако эти флаги не решают всех проблем.

Решение №10
В TypeScript 5.0 появился --verbatimModuleSyntax. При включении этого параметра применяются следующие правила:
1. Импорт или экспорт без модификатора type не удаляются.
2. Импорт или экспорт, в которых используется модификатор type, отбрасываются.

Например:

// Erased away entirely.
import type { A } from "a";

// Rewritten to 'import { b } from "bcd";'
import { b, type c, type d } from "bcd";

// Rewritten to 'import {} from "xyz";'
import { type xyz } from "xyz";

Как видите, это значительно упрощает задачу — и нам, и компиляторам. ³

Вы можете включить эту опцию в файле конфигурации TypeScript с помощью следующего кода:

{
  "compilerOptions": {
    "verbatimModuleSyntax": true
  }
}

@satisfies support в JSDoc

Если вы знакомы с ключевым словом satisfies (появившимся в TypeScript 4.9), смело переходите к проблеме №11. Если не…

…оператор satisfies позволяет нам проверить, что тип выражения соответствует некоторому типу, не изменяя результирующий тип этого выражения.

Например, предположим, что у нас есть следующая переменная с именем Pikachu, имеющая тип Pokemon:

type Level = number | string;
type Pokemon = Record<string, Level>;

const Pikachu: Pokemon = {
    type: 'electric',
    level: 30,
}

Это выглядит нормально, верно? Ну а если мы хотим повысить уровень Pikachu ?

Мы получаем ошибку от TypeScript, потому что то, как мы написали наш тип Pokemon, означает, что мы потеряли уровень специфичности в нашем объекте. Мы знаем, что level — это number в нашем случае, но TypeScript — нет, и здесь на помощь приходит satisfies:

type Level = number | string;
type Pokemon = Record<string, Level>;

const Pikachu = {
  type: "electric",
  level: 30,
} satisfies Pokemon;

Pikachu.level++;

Используя satisfies, мы проверяем, что тип Pikachu соответствует типу Pokemon, не изменяя результирующий тип Pikachu на менее конкретный.

Проблема № 11
Отлично, что у нас есть ключевое слово satisfies для проверки типов переменных в нашем коде TypeScript, но что, если мы хотим использовать TypeScript для проверки типов нашего кода JavaScript с помощью аннотаций JSDoc?

Решение № 11
В TypeScript 5.0 появился новый тег JSDoc под названием @satisfies, который делает то же самое для кода JavaScript, что и ключевое слово satisfies для кода TypeScript:

// interface Pokemon {
//   name(name: string): string;
// }

/**
 * @typedef {Function} Pokemon
 * @param {string} name
 * @returns {string}
 */

/**
 * @satisfies {Pokemon}
 */
function pokemon(name: string) {
  return `This Pokemon is called ${name}!`;
}

В приведенном выше примере функция pokemon имеет тег JSDoc @satisfies, который проверяет соответствие интерфейсу Pokemon.

@overload support в JSDoc

Опять же, если вы знакомы с перегрузками, не стесняйтесь сразу переходить к проблеме №12, иначе…

Перегрузки дают нам способ сказать, что функция может вызываться с разными аргументами и, возможно, возвращать разные результаты. Они могут ограничить то, как вызывающие абоненты могут на самом деле использовать наши функции, и уточнить, какие результаты они получат в ответ.

Вот пример использования перегрузок:

function greet(name: string): void;
function greet(firstName: string, lastName: string): void;

function greet(arg1: string, arg2?: string): void {
  if (arg2) {
    console.log(`Hello, ${arg1} ${arg2}!`);
  } else {
    console.log(`Hello, ${arg1}!`);
  }
}

greet("John"); // Output: Hello, John!
greet("Jane", "Doe"); // Output: Hello, Jane Doe!

В этом примере вывод функции зависит от того, предоставлен ли второй аргумент функции.

Ошибка №12
Та же история, что и Ошибка №11! Нам очень нравится, что мы можем использовать перегрузки, поскольку они обеспечивают безопасность типов при вызове функции с различными комбинациями аргументов. Но… что, если мы хотим использовать их в наших файлах JavaScript?

Решение №12
В TypeScript 5.0 появился тег @overload, который теперь позволяет JSDoc объявлять перегрузки. Каждый комментарий JSDoc с тегом @overload рассматривается как отдельная перегрузка для следующего объявления функции.

Используя наш предыдущий пример, это будет выглядеть так:

/**
 * @overload
 * @param {string} arg1
 * @return {string}
 */

/**
 * @overload
 * @param {string} arg1
 * @param {string} arg2
 * @return {string}
 */

/**
 * @param {string} arg1
 * @param {string} arg2
 */
function greet(arg1: string, arg2?: string): void {
  if (arg2) {
    console.log(`Hello, ${arg1} ${arg2}!`);
  } else {
    console.log(`Hello, ${arg1}!`);
  }
}

Передача флагов, специфичных для излучения, под --build

Проблема № 13
Представим, что мы хотим иметь разные сборки для разработки и производства для нашего кода. Мы хотим, чтобы наша рабочая сборка библиотеки создавала файлы объявлений, но не наша сборка для разработки.

Решение № 13
В TypeScript 5.0 теперь мы можем передавать следующие флаги, относящиеся к эмиссии, с помощью флага --build:

  • --declaration: Создайте .d.ts файлов из файлов TypeScript и JavaScript в своем проекте.
  • --emitDeclarationOnly: выводить только файлы d.ts, а не файлы JavaScript.
  • --declarationMap: Создать исходные карты для d.ts файлов.
  • --sourceMap: Создать исходные файлы карты для созданных файлов JavaScript.
  • --inlineSourceMap: Включить файлы исходной карты в сгенерированный JavaScript.¹

Для проблемы, описанной выше, теперь мы можем отключить флаг --declaration по умолчанию и запустить нашу сборку для разработки со следующим:

tsc --build -p ./my-project-dir

И мы можем просто добавить флаг --declaration для нашей производственной сборки:

tsc --build -p ./my-project-dir --declaration

Спасибо за прочтение! Я знаю, что это ОГРОМНАЯ статья, но я надеюсь, что вы нашли ее полезной!

Большая часть информации в этой статье взята из официальной документации TypeScript 5.0, и я также нашел эту статью от Zack действительно полезной!