npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@budarin/pluggable-serviceworker

v1.2.1

Published

Extensible via plugins service worker

Downloads

382

Readme

@budarin/pluggable-serviceworker

🔌 Расширяемый через плагины Service Worker

Библиотека для создания модульных и расширяемых Service Worker'ов с помощью системы плагинов.

🚀 Почему этот пакет облегчает разработку?

Разработка Service Worker'ов традиционно сложна из-за необходимости вручную управлять множественными обработчиками событий, обработкой ошибок и порядком выполнения. Этот пакет решает эти проблемы:

🔌 Модульная архитектура

  • Плагинная система позволяет разбивать функциональность на независимые модули
  • Каждый плагин отвечает за свою задачу (кеширование, аутентификация, уведомления)
  • Легко добавлять/удалять функциональность без изменения основного кода
  • Не нужно думать об инфраструктурном коде в обработчиках событий - пишите простой код не думая о сложностях кода самого сервисворкера

🎯 Управление порядком выполнения

  • Предсказуемый порядок - плагины без order выполняются первыми, затем по возрастанию order
  • Гибкость - можно контролировать последовательность инициализации
  • Масштабируемость - легко добавлять новые плагины в нужном месте

Оптимизированная логика выполнения

  • Параллельно для install, activate, message, sync - независимые задачи выполняются одновременно
  • Последовательно для fetch, push - первый успешный результат прерывает цепочку
  • Производительность - правильный выбор стратегии для каждого типа события

🛡️ Централизованная обработка ошибок

  • Единый обработчик для всех типов ошибок
  • Типизированные ошибки - знаешь, что именно сломалось
  • Изоляция - ошибка в одном плагине не ломает остальные
  • Автоматическая обработка глобальных событий ошибок

📝 Удобное логирование

  • Настраиваемый логгер с разными уровнями
  • Контекстная информация в логах
  • Отладка становится намного проще

📦 Установка

npm install @budarin/pluggable-serviceworker

или

pnpm add @budarin/pluggable-serviceworker

🚀 Быстрый старт

Базовое использование

// sw.js
import {
    initServiceWorker,
    type ServiceWorkerPlugin,
    type SwContext,
} from '@budarin/pluggable-serviceworker';

// Контекст: список ассетов для precache и имя кеша
interface PrecacheAndServeContext extends SwContext {
    assets: string[];
    cacheName: string;
}

const precacheAndServePlugin: ServiceWorkerPlugin<PrecacheAndServeContext> = {
    name: 'precache-and-serve',

    install: async (_event, context) => {
        const cache = await caches.open(context.cacheName);
        await cache.addAll(context.assets);
    },

    fetch: async (event, context) => {
        const cache = await caches.open(context.cacheName);
        return cache.match(event.request) ?? undefined;
    },
};

// TypeScript проверит, что в options есть assets и cacheName
initServiceWorker([precacheAndServePlugin], {
    logger: console,
    assets: ['/', '/styles.css', '/script.js'],
    cacheName: 'my-cache-v1',
});

Важно: для fetch плагину не нужно самому вызывать fetch(event.request), если все плагины вернули undefined - фреймворк сам выполняет запрос в сеть. Во все обработчики плагинов вторым аргументом передаётся контекст — те же данные, что вы передали в initServiceWorker.

Демо

В папке demo/ — приложение React + Vite с пресетом offlineFirst и типовым сервис-воркером activateOnSignal. Запуск из корня: pnpm install && pnpm build, затем cd demo && pnpm install && pnpm run dev. Подробности и ссылки на публичные песочницы (StackBlitz, CodeSandbox) — в demo/README.md.

Open in StackBlitz · Open in CodeSandbox

initServiceWorker(plugins, options)

initServiceWorker — точка входа: она регистрирует обработчики событий Service Worker (install, activate, fetch, …) и прогоняет их через список плагинов.

  • plugins: массив плагинов
  • options: один общий объект с настройками/данными, который будет доступен плагинам как контекст

Пример:

initServiceWorker([myPlugin], {
    logger: console,
    // ... тут будут поля контекста, которые требуют плагины ...
});

⚙️ Конфигурация и контекст (options)

Функция initServiceWorker принимает второй параметр options типа ServiceWorkerInitOptions (контекст для плагинов + onError для библиотеки). В обработчики плагинов вторым аргументом передаётся контекст — часть этого объекта без onError (тип контекста — SwContext и ваши поля; при типизированных плагинах — пересечение требуемых ими полей).

interface SwContext {
    logger?: Logger; // по умолчанию console
    // сюда можно добавлять свои поля: version, assets, cacheName и т.д.
}

