Структурирование проекта TypeScript с рабочими

Как мне структурировать проект, который включает сценарий основного потока (DOM) и работников? Например:

main.ts

// This file must have DOM types, but not worker types.

const worker = new Worker('worker.js');

worker.onmessage = (event) => {
  // Ideally, I should be able to reference types from the worker:
  const data = event.data as import('./worker').HelloMessage;
  console.log(data.hello);
};

worker.ts

// This file must have worker types, but not DOM types.
// The global object must be one of the worker globals (how do I pick which?)

const helloMessage = {
  hello: 'world',
};

export type HelloMessage = typeof helloMessage;

postMessage(helloMessage);

Всякий раз, когда я пробовал это в прошлом, я чувствую, что боролся с TypeScript одним из следующих способов:

  • Использование одного tsconfig.json который имеет рабочий и DOM-типы. Но, конечно, это не точно по типу.
  • Использование нескольких tsconfig.json. Но тогда границы проекта затрудняют ссылки на типы между ними.

Кроме того, как я могу объявить глобальный в работнике? Раньше я пользовался declare var self: DedicatedWorkerGlobalScope, но есть ли способ установить глобальное (а не просто установить self)?

Ответы

Ответ 1

Большое спасибо Маттиасу Буэленсу, который указал мне правильное направление.

Вот рабочий пример.

Структура проекта:

  • dist
  • src
    • generic-tsconfig.json
    • main
      • (машинописные файлы)
      • tsconfig.json
    • dedicated-worker
      • (машинописные файлы)
      • tsconfig.json
    • service-worker
      • (машинописные файлы)
      • tsconfig.json

src/generic-tsconfig.json

Это содержит конфигурацию, общую для каждого проекта:

{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "strict": true,
    "moduleResolution": "node",
    "rootDir": ".",
    "outDir": "../dist",
    "composite": true,
    "declarationMap": true,
    "sourceMap": true
  }
}

Я сознательно избегал вызова этого tsconfig.json, так как это не сам проект. Адаптируйте вышеизложенное к вашим потребностям Вот важные части:

  • outDir - это то место, куда пойдут переданный скрипт, объявления и исходные карты.
  • rootDir - при установке этого параметра в каталог src каждый из подпроектов (main, dedicated-worker, service-worker) будет отображаться в качестве подкаталогов в outDir, в противном случае они будут пытаться использовать один и тот же каталог и перезаписывать друг друга.
  • composite - это необходимо для TypeScript, чтобы сохранить ссылки между проектами.

Не включайте references в этот файл. Они будут проигнорированы по какой-то недокументированной причине (вот где я застрял).

src/main/tsconfig.json

Это конфигурация для проекта 'main thread', например, JavaScript, который будет иметь доступ к документу.

{
  "extends": "../generic-tsconfig.json",
  "compilerOptions": {
    "lib": ["esnext", "dom"],
  },
  "references": [
    {"path": "../dedicated-worker"},
    {"path": "../service-worker"}
  ]
}
  • extends - это указывает на наш общий конфиг выше.
  • compilerOptions.lib - библиотеки, используемые этим проектом. В этом случае JS и DOM.
  • references - так как это основной проект (тот, который мы создаем), он должен ссылаться на все другие подпроекты, чтобы гарантировать, что они также построены.

src/dedicated-worker/tsconfig.json

Это конфигурация для выделенного работника (вид, который вы создаете с помощью new Worker()).

{
  "extends": "../generic-tsconfig.json",
  "compilerOptions": {
    "lib": ["esnext", "webworker"],
  }
}

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

Использование выделенных типов работников

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

postMessage('foo');

Это работает, так как типы "webworker" TypeScript создают глобальные переменные для всех выделенных рабочих глобальных переменных. Тем не мение:

self.postMessage('foo');

… Это терпит неудачу, поскольку TypeScript дает self несуществующему типу что-то вроде абстрактного рабочего глобально.

Чтобы это исправить, включите это в свой источник:

declare var self: DedicatedWorkerGlobalScope;
export {};

Это устанавливает self в правильный тип.

Бит declare var не работает, если файл не является модулем, а фиктивный экспорт заставляет TypeScript рассматривать его как модуль. Это означает, что вы объявляете self в области видимости модуля, которой в данный момент не существует. В противном случае вы пытаетесь объявить его в глобальном, где он уже существует.

src/service-worker/tsconfig.json

То же, что и выше.

{
  "extends": "../generic-tsconfig.json",
  "compilerOptions": {
    "lib": ["esnext", "webworker"],
  }
}

Использование сервисных рабочих типов

Как и выше, типы "webworker" TypeScript создают глобальные переменные для всех выделенных рабочих глобальных переменных. Но это не выделенный работник, поэтому некоторые типы неверны:

postMessage('yo');

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

К сожалению, вы ничего не можете сделать, чтобы исправить реальную глобальность, но вы все еще можете исправить self:

declare var self: ServiceWorkerGlobalScope;
export {};

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

addEventListener('fetch', (event) => {
  // This is a type error, as the global addEventListener
  // doesn't know anything about the 'fetch' event.
  // Therefore it doesn't know about event.request.
  console.log(event.request);
});

self.addEventListener('fetch', (event) => {
  // This works fine.
  console.log(event.request);
});

Те же проблемы и обходные пути существуют для других типов работников, таких как рабочие листы и общие работники.

Строительство

tsc --build src/main

И это оно!