Shared Workers

Shared Workers представляют собой тип веб-воркеров, предназначенных для совместного использования одного фонового JavaScript-контекста несколькими вкладками, окнами или iframe, происходящими из одного источника. В отличие от классических Dedicated Worker, которые привязаны к одной странице, Shared Worker функционирует как общий сервисный процесс внутри браузера, принимающий соединения от множества клиентов и обеспечивающий централизованную обработку данных.

В контексте Parcel работа с Shared Workers сводится не только к написанию самого воркера, но и к корректной сборке, разделению бандлов, поддержке модульной структуры и интеграции с системой HMR (Hot Module Replacement). Parcel автоматически определяет воркеры как отдельные точки входа и формирует для них независимые бандлы, сохраняя при этом связь с основным приложением.


Shared Worker существует в единственном экземпляре на уровень origin. При создании нового подключения через new SharedWorker() браузер проверяет, существует ли уже активный воркер с таким же URL. Если существует — добавляется новое соединение, если нет — создаётся новый процесс.

Ключевой механизм взаимодействия строится на портах:

  • каждый клиент получает собственный MessagePort
  • коммуникация осуществляется через postMessage
  • обмен событиями происходит асинхронно

Shared Worker не имеет доступа к DOM, но может использовать:

  • IndexedDB
  • WebSocket
  • Fetch API
  • Web Crypto
  • таймеры

Базовая структура Shared Worker

Файл воркера представляет собой отдельный модуль:

// shared-worker.js

const connections = new Set();

self.onconn ect = (event) => {
  const port = event.ports[0];
  connections.add(port);

  port.start();

  port.onmess age = (e) => {
    const message = e.data;

    for (const client of connections) {
      client.postMessage({
        type: 'broadcast',
        payload: message,
      });
    }
  };

  port.postMessage({
    type: 'init',
    payload: 'Shared Worker подключён',
  });
};

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


Подключение Shared Worker в приложении Parcel

Parcel обрабатывает импорт воркеров как отдельный граф зависимостей. Поддерживается стандартный синтаксис создания воркера:

const worker = new SharedWorker(
  new URL('./shared-worker.js', import.meta.url)
);

worker.port.start();

worker.port.onmess age = (event) => {
  console.log('Сообщение от воркера:', event.data);
};

worker.port.postMessage({
  type: 'ping',
});

Parcel анализирует new URL(..., import.meta.url) и формирует корректный отдельный бандл для воркера, обеспечивая:

  • изоляцию зависимостей
  • отдельный chunk для worker-кода
  • корректное разрешение путей
  • поддержку dev-сервера

Особенности сборки Shared Worker в Parcel

Parcel рассматривает Shared Worker как отдельную точку входа, аналогично Web Worker. При этом сохраняется связь с основным приложением через URL-резолвинг.

Разделение бандлов

При сборке создаются:

  • основной bundle приложения
  • отдельный bundle Shared Worker

Это обеспечивает:

  • параллельную загрузку
  • кэширование воркера браузером
  • повторное использование между вкладками

Работа с модулями внутри Shared Worker

Shared Worker в Parcel поддерживает ES Modules, что позволяет строить сложную архитектуру:

// shared-worker.js
import { formatMessage } from './utils/format.js';

const clients = new Set();

self.onconn ect = (event) => {
  const port = event.ports[0];
  clients.add(port);

  port.start();

  port.onmess age = (e) => {
    const formatted = formatMessage(e.data);

    for (const client of clients) {
      client.postMessage(formatted);
    }
  };
};

Parcel автоматически:

  • объединяет зависимости в worker bundle
  • оптимизирует tree-shaking
  • устраняет неиспользуемый код
  • обрабатывает трансформации через Babel или SWC (в зависимости от конфигурации)

Ограничения Shared Worker в браузере