// В initServiceWorker передаётся ServiceWorkerInitOptions = SwContext + onError:
interface ServiceWorkerInitOptions extends SwContext {
    onError?: (error, event, errorType?) => void; // только для библиотеки, в плагины не передаётся
}

В тип контекста, который видят плагины, входит только SwContext и ваши поля; onError в этот тип не входит и используется только библиотекой.

Формируйте объект options в своём сервис-воркере (контекст для плагинов + при необходимости onError) и передавайте его в initServiceWorker. В плагины передаётся тот же объект как контекст — плагины получают доступ к полям контекста, а onError остаётся внутренним делом библиотеки.

Поля конфигурации

logger?: Logger (опциональное)

Объект для логирования с методами info, warn, error, debug. По умолчанию используется console. Может быть передан любой объект, реализующий интерфейс Logger.

interface Logger {
    trace: (...data: unknown[]) => void;
    debug: (...data: unknown[]) => void;
    info: (...data: unknown[]) => void;
    warn: (...data: unknown[]) => void;
    error: (...data: unknown[]) => void;
}

Пример:

const logger = console; // Использование стандартного console

const options = {
    logger,
    // или
    logger: {
        trace: (...data) => customLog('TRACE', ...data),
        debug: (...data) => customLog('DEBUG', ...data),
        info: (...data) => customLog('INFO', ...data),
        warn: (...data) => customLog('WARN', ...data),
        error: (...data) => customLog('ERROR', ...data),
    },
};

onError?: (error, event, errorType) => void (опциональное)

Единый обработчик для всех типов ошибок в Service Worker. Дефолтного обработчика ошибок нет - если onError не передан, ошибки будут проигнорированы (не обработаны).

Параметры:

  • error: Error | any - объект ошибки
  • event: Event - событие, в контексте которого произошла ошибка
  • errorType?: ServiceWorkerErrorType - тип ошибки (см. раздел "Обработка ошибок")

Важно: Если onError не указан, ошибки в плагинах и глобальные ошибки будут проигнорированы. Для production-окружения рекомендуется всегда указывать onError для логирования и мониторинга ошибок.

Пример минимальной конфигурации:

// Без onError - ошибки будут проигнорированы
initServiceWorker([cachePlugin], {});

// С onError - ошибки будут обработаны
initServiceWorker([cachePlugin], {
    logger: console,
    onError: (error, event, errorType) => {
        console.error('Service Worker error:', error, errorType);
    },
});

Обработка ошибок

Библиотека позволяет описать единый обработчик для всех типов ошибок в Service Worker и выполнить обработку индивидуально каждого типа ошибки. Она сама подписывается на глобальные события error, messageerror, unhandledrejection, rejectionhandled; ошибка в одном плагине не останавливает выполнение остальных. Если внутри onError произойдёт исключение, оно логируется через options.logger.

import {
    initServiceWorker,
    ServiceWorkerErrorType,
} from '@budarin/pluggable-serviceworker';

const logger = console; // или свой объект с методами info, warn, error, debug

const options = {
    logger,
    onError: (error, event, errorType) => {
        logger.info(`Ошибка типа "${errorType}":`, error);

        switch (errorType) {
            case ServiceWorkerErrorType.ERROR:
                // JavaScript ошибки
                logger.error('JavaScript error:', error);
                break;

            case ServiceWorkerErrorType.MESSAGE_ERROR:
                // Ошибки сообщений
                logger.error('Message error:', error);
                break;

            case ServiceWorkerErrorType.UNHANDLED_REJECTION:
                // Необработанные Promise rejection
                logger.error('Unhandled promise rejection:', error);
                break;

            case ServiceWorkerErrorType.REJECTION_HANDLED:
                // Обработанные Promise rejection
                logger.info('Promise rejection handled:', error);
                break;

            case ServiceWorkerErrorType.PLUGIN_ERROR:
                // Ошибки в плагинах
                logger.error('Plugin error:', error);
                break;

            default:
                // Неизвестные типы ошибок
                logger.error('Unknown error type:', error);

                // можно даже так - отправка ошибки в аналитику
                fetch('/api/errors', {
                    method: 'POST',
                    body: JSON.stringify({
                        error: error.message,
                        eventType: event.type,
                        url: event.request?.url,
                        timestamp: Date.now(),
                    }),
                }).catch(() => {
                    // Игнорируем ошибки отправки логов
                });
        }
    },
};

initServiceWorker(
    [
        /* ваши плагины */
    ],
    options
);

🔌 Интерфейс плагина

Каждый плагин реализует интерфейс ServiceWorkerPlugin<C>, где C extends SwContext — тип контекста, который плагин ожидает. Во все обработчики вторым аргументом передаётся контекст (те же данные, что в options при инициализации, без onError).

interface ServiceWorkerPlugin<C extends SwContext = SwContext> {
    name: string;
    order?: number;

