Как мне структурировать проект, который включает сценарий основного потока (DOM) и рабочие потоки? Например:
// 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.info(data.hello);
};
// 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
)?
Большое спасибо Маттиас Бьюленс, который указал мне правильное направление.
Структура проекта такова:
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
Это конфигурация для проекта «основной поток», например, 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');
Это работает, поскольку типы «веб-работников» 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"],
}
}
Как и выше, типы «веб-работников» 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.info(event.request);
});
self.addEventListener('fetch', (event) => {
// This works fine.
console.info(event.request);
});
Те же проблемы и обходные пути существуют для других типов рабочих процессов, таких как рабочие процессы и общие рабочие процессы.
tsc --build src/main
Вот и все!
Отличный ответ +1. К вашему сведению, я создал минимальную настройку с рабочим примером для TS с Web Worker, вот мое решение: github.com/gibbok/typescript-веб-работники Я использую приведение к DedicatedWorkerGlobalScope, чтобы помочь TS получить типы для себя. Я надеюсь, может быть полезным.
@GibboK, этот проект, на который вы ссылаетесь, не решает проблему конфликтов между типами worker и dom, потому что он полностью исключает типы dom, я думаю, что ваш пример может быть слишком минимальным, чтобы быть полезным в реальной ситуации.
Спасибо за подробное объяснение, @JaffaTheCake! Добавьте это в закладки.