Shared Worker имеет ряд архитектурных ограничений, которые влияют на проектирование при использовании Parcel:

  • отсутствие DOM API
  • ограниченная поддержка в некоторых браузерах
  • невозможность прямого доступа к window
  • необходимость ручного управления портами
  • отсутствие автоматического завершения процесса

Эти особенности требуют явного контроля жизненного цикла соединений:

self.onconn ect = (event) => {
  const port = event.ports[0];

  port.onmess age = () => {
    // обработка сообщений
  };

  port.start();
};

Управление состоянием внутри Shared Worker

Shared Worker часто используется как централизованное хранилище состояния между вкладками.

Пример синхронизации состояния:

const state = {
  counter: 0,
};

const ports = new Set();

self.onconn ect = (event) => {
  const port = event.ports[0];
  ports.add(port);

  port.start();

  port.onmess age = (e) => {
    if (e.data.type === 'increment') {
      state.counter += 1;

      for (const p of ports) {
        p.postMessage({
          type: 'state',
          payload: state.counter,
        });
      }
    }
  };

  port.postMessage({
    type: 'state',
    payload: state.counter,
  });
};

Parcel не вмешивается в логику состояния, но обеспечивает стабильную упаковку кода и повторное использование воркера между сессиями.


Интеграция с Hot Module Replacement

Parcel поддерживает HMR и для Shared Worker, однако поведение отличается от основного приложения.

При обновлении модуля:

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

Типичный сценарий обработки обновления:

if (module.hot) {
  module.hot.dispose(() => {
    self.close();
  });
}

Shared Worker не перезапускается “мягко” — пересборка приводит к новому экземпляру.


Разделение ответственности между клиентом и Shared Worker

Клиентская часть отвечает за:

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

Shared Worker отвечает за:

  • агрегацию данных
  • синхронизацию между вкладками
  • выполнение фоновых задач

Parcel обеспечивает корректное разделение этих слоёв на уровне сборки.


Производительность и кэширование

Shared Worker обладает особенностью повторного использования между вкладками, что снижает нагрузку на:

  • CPU
  • память
  • сетевые запросы

Parcel дополнительно усиливает эффект за счёт:

  • хэширования файлов воркера
  • долгосрочного кэширования
  • оптимизации чанков

При изменении кода воркера меняется его URL, что приводит к созданию новой версии процесса.


Типизация и использование TypeScript

Parcel поддерживает TypeScript в Shared Worker без дополнительной конфигурации:

interface Message {
  type: string;
  payload: unknown;
}

self.onconn ect = (event: MessageEvent) => {
  const port = event.ports[0];

  port.onmess age = (e: MessageEvent<Message>) => {
    port.postMessage({
      type: 'echo',
      payload: e.data.payload,
    });
  };

  port.start();
};

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


Сравнение Shared Worker и альтернативных подходов

Shared Worker занимает промежуточное положение между Service Worker и Dedicated Worker.

Shared Worker:

  • общий контекст между вкладками
  • прямой обмен сообщениями
  • отсутствие контроля сети

Service Worker:

  • перехват сетевых запросов
  • работа как прокси
  • жизненный цикл вне вкладок

Dedicated Worker:

  • изолированная задача
  • связь только с одной страницей

Parcel поддерживает все три типа, но Shared Worker требует особой модели сборки из-за постоянного соединения между клиентами.


Обработка ошибок и устойчивость

Ошибки в Shared Worker не всегда приводят к завершению процесса, поэтому требуется явная обработка:

self.oner ror = (error) => {
  // логирование или отправка в мониторинг
};

Parcel не перехватывает runtime-ошибки внутри воркера, но обеспечивает корректное обновление бандла при изменении исходного кода.


Практическая модель масштабирования

Shared Worker часто применяется для:

  • синхронизации авторизации между вкладками
  • кеширования данных API
  • координации WebSocket соединений
  • централизованных очередей задач

Parcel в таких сценариях выступает как слой, обеспечивающий:

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