    install?: (event: ExtendableEvent, context?: C) => Promise<void> | void;
    activate?: (event: ExtendableEvent, context?: C) => Promise<void> | void;
    fetch?: (
        event: FetchEvent,
        context?: C
    ) => Promise<Response | undefined> | Response | undefined;
    message?: (event: SwMessageEvent, context?: C) => void;
    sync?: (event: SyncEvent, context?: C) => Promise<void> | void;
    push?: (
        event: PushEvent,
        context?: C
    ) =>
        | Promise<PushNotificationPayload | void>
        | PushNotificationPayload
        | void;
    periodicsync?: (
        event: PeriodicSyncEvent,
        context?: C
    ) => Promise<void> | void;
}

Плагин может объявить требуемый контекст через дженерик: ServiceWorkerPlugin<SwContext & { assets: string[]; cacheName: string }>. Тогда TypeScript потребует передать в initServiceWorker объект options с полями assets и cacheName (при вызове с литералом массива плагинов тип options выводится автоматически).

📝 Описание методов

| Метод | Событие | Возвращает | Описание | | -------------- | -------------- | --------------------------------- | -------------------------------------------------------------- | | install | install | void | Инициализация плагина при установке SW | | activate | activate | void | Активация плагина при обновлении SW | | fetch | fetch | Response \| undefined | Обработка сетевых запросов | | message | message | void | Обработка сообщений от основного потока | | sync | sync | void | Синхронизация данных в фоне | | push | push | PushNotificationPayload \| void | Данные для уведомления; библиотека вызывает showNotification | | periodicsync | periodicsync | void | Периодические фоновые задачи |

🎯 Особенности обработчиков

  • Во все методы вторым аргументом передаётся контекст (данные из объекта, переданного в initServiceWorker, без onError). Параметр можно не использовать, если плагину контекст не нужен.
  • fetch: Возвращает Response для завершения цепочки или undefined для передачи следующему плагину. Если все плагины вернули undefined, фреймворк вызывает fetch(event.request).
  • push: Как и fetch — возвращает PushNotificationPayload (объект для Notification API) или undefined. Тип экспортируется из пакета. Первый плагин, вернувший объект с title, «выигрывает»: библиотека вызывает self.registration.showNotification(title, options). Если все вернули undefined, уведомление не показывается.
  • Остальные обработчики (install, activate, message, sync, periodicsync): возвращаемое значение не используется; фреймворк вызывает метод каждого плагина по очереди, цепочка не прерывается.
  • Все обработчики опциональны — реализуйте только нужные события.

🔄 Обновление Service Worker (skipWaiting / clients.claim)

Библиотека не вызывает skipWaiting() и clients.claim() — это поведение задаётся индивидуально в каждом проекте и оставлено на усмотрение плагинов. При необходимости вызывайте их в своих обработчиках install и activate (в библиотеке реализованы данные примитивы - смотри ниже):

const updatePlugin = {
    name: 'update-plugin',
    install: (event) => {
        self.skipWaiting();
    },
    activate: (event) => {
        event.waitUntil(self.clients.claim());
    },
};

🎯 Порядок выполнения

Плагины выполняются в следующем порядке:

  1. Сначала ВСЕ плагины без order - в том порядке, в котором они были добавлены
  2. Затем плагины с order - в порядке возрастания значений order

Пример:

const plugins = [
    { name: 'first' }, // без order - выполняется первым
    { name: 'fourth', order: 2 },
    { name: 'second' }, // без order - выполняется вторым
    { name: 'third', order: 1 },
    { name: 'fifth' }, // без order - выполняется третьим
];

// Порядок выполнения: first → second → fifth → third → fourth

Преимущества новой системы:

  • 🎯 Предсказуемость - плагины без order всегда выполняются первыми
  • 🔧 Простота - не нужно знать, какие номера уже заняты
  • 📈 Масштабируемость - легко добавлять новые плагины в нужном порядке

⚡ Логика выполнения обработчиков

Разные типы событий Service Worker обрабатываются по-разному в зависимости от их специфики:

🔄 Параллельное выполнение

События: install, activate, message, sync, periodicsync

Все обработчики выполняются одновременно с помощью Promise.all():

// Все плагины инициализируются параллельно
const installPlugin1 = {
    name: 'cache-assets',
    install: async () => {
        /* кеширование ресурсов приложения*/
    },
};
const installPlugin2 = {
    name: 'cache-ext',
    install: async () => {
        /* кэширование вспомогательных ресурсов */
    },
};

// Оба install обработчика выполнятся одновременно

Почему параллельно:

  • install/activate: Все плагины должны инициализироваться независимо
  • message: Все плагины должны получить сообщение одновременно
  • sync: Разные задачи синхронизации независимы (синхронизация данных + кеша)
  • periodicsync: Периодические задачи независимы друг от друга

