Как использовать веб-воркеров, написанных на TypeScript, в приложении Angular без настраиваемой конфигурации веб-пакета?

При выполнении рефакторинга приложения Angular 6 возникает необходимость перенести простые старые веб-воркеры Javascript на TypeScript. После некоторых исследований выяснилось, что на момент написания этой статьи простого способа сделать это с помощью angular-cli не было.

Примечание: в версии 8 запланирована улучшенная поддержка веб-воркеров с помощью angular-cli.

Веб-воркеры TypeScript требуют тяжелой конфигурации веб-пакета за пределами angular-cli, как обсуждается в этом сообщении.

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

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

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

Полные источники примеров, цитируемых в этой статье, доступны здесь: https://github.com/fleureyf/angular-web-workers



Объем этой статьи

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

  • Возможное использование внешних зависимостей Javascript
  • Полная двунаправленная связь между основным и рабочим потоком
  • Реализация worker-процесса Typescript разбита на несколько классов TypeScript в отдельных файлах.

Оглавление

Интерфейсы между основным и рабочим потоком

Мы хотим использовать TypeScript для обеспечения соблюдения типов везде, где это возможно. Сначала мы определяем некоторые интерфейсы, которые будут использоваться для обмена между основным потоком (в нашем случае приложением Angular) и рабочими потоками.

Фоновая задача обернет задания, которые мы хотим выполнить в рабочем потоке. Сообщения фоновой задачи будут отправлены рабочим потоком обратно в основной поток для отслеживания состояния и выполнения.

Простой рабочий

Давайте начнем с написания простого рабочего TypeScript со всей логикой, содержащейся в одном классе.

Действия рабочих

Мы продолжаем использовать TypeScript и применяем прототип действий, поддерживаемых воркером.

Таким образом, рабочий реализует этот интерфейс и соответствующим образом управляет его состоянием.

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

Примечание. При транспиляции к prototype будет добавлено «обозначение метода», а вместо него в качестве свойства будет добавлено обозначение «толстая стрелка» (или функция). Или позже мы обратимся к функции toString(), которая в нашем случае не поддерживает содержимое prototype.

Импорт внешних скриптов

Одна из основных потребностей рабочих - импортировать внешние сценарии Javascript с помощью встроенной функции importScripts. Мы также будем использовать функцию postMessage для отправки сообщений обратно в основное приложение. Мы можем объявить эти типы, поскольку знаем, что они будут доступны во время выполнения.

Для работы с нашей архитектурой importScripts аргументы должны быть полным URL-адресом, включая базовое расположение приложения (например, http://my.domain.org/assets/my_external_js_dependency.js).

Чтобы получить базовое местоположение, и другие параметры, которые могут быть полезны на рабочей стороне, мы принимаем объект WorkerConfig в конструкторе.

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

Рабочий сервис

Сервис Angular позаботится обо всем управлении работниками, включая:

  • Создание экземпляра рабочего
  • Отправка инструкции работнику
  • Получение сообщений от воркера
  • Прекращение работника

Сложная часть: создание URL-адреса рабочего объекта

Сначала мы создаем URL-адрес объекта, содержащий полезную нагрузку рабочего. Это уловка, которая позволяет нам использовать нашего рабочего TypeScript без конфигурации сборки. Он использует тот факт, что мы можем преобразовать любой символ Javascript в строку.

Примечание: этот шаг также вызывает ограничения метода

Нам нужно написать простой текст (к сожалению), чтобы создать экземпляр нашего рабочего класса и подключить прослушиватель событий.

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

Создание экземпляра рабочего

Когда задача запускается, создается новый воркер, и все его сообщения передаются в Observable для облегчения конечного потребления в приложении.

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

Отправка инструкции работнику

Давайте создадим метод notify для отправки инструкций работнику. Мы можем установить тип action.

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

Получение сообщений от воркера

Для облегчения использования мы заключим все сообщения, полученные рабочим, в Observable.

Мы просто присоединяем к рабочему процессору два слушателя для message и error и отправляем данные события наблюдаемому. Мы также устанавливаем тип сообщения BackgroundTaskMessage.

Инструкция start отправляется работнику после присоединения слушателей.

Прекращение работника

Когда задача завершена, работник отправит сообщение со статусом TERMINATED. Когда это сообщение получено, наблюдаемое завершается, а рабочий процесс завершается.

использование

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

Приложение

Используйте другой класс TypeScript внутри рабочего

Нам все еще не хватает одной функции: использовать другие классы TypeScript внутри нашего worker.

Если мы просто используем другой класс в нашем основном рабочем классе, он не будет доступен во время выполнения. Помните, что мы просто создаем строковое представление одного из наших символов (в основном BackgroundWorker в нашем примере), но никто (_ Webpack_) здесь не для разрешения наших зависимостей. Таким образом, мы должны «внедрить» себе внешние символы TypeScript, которые мы хотим использовать.

Хорошо, что мы можем обеспечить соблюдение типов наших зависимостей на стороне worker. Плохо то, что нам придется добавить простой текст в службу Angular, как мы это сделали для свойств.

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

Ограничения

Есть некоторые другие ограничения для кода, написанного для выполнения в рабочем потоке. Он включает в себя сам рабочий класс, а также все «внедренные» зависимости.

  • Как мы уже упоминали, все методы должны быть написаны с использованием нотации жирных стрелок ES6, иначе они будут недоступны во время выполнения.
  • Нет поддержки async / await
  • Нет поддержки расширений

В целом, я думаю, что это хорошее решение для использования Typescript в фоновых рабочих процессах, сохраняя при этом простую конфигурацию и полностью поддерживающую angular-cli. Но, как уже упоминалось, это компромисс с его ограничениями.