Импорт Worker через new Worker(new URL(...))

В современных сборщиках модулей создание 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 решает несколько задач:

1. Явная привязка к текущему модулю

import.meta.url представляет абсолютный URL текущего модуля. Это позволяет интерпретировать относительный путь однозначно:

new URL('./worker.js', import.meta.url)

Фактически создаётся абсолютный адрес ресурса, который Parcel может анализировать на этапе сборки.

2. Участие воркера в графе зависимостей

Parcel включает файл воркера в dependency graph. Это означает:

  • воркер обрабатывается трансформерами
  • применяется транспиляция (Babel/TypeScript)
  • выполняется tree-shaking
  • возможна оптимизация чанков

3. Корректная работа в продакшн-сборке

В итоговой сборке путь к воркеру может быть изменён (hashing, CDN, code splitting). new URL позволяет Parcel подставить корректный итоговый URL автоматически.

Поведение Parcel при обработке Web Worker

Parcel рассматривает вызов new Worker(new URL(...)) как сигнал о необходимости создать отдельный bundle.

Разделение на отдельный чанк

При сборке формируются два независимых результата:

  • основной бандл приложения
  • отдельный бандл воркера

Воркер не попадает внутрь основного файла, а выносится в отдельный JS-артефакт.

Изоляция контекста

Parcel сохраняет изоляцию:

  • нет общего scope между main thread и worker
  • отсутствует доступ к DOM в воркере
  • коммуникация только через postMessage

Оптимизация кода воркера

Код воркера проходит те же этапы трансформации:

  • транспиляция современного JS
  • поддержка TypeScript
  • минификация
  • удаление неиспользуемых модулей

ES Module воркеры

При использовании:

{ type: 'module' }

воркер становится ES Module контекстом.

Особенности module worker:

  • поддержка import и export
  • возможность импортировать зависимости
  • асинхронная загрузка модулей
  • строгий режим выполнения по умолчанию

Пример воркера:

// worker.js
import { heavyCalculation } from './math.js';

self.onmess age = (event) => {
  const result = heavyCalculation(event.data);
  self.postMessage(result);
};

Parcel анализирует импорт ./math.js и включает его в граф зависимостей воркера.

Отличие от классического Worker-создания

Старый подход:

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

При использовании 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 обрабатывает такие зависимости отдельно:

  • изображения проходят оптимизацию
  • JSON инлайнится или выносится
  • ассеты получают хешированные имена

Это делает воркер полноценной частью asset pipeline.

Поведение в dev-режиме

В режиме разработки Parcel обеспечивает:

  • быстрый пересбор воркера при изменениях
  • горячую перезагрузку (HMR) в ограниченном виде
  • отдельный процесс обновления worker bundle

Воркеры не всегда поддерживают полноценный 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' }
);

Каждый воркер становится отдельным чанком со своей зависимостью.

Оптимизация и code splitting

Parcel рассматривает воркер как точку разделения кода:

  • общий код может быть вынесен в shared chunks
  • дублирование модулей минимизируется
  • выполняется агрессивный tree-shaking

Если основной код и воркер используют один модуль, Parcel может:

  • выделить общий chunk
  • переиспользовать его в обоих бандлах
  • уменьшить общий размер сборки

Особенности работы с assets внутри воркера

При импорте ресурсов внутри воркера Parcel ведёт себя аналогично основному бандлу:

import workerData from './config.json';
import url from './asset.png';

Результаты:

  • JSON превращается в JS объект или файл зависимости
  • изображения оптимизируются и получают URL
  • пути корректно резолвятся относительно worker bundle

Безопасность и изоляция исполнения

Web Worker в Parcel сохраняет браузерную модель безопасности:

  • нет доступа к window
  • нет доступа к DOM
  • нет синхронных блокировок UI
  • отдельный event loop

Parcel не изменяет этот контракт, но обеспечивает корректную упаковку кода, чтобы исключить утечки зависимостей из main thread.

Типичные ошибки при использовании

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

new Worker(new URL(workerPath, import.meta.url));

Ошибка связана с невозможностью статического анализа.

Отсутствие type: module

new Worker(new URL('./worker.js', import.meta.url));

Без указания типа поведение зависит от окружения, что может привести к несовместимости при сборке.

Импорт Node-специфичных модулей

В воркере нельзя использовать API Node.js без соответствующей конфигурации, так как воркер выполняется в браузерном контексте.

Архитектурная роль конструкции new Worker(new URL(...))

Эта форма создания воркера является точкой интеграции трёх систем:

  • браузерного API Web Worker
  • ES Module спецификации (import.meta.url)
  • системы сборки Parcel

Конструкция выступает контрактом, который одновременно:

  • сообщает браузеру, что требуется отдельный поток
  • сообщает Parcel, что требуется отдельный bundle
  • гарантирует корректную адресацию ресурса в любой среде выполнения