В современных сборщиках модулей создание Web Worker перестало быть
внешней процедурой с отдельными путями и ручным подключением файлов.
Вместо этого используется модель, в которой воркер становится частью
графа зависимостей, а его модуль обрабатывается тем же пайплайном, что и
основной код. В Parcel эта модель реализуется через конструкцию
new Worker(new URL('./worker.js', import.meta.url)),
позволяющую корректно резолвить, бандлить и оптимизировать код
воркера.
Ключевая конструкция выглядит следующим образом:
const worker = new Worker(
new URL('./worker.js', import.meta.url),
{ type: 'module' }
);
Составляющие этой записи имеют принципиальное значение:
new URL('./worker.js', import.meta.url) формирует
абсолютный URL на модуль воркера, исходя из текущего ES-модуляnew Worker(...) создаёт поток выполнения в отдельном
контексте{ type: 'module' } указывает, что воркер интерпретирует
код как ES ModuleПодход заменяет устаревшие схемы с передачей строковых путей, которые плохо интегрируются с бандлерами и не участвуют в оптимизации зависимостей.
new URL(..., import.meta.url)Parcel и аналогичные сборщики работают с модульным графом, где важно статически анализировать зависимости. Конструкция:
new Worker('./worker.js')
не даёт достаточно информации для корректного разрешения модуля на этапе сборки.
Использование new URL решает несколько задач:
import.meta.url представляет абсолютный URL текущего
модуля. Это позволяет интерпретировать относительный путь
однозначно:
new URL('./worker.js', import.meta.url)
Фактически создаётся абсолютный адрес ресурса, который Parcel может анализировать на этапе сборки.
Parcel включает файл воркера в dependency graph. Это означает:
В итоговой сборке путь к воркеру может быть изменён (hashing, CDN,
code splitting). new URL позволяет Parcel подставить
корректный итоговый URL автоматически.
Parcel рассматривает вызов new Worker(new URL(...)) как
сигнал о необходимости создать отдельный bundle.
При сборке формируются два независимых результата:
Воркер не попадает внутрь основного файла, а выносится в отдельный JS-артефакт.
Parcel сохраняет изоляцию:
postMessageКод воркера проходит те же этапы трансформации:
При использовании:
{ type: 'module' }
воркер становится ES Module контекстом.
import и exportПример воркера:
// worker.js
import { heavyCalculation } from './math.js';
self.onmess age = (event) => {
const result = heavyCalculation(event.data);
self.postMessage(result);
};
Parcel анализирует импорт ./math.js и включает его в
граф зависимостей воркера.
Старый подход:
const worker = new Worker('worker.js');
Проблемы:
Современный подход:
new Worker(new URL('./worker.js', import.meta.url))
Преимущества:
Parcel должен понимать путь к воркеру на этапе сборки. Поэтому допустимы только статические выражения:
new Worker(new URL('./worker.js', import.meta.url));
new Worker(new URL(filePath, import.meta.url));
const path = './worker.js';
new Worker(new URL(path, import.meta.url));
Динамические значения не могут быть включены в dependency graph, что нарушает модель сборки.
При использовании TypeScript воркер может быть типизирован:
self.onmess age = (event: MessageEvent<number>) => {
self.postMessage(event.data * 2);
};
Основной файл:
const worker = new Worker(
new URL('./worker.ts', import.meta.url),
{ type: 'module' }
);
Parcel автоматически транспилирует .ts файл воркера,
включая его в сборку.
Обмен сообщениями осуществляется через postMessage:
// main thread
worker.postMessage({ value: 42 });
worker.onmess age = (event) => {
console.log(event.data);
};
// worker.js
self.onmess age = (event) => {
const value = event.data.value;
self.postMessage(value * 2);
};
Parcel не вмешивается в этот механизм, но гарантирует корректное разделение кода.
В модульных воркерах можно использовать импорты:
import image from './image.png';
import data from './data.json';
Parcel обрабатывает такие зависимости отдельно:
Это делает воркер полноценной частью asset pipeline.
В режиме разработки Parcel обеспечивает:
Воркеры не всегда поддерживают полноценный HMR, поэтому часто происходит пересоздание экземпляра воркера при изменении кода.
Parcel поддерживает создание нескольких воркеров:
const parserWorker = new Worker(
new URL('./parser.js', import.meta.url),
{ type: 'module' }
);
const calcWorker = new Worker(
new URL('./calc.js', import.meta.url),
{ type: 'module' }
);
Каждый воркер становится отдельным чанком со своей зависимостью.
Parcel рассматривает воркер как точку разделения кода:
Если основной код и воркер используют один модуль, Parcel может:
При импорте ресурсов внутри воркера Parcel ведёт себя аналогично основному бандлу:
import workerData from './config.json';
import url from './asset.png';
Результаты:
Web Worker в Parcel сохраняет браузерную модель безопасности:
windowParcel не изменяет этот контракт, но обеспечивает корректную упаковку кода, чтобы исключить утечки зависимостей из main thread.
new Worker(new URL(workerPath, import.meta.url));
Ошибка связана с невозможностью статического анализа.
type: modulenew Worker(new URL('./worker.js', import.meta.url));
Без указания типа поведение зависит от окружения, что может привести к несовместимости при сборке.
В воркере нельзя использовать API Node.js без соответствующей конфигурации, так как воркер выполняется в браузерном контексте.
new Worker(new URL(...))Эта форма создания воркера является точкой интеграции трёх систем:
import.meta.url)Конструкция выступает контрактом, который одновременно: