Что такое TypeScript. Обзор для JavaScript программистов

Источник: «What is TypeScript? An overview for JavaScript programmers»
Если вы JavaScript программист и хотите получить представление об использовании TypeScript (первый шаг перед изучением подробностей), то эта статья для вас.

TypeScript — это JavaScript плюс синтаксис типов

Несмотря на то что приведённое описание TypeScript не является на 100% точным (есть несколько исключений), я считаю его удобным для понимания того, как он работает: TypeScript — это JavaScript плюс синтаксис типов.

Рассмотрим следующий TypeScript код:

function add(x: number, y: number): number {
return x + y;
}

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

function add(x, y) {
return x + y;
}

Синтаксис типов используется только для проверки типов (во время редактирования и компиляции) и даёт множество проверок согласованности и лучшее автозавершение.

Способы запуска TypeScript кода

Рассмотрим следующий проект на TypeScript:

ts-app/
tsconfig.json
src/
main.ts
util.ts
util_test.ts
test/
integration_test.ts

Давайте рассмотрим способы выполнения этого кода.

Непосредственное выполнение TypeScript

Большинство серверных режимов исполнения теперь могут выполнять код TypeScript напрямую — например, Node.js, Deno и Bun. Другими словами, следующее работает в Node.js 23.6.0+:

cd ts-app/
node src/main.ts

Сборка TypeScript

При разработке веб-приложений сборка является обычной практикой — даже для проектов на чистом JavaScript: Весь код JavaScript (код приложения и код библиотек) объединяется в один файл JavaScript (иногда больше, но никогда не больше нескольких) — обычно загружаемый из HTML-файла. Это даёт несколько преимуществ:

Большинство бандлеров поддерживают TypeScript — либо напрямую, либо через плагины. Это означает, что мы запускаем наш TypeScript код через JavaScript файл bundle.js, созданный бандлером:

ts-app/
tsconfig.json
src/
main.ts
util.ts
util_test.ts
test/
integration_test.ts
dist/
bundle.js

Транспиляция TypeScript в JavaScript

Другой вариант — скомпилировать приложение TypeScript в JavaScript с помощью компилятора TypeScript tsc и запустить полученный код. До появления встроенной поддержки TypeScript в серверных средах исполнения JavaScript это был единственный способ запуска TypeScript.

Компиляция исходного кода в исходный код также называется транспиляцией. tsconfig.json определяет, куда будет записываться вывод транспиляции. Предположим, мы запишем его в каталог dist/:

ts-app/
tsconfig.json
src/
main.ts
util.ts
util_test.ts
test/
integration_test.ts
dist/
src/
main.js
util.js
util_test.js
test/
integration_test.js

Расширения имён файлов локально импортируемых модулей TypeScript

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

// main.ts
import {helperFunc} from './util.js';

Однако можно указать TypeScript переписать расширение имени файла .ts в .js. Тогда следующий импорт работает как при непосредственном выполнении кода, так и при его транспонировании:

// main.ts
import {helperFunc} from './util.ts';

Публикация пакета библиотеки в npm реестре

Реестр npm по-прежнему остаётся самым популярным способом публикации пакетов. Несмотря на то что Node.js поддерживает пакеты приложений, написанные на TypeScript, пакеты библиотек должны быть развёрнуты как JavaScript код — так, чтобы они могли использоваться как JavaScript, так и TypeScript. Поэтому один файл библиотеки lib.ts часто разворачивается в виде пяти файлов (четыре из которых компилируются TypeScript из lib.ts):

(Подробнее, что всё это значит, вы узнаете через секунду).

В качестве примера рассмотрим следующий пакет библиотеки:

ts-lib/
package.json
tsconfig.json
src/
lib.ts
dist/
src/
lib.js
lib.js.map
lib.d.ts
lib.d.ts.map

Основные: .js и .d.ts

Интересно, что комбинация JavaScript и типов в lib.ts разделяется на lib.js, содержащий только JavaScript, и lib.d.ts, содержащий только типы. Зачем это нужно? Это позволяет использовать пакеты библиотек как в JavaScript коде, так и в TypeScript коде:

На самом деле, за кулисами многие редакторы (например, Visual Studio Code) используют своего рода облегчённый режим TypeScript при редактировании JavaScript кода, что позволяет нам также получить простую проверку типов и дописывание кода.

Это исходный файл TypeScript lib.ts

/** Сложение двух чисел. */
export function add(x: number, y: number): number {
return x + y; // сложение чисел
}

Он делится на lib.js:

/** Сложение двух чисел. */
export function add(x, y) {
return x + y; // сложение чисел
}
//# sourceMappingURL=lib.js.map

И lib.d.ts:

/** Сложение двух чисел. */
export declare function add(x: number, y: number): number;
//# sourceMappingURL=lib.d.ts.map

Опционально: source map

Если мы компилируем файл I в файл O, то source map для O сопоставляет места исходного кода в O с местами исходного кода в I. Это означает, что можно работать с O, но видеть информацию из I — например:

Все функции, связанные с source map, за исключением трассировки стека, требуют доступа к исходному TypeScript коду. Вот почему имеет смысл включать lib.ts, если есть source map.

Так выглядит lib.js.map:

{
"version": 3,
"file": "lib.js",
"sourceRoot": "",
"sources": [
"../../src/lib.ts"
],
"names": [],
"mappings": "AAAA,uBAAuB;AACvB,MAAM,UAAU,···"
}

Так выглядит lib.d.ts.map:

{
"version": 3,
"file": "lib.d.ts",
"sourceRoot": "",
"sources": [
"../../src/lib.ts"
],
"names": [],
"mappings": "AAAA,uBAAuB;AACvB,wBAAgB,GAAG,···"
}