➡️ Последовательное выполнение

События: fetch, push

Обработчики выполняются по очереди до первого успешного результата:

Fetch - с прерыванием цепочки

const authPlugin = {
    name: 'auth',
    // Без order - выполняется первым
    fetch: async (event) => {
        if (needsAuth(event.request)) {
            return new Response('Unauthorized', { status: 401 }); // Прерывает цепочку
        }
        return undefined; // Передает следующему плагину
    },
};

Почему последовательно:

  • fetch: Нужен только один ответ, первый успешный прерывает цепочку. Если никто не вернул ответ — выполняется fetch(event.request)
  • push: Плагин может вернуть данные для уведомления типа PushNotificationPayload | undefined. Библиотека один раз вызывает showNotification по первому вернувшемуся payload; цепочка прерывается — одно уведомление, без конфликтов.

📋 Сводная таблица

| Событие | Выполнение | Прерывание | Причина | | -------------- | --------------- | ---------- | ------------------------------------------------------ | | install | Параллельно | Нет | Независимая инициализация | | activate | Параллельно | Нет | Независимая активация | | fetch | Последовательно | Да | Нужен один ответ | | message | Параллельно | Нет | Все получают сообщение | | sync | Параллельно | Нет | Независимые задачи | | periodicsync | Параллельно | Нет | Независимые периодические задачи | | push | Последовательно | Да | Один показ уведомления (библиотека по первому payload) |

Примитивы, пресеты и типовые сервис-воркеры

Примитивы (плагины)

Один примитив — одна операция. Импорт: @budarin/pluggable-serviceworker/plugins.

| Название | Событие | Описание | | ------------------------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | precache | install | Кеширует список context.assets в кеш context.cacheName. | | skipWaiting | install | Вызывает skipWaiting(). | | claim | activate | Вызывает clients.claim(). | | claimOnMessage | message | При сообщении с event.data.type === context.claimMessageType (по умолчанию 'SW_ACTIVATE') вызывает skipWaiting(). clients.claim() вызывается плагином claim в activate. | | serveFromCache | fetch | Отдаёт из кеша; при промахе — undefined. | | restoreAssetToCache | fetch | Для URL из context.assets: сначала из кеша; если в кеше нет — запрос с сервера, в кеш, ответ браузеру. | | cacheFirst | fetch | Кеш → при промахе сеть, ответ в кеш. | | networkFirst | fetch | Сеть → при ошибке/офлайне из кеша. | | staleWhileRevalidate | fetch | Отдаёт из кеша, в фоне обновляет кеш из сети. |

Контекст для кеширующих примитивов: OfflineFirstContext (assets, cacheName, опционально claimMessageType). Импортируйте тип из основного пакета.

Пресеты

Комбинации примитивов (стратегии кеширования). Импорт: @budarin/pluggable-serviceworker/presets.

| Название | Состав | Назначение | | ---------------- | ------------------------- | ------------------------------------ | | offlineFirst | precache + serveFromCache | Статика из кеша, при промахе — сеть. |

Стратегии networkFirst, staleWhileRevalidate и др. доступны как примитивы — собирайте свой кастомный сервис-воркер из примитивов и пресетов.

Типовые сервис-воркеры (из коробки)

Готовые точки входа по моменту активации (все с кешированием offline-first). Импорт: @budarin/pluggable-serviceworker/sw.

| Название | Описание | | ------------------------------------ | --------------------------------------------------------------------------------------------------- | | activateOnNextVisitServiceWorker | Кеширующий SW, активируется при следующем визите страницы. | | activateImmediatelyServiceWorker | Кеширующий SW, активируется сразу (skipWaiting + claim). | | activateOnSignalServiceWorker | Кеширующий SW, активируется по сигналу со страницы (сообщение с типом из options.claimMessageType). |

Пример использования типового SW:

// sw.js — точка входа вашего сервис-воркера
import { activateOnNextVisitServiceWorker } from '@budarin/pluggable-serviceworker/sw';

activateOnNextVisitServiceWorker({
    assets: ['/', '/styles.css', '/script.js'],
    cacheName: 'my-cache-v1',
    logger: console,
    onError: (err, event, type) => console.error(type, err),
});

На странице регистрируйте этот файл: navigator.serviceWorker.register('/sw.js') (или путь, по которому сборка отдаёт ваш sw.js).

Режим разработки

Если нужно, чтобы в режиме разработки ни один из плагинов ничего не кэшировал и не пытался что-либо отдавать из кэша - в плагине и сервисворкере можно использовть import.meta.env.DEV для Vite для условного использования кэширования.

📄 Лицензия

MIT © Vadim Budarin