Русский
Русский
English
Статистика
Реклама

Как я закрыл трехлетний issue в TypeScript



Всё началось с моего желания описать структуру сообщений между web worker'ами. К сожалению, на тот момент встроенные возможности TypeScript этого не позволяли.

Я засучил рукава и решил это исправить.

Суть проблемы


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

new Worker().addEventListener('message', (message) => {    message // MessageEvent    message.data // any})

В поле data находятся именно те данные, что вы, автор кода, отправляете. И именно тип этого поля хочется определять.

Изучаем MessageEvent поближе


MessageEvent интерфейс, описывающий сообщения при коммуникации между вкладками, воркерами, сокетами, WebRTC каналами и т.д.

В экосистеме TypeScript этот интерфейс является частью lib.dom.ts и lib.webworker.d.ts, и описан следующим образом:

interface MessageEvent extends Event {    readonly data: any;    readonly lastEventId: string;    readonly origin: string;    readonly ports: ReadonlyArray<MessagePort>;    readonly source: MessageEventSource | null;}

Опытные разработчики сразу увидят тут проблему. Этот интерфейс не Generic поле data описано строго как any, и мы никак не можем на это повлиять из вне.

Решив поискать информацию по этому поводу в репозитории TypeScript я быстро нашел issue в котором описана эта проблема. И даже больше автор предложил решение. Но за почти три года, оно так и не было имплементировано. Я засучил рукава, и решил сделать это.

Всего-то и нужно что привести интерфейс MessageEvent, в файлах lib/lib.dom.d.ts и lib/lib.webworker.d.ts к такому виду:

interface MessageEvent<T = any> extends Event {    readonly data: T;    readonly lastEventId: string;    readonly origin: string;    readonly ports: ReadonlyArray<MessagePort>;    readonly source: MessageEventSource | null;}

Сделаем это.

Изучаем TypeScript Instructions for Contributing Code


В нем есть целый раздел посвященный изменениям в файлах lib.d.ts. Оттуда узнаём две вещи:

  1. Файлы в папке lib/ напрямую изменять нельзя. Там находятся last-known-good версии и они периодически обновляются на основе соответствующих файлов из папки src/lib/. Вот в них то и нужно вносить правки. В нашем случае это src/lib/dom.generated.d.ts и src/lib/webworker.generated.d.ts
  2. Практически все файлы в директории src/lib/ можно просто отредактировать. За исключением генерируемых (.generated.d.ts). Такие файлы создаются с помощью утилиты TSJS-lib-generator и мы должны вносить правки именно в неё.

Изучаем TSJS-lib-generator


TSJS-lib-generator это инструмент (написанный на TS) который принимает все известные Microsoft Edge веб-интерфейсы и преобразовывает их в набор TypeScript интерфейсов. При этом существует возможность переопределить характеристики каких-либо интерфейсов, удалить некоторые или добавить новые.

Все эти правила описываются в json формате в файлах addedTypes.json, overridingTypes.json и removedTypes.json.

Правило для изменения MessageEvent


Нам нужно изменить существующий интерфейс, поэтому будем редактировать overridingTypes.json.

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

Итак, в overridingTypes.json в свойстве interfaces добавляем новый интерфейс, пока что без каких либо свойств:

{  "interfaces": {    "interface": {      "MessageEvent": {}    }  }}

Пробуем запустить сборку и проверим, что ничего не сломалось:

npm run build

TSJS-lib-generator сгенерирует те самые *.generated.d.ts файлы. И сейчас они должны быть идентичны *.generated.d.ts файлам в репозитории TS.

Добавляем свойство type-parameters, тем самым превращая MessageEvent в Generic:

{  "interfaces": {    "interface": {      "MessageEvent": {        "type-parameters": [          {            "name": "T",            "default": "any"          }        ]      }    }  }}

Запускаем сборку и проверяем результат:

interface MessageEvent<T = any> extends Event {    readonly data: any;    readonly lastEventId: string;    readonly origin: string;    readonly ports: ReadonlyArray<MessagePort>;    readonly source: MessageEventSource | null;}

Уже ближе к тому, что мы в итоге хотим получить. Добавим описание свойства data и сигнатуры конструктора:

{  "interfaces": {    "interface": {      "MessageEvent": {        "name": "MessageEvent",        "type-parameters": [          {            "name": "T",            "default": "any"          }        ],        "properties": {          "property": {            "data": {              "name": "data",              "read-only": 1,              "override-type": "T"            }          }        },        "constructor": {          "override-signatures": [            "new<T>(type: string, eventInitDict?: MessageEventInit<T>): MessageEvent<T>"          ]        }      }    }  }}

И вуаля! Генерируется в точности то, что нам необходимо.

interface MessageEvent<T = any> extends Event {    readonly data: T;    readonly lastEventId: string;    readonly origin: string;    readonly ports: ReadonlyArray<MessagePort>;    readonly source: MessageEventSource | null;}

Далее дело за малым:

  • Запустить тесты
  • Оформить Pull Request в соответствии со всеми правилами описанными в Contribution Guidelines
  • И подписать CLA от Microsoft.

Итог


Спустя неделю с момента отправки мой Pull Request был принят. 12.06.2020 были обновлены src/lib/dom.generated.d.ts и src/lib/webworker.generated.d.ts в репозитории TypeScript. А на момент написания статьи все правки перенесены в lib/lib.dom.ts и lib/lib.webworker.d.ts и уже доступны в TypeScript Nightly.
Источник: habr.com
К списку статей
Опубликовано: 21.06.2020 14:07:34
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Typescript

Dom api

Contributing

Категории

Последние комментарии

© 2006-2020, personeltest.ru