В обоих случаях фактическое содержимое «сопоставлений» было сокращено. А в реальном выводе tsc JSON всегда сжимается в одну строку.

DefinitelyTyped: репозиторий с типами для пакетов npm без типов

Сейчас многие пакеты npm поставляются с типами TypeScript. Однако далеко не во всех они есть. В этом случае может помочь DefinitelyTyped: Если он поддерживает пакет pkg без типов, то можно установить пакет с типами @types/pkg для pkg.

Одним из важных пакетов DefinitelyTyped для Node.js является пакет @types/node с типами для всех его API. Если вы разрабатываете TypeScript на Node.js, то этот пакет обычно входит в число зависимостей разработки.

Компиляция TypeScript с помощью инструментов, отличных от tsc

Давайте вспомним задачи, выполняемые tsc (в этом разделе пропустим source map):

  1. Компилирует файлы TypeScript в файлы JavaScript.
  2. Компилирует файлы TypeScript в файлы объявления типов.
  3. Проверяет типы TypeScript файлов.

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

Есть также параметры tsconfig.json, предупреждающие нас, если мы не придерживаемся этих подмножеств TypeScript (подробнее (eng))). На практике это не такая уж большая жертва.

Type Stripping

Type stripping — простейший способ компиляции TypeScript в JavaScript:

Второй пункт означает, что некоторые возможности TypeScript не поддерживаются — например:

Одним из существенных преимуществ type stripping является то, что оно не требует настройки (через tsconfig.json или другими способами), потому что очень простое. Это делает платформы, использующие его, более устойчивыми к изменениям, вносимым в TypeScript.

Техника type stripping: замена типов пробелами

Одна из умных техник удаления типов была впервые применена в инструменте ts-blank-space (автор Ashley Claymore для Bloomberg): Вместо удаления синтаксиса типа он заменяет его пробелами. Это означает, что позиции исходного кода в выводе не меняются. Таким образом, все позиции, отображаемые (например, в трассировках стека), продолжают работать на входе, и необходимость в source map снижается: Они по-прежнему нужны для отладки и перехода к определениям, но JavaScript, сгенерированный в результате type stripping, относительно близок к исходному TypeScript, и часто даже в этом случае всё в порядке.

Например — ввод (TypeScript):

function add(x: number, y: number): number {
return x + y;
}

Вывод (JavaScript):

function add(x        , y        )         {
return x + y;
}

Если хотите исследовать глубже, можете заглянуть на игровую площадку ts-blank-space.

Изолированные объявления

«Изолированное объявление» — стиль написания TypeScript, при котором типы для файла объявления (.d.ts) легко извлекаются. В основном это означает предоставление возвращаемых типов для экспортируемых функций. В принципе, TypeScript может сделать это за нас, но простые генераторы файлов объявлений этого сделать не могут. Это ограничение не существует для неэкспортируемых функций, потому что они не отображаются в файлах объявления.

Первая версия файла TypeScript strings.ts:

// Не OK: экспортируемая функция без возвращаемого типа
export function upperCase(str: string) {
return str.toUpperCase();
}

// Не экспортируется, возвращаемый тип не требуется
function internalHelper() {}

strings.ts в стиле изолированных объявлений:

// OK: экспортируемая функция содержит возвращаемый тип
export function upperCase(str: string): string {
return str.toUpperCase();
}

// Не экспортируется, возвращаемый тип не требуется
function internalHelper() {}

Это сгенерированный файл объявлений strings.d.ts (обратите внимание, что internalHelper в нем отсутствует):

export declare function upperCase(str: string): string;

JSR — JavaScript реестр

JavaScript реестр JSR — альтернатива npm и npm реестру для публикации пакетов. Он работает следующим образом:

В отличие от npm реестра, ваш пакет TypeScript библиотеки можно использовать на Node.js только в том случае, если вы загрузите .js файлы и .d.ts файлы.

JSR также предоставляет несколько возможностей, которых нет у npm, например, автоматическую генерацию документации. Дополнительную информацию можно найти в официальной документации Why JSR?.

Кому принадлежит JSR

Цитирую страницу официальной документации Governance:

JSR не принадлежит ни одному человеку или организации. Это проект, управляемый сообществом, открытый для всех и созданный для всей экосистемы JavaScript.

В настоящее время JSR управляется компанией Deno. В настоящее время мы работаем над созданием совета по управлению проектом, который затем будет работать над переводом проекта в фонд.

Редактирование TypeScript

Две популярные IDE для JavaScript:

Замечания в этом разделе относятся к Visual Studio Code, но могут быть применимы и к другим IDE.

В Visual Studio Code мы получаем два различных способа проверки типов:

Проверка типов JavaScript файлов

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

/**
* @param {number} x - Первый операнд
* @param {number} y - Второй операнд
* @returns {number} Сумма обоих операндов
*/

function add(x, y) {
return x + y;
}

Если сделать так, то мы все равно будем писать на TypeScript, просто с другим синтаксисом.

Преимущества такого подхода:

Недостатки такого подхода:

Чтобы пояснить недостатки, рассмотрим, определение интерфейса в TypeScript:

interface Point {
x: number;
y: number;
/** опциональное свойство */
z?: number;
}

И через JSDoc:

/**
* @typedef Point
* @prop {number} x
* @prop {number} y
* @prop {number} [z] опциональное свойство
*/

Более подробная информация содержится в TypeScript Handbook:

Комментарии


Дополнительные материалы

Предыдущая Статья

CSS трансформации и матрица

Следующая Статья

Спокойное подчёркивание текста