@budarin/pluggable-serviceworker 1.5.0 → 1.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +67 -39
- package/dist/plugins/claimAndReloadClients.js +5 -5
- package/dist/plugins/skipWaitingOnMessage.js +2 -1
- package/dist/sw/activateImmediately.d.ts +1 -1
- package/dist/sw/activateImmediately.js +2 -2
- package/dist/sw/activateOnNextVisit.d.ts +1 -1
- package/dist/sw/activateOnNextVisit.js +1 -1
- package/dist/utils/openCacheAndMatch.d.ts +4 -0
- package/dist/utils/openCacheAndMatch.js +5 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,39 +6,59 @@
|
|
|
6
6
|
|
|
7
7
|
## 🚀 Почему этот пакет облегчает разработку?
|
|
8
8
|
|
|
9
|
-
Разработка Service Worker'ов традиционно сложна из-за необходимости вручную управлять множественными обработчиками событий, обработкой ошибок и порядком
|
|
9
|
+
Разработка Service Worker'ов традиционно сложна из-за необходимости вручную управлять множественными обработчиками событий, обработкой ошибок и порядком выполнения или изучать сложные и большие библиотеки. Этот пакет решает эти проблемы:
|
|
10
10
|
|
|
11
11
|
### 🔌 **Модульная архитектура**
|
|
12
12
|
|
|
13
13
|
- **Плагинная система** позволяет разбивать функциональность на независимые модули
|
|
14
14
|
- Каждый плагин отвечает за свою задачу (кеширование, аутентификация, уведомления)
|
|
15
15
|
- Легко добавлять/удалять функциональность без изменения основного кода
|
|
16
|
-
- Не нужно думать об инфраструктурном коде в обработчиках событий
|
|
16
|
+
- Не нужно думать об инфраструктурном коде в обработчиках событий — пишите простой код, не думая о сложностях самого сервис-воркера
|
|
17
17
|
|
|
18
|
-
### 🎯
|
|
18
|
+
### 🎯 **Предсказуемый порядок выполнения**
|
|
19
19
|
|
|
20
|
-
- **Предсказуемый порядок**
|
|
21
|
-
- **Гибкость**
|
|
22
|
-
-
|
|
20
|
+
- **Предсказуемый порядок** — плагины без `order` выполняются первыми, затем по возрастанию/убыванию `order`
|
|
21
|
+
- **Гибкость** — можно контролировать последовательность инициализации
|
|
22
|
+
- **Параллельно** для `install`, `activate`, `message`, `sync` — независимые задачи выполняются одновременно
|
|
23
|
+
- **Последовательно** для `fetch` — первый вернувший ответ завершает цепочку; для `push` вызываются все плагины
|
|
24
|
+
- Легко добавлять новые плагины в нужное место
|
|
23
25
|
|
|
24
|
-
###
|
|
26
|
+
### 📖 **Легко изучить и понять**
|
|
25
27
|
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
28
|
+
- Один контракт — плагин с опциональными хуками, без отдельной модели роутинга и стратегий
|
|
29
|
+
- Мало сущностей: плагин, фабрика плагина, `initServiceWorker`, опции
|
|
30
|
+
- Низкая когнитивная нагрузка: цепочка плагинов и возвращаемые значения задают всё поведение
|
|
31
|
+
- Быстро войти в проект по примерам и типу `ServiceWorkerPlugin`
|
|
32
|
+
|
|
33
|
+
### 📦 **Маленький размер**
|
|
34
|
+
|
|
35
|
+
- Минимум зависимостей, только рантайм плагинов
|
|
36
|
+
- Нет встроенной сборки и тяжёлых модулей — в бандл попадает только то, что вы подключаете
|
|
37
|
+
- Подходит для проектов, где важен размер бандла и простота зависимостей
|
|
38
|
+
|
|
39
|
+
### 🎛 **Полный контроль над кодом**
|
|
40
|
+
|
|
41
|
+
- Список кешируемых ассетов и стратегии задаёте вы — в коде или конфиге
|
|
42
|
+
- Порядок плагинов, обработка ошибок и логгер полностью в ваших руках
|
|
43
|
+
- Любая кастомная логика в `fetch`, `push`, `sync` — через свои плагины, без обхода чужих абстракций
|
|
29
44
|
|
|
30
45
|
### 🛡️ **Централизованная обработка ошибок**
|
|
31
46
|
|
|
32
|
-
- **Единый обработчик** для всех типов ошибок
|
|
33
|
-
- **Типизированные ошибки**
|
|
34
|
-
- **Изоляция**
|
|
47
|
+
- **Единый обработчик** `onError` для всех типов ошибок в сервис-воркере с определение места возникновения ошибки в коде
|
|
48
|
+
- **Типизированные ошибки** — знаешь, что именно сломалось
|
|
49
|
+
- **Изоляция** — ошибка в одном плагине не ломает остальные
|
|
35
50
|
- **Автоматическая обработка** глобальных событий ошибок
|
|
36
51
|
|
|
37
52
|
### 📝 **Удобное логирование**
|
|
38
53
|
|
|
39
|
-
- **Настраиваемый логгер** с разными уровнями
|
|
40
|
-
-
|
|
41
|
-
-
|
|
54
|
+
- **Настраиваемый логгер** с разными уровнями (`trace`, `debug`, `info`, `warn`, `error`)
|
|
55
|
+
- Во все обработчики плагинов передаётся один и тот же `logger` — контекстная отладка
|
|
56
|
+
- При желании можно подставить любой объект с этим интерфейсом
|
|
57
|
+
|
|
58
|
+
### ✅ **Готовые решения из коробки**
|
|
59
|
+
|
|
60
|
+
- Пресет **offlineFirst** — precache при установке и отдача из кеша при запросах
|
|
61
|
+
- Готовые сервисворкеры: **activateOnSignal**, **activateImmediately**, **activateOnNextVisit** — рабочий типовой сервис-воркер в несколько строк без написания плагинов
|
|
42
62
|
|
|
43
63
|
## 📦 Установка
|
|
44
64
|
|
|
@@ -440,6 +460,7 @@ function authPlugin(config: {
|
|
|
440
460
|
if (protectedPaths.some((p) => path.startsWith(p))) {
|
|
441
461
|
if (needsAuth(event.request)) {
|
|
442
462
|
logger.warn('auth: unauthorized', event.request.url);
|
|
463
|
+
|
|
443
464
|
return new Response('Unauthorized', { status: 401 }); // Прерывает цепочку
|
|
444
465
|
}
|
|
445
466
|
}
|
|
@@ -454,7 +475,7 @@ function authPlugin(config: {
|
|
|
454
475
|
**Почему последовательно:**
|
|
455
476
|
|
|
456
477
|
- **fetch**: Нужен только один ответ на текущий запрос браузера, первый успешный прерывает цепочку. Если никто не вернул ответ — выполняется `fetch(event.request)`
|
|
457
|
-
- **push**: Плагин может вернуть `PushNotificationPayload`, `false` (не показывать) или `undefined` (решение отдаётся библиотеке). Библиотека вызывает `showNotification` для каждого payload. Не показываем, если все вернули `false` или смесь без payload. Библиотека показывает
|
|
478
|
+
- **push**: Плагин может вернуть `PushNotificationPayload`, `false` (не показывать) или `undefined` (решение отдаётся библиотеке). Библиотека вызывает `showNotification` для каждого payload. Не показываем, если все вернули `false` или смесь без payload. Библиотека показывает нотификацию и в случае когда **все** плагины вернули `undefined`.
|
|
458
479
|
|
|
459
480
|
### 📋 Сводная таблица
|
|
460
481
|
|
|
@@ -479,19 +500,19 @@ function authPlugin(config: {
|
|
|
479
500
|
| Название | Событие | Описание |
|
|
480
501
|
| ------------------------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------- |
|
|
481
502
|
| `precache*(config)` | `install` | Кеширует список ресурсов из `config.assets` в кеш `config.cacheName`. |
|
|
482
|
-
| `precacheAndNotify(config)` | install
|
|
503
|
+
| `precacheAndNotify(config)` | `install` | Как **precache**, затем отправляет активным клиентам сообщение `{ type: config.messageType }` (по умолчанию `SW_INSTALLED`). |
|
|
483
504
|
| `precacheMissing(config)` | `install` | Добавляет в кеш только те ресурсы из `config.assets`, которых ещё нет в кеше. |
|
|
484
505
|
| `pruneStaleCache(config)` | `activate` | Удаляет из кеша записи, чей URL не входит в `config.assets`. |
|
|
485
|
-
| `skipWaiting
|
|
506
|
+
| `skipWaiting` | `install` | Вызывает `skipWaiting()`. |
|
|
486
507
|
| `claim` | `activate` | Вызывает `clients.claim()`. |
|
|
487
508
|
| `reloadClients` | `activate` | Перезагружает все окна-клиенты через `client.navigate(client.url)`. |
|
|
488
509
|
| `claimAndReloadClients` | `activate` | Композиция **claim** + **reloadClients**: сначала claim, затем перезагрузка (порядок гарантирован — один плагин). |
|
|
489
510
|
| `skipWaitingOnMessage(config?)` | `message` | При сообщении с `event.data.type === 'SW_MSG_SKIP_WAITING'` вызывает `skipWaiting()`. |
|
|
490
511
|
| `serveFromCache(config)` | `fetch` | Отдаёт ресурс из кеша `config.cacheName`; при отсутствии его в кэше — undefined. |
|
|
491
512
|
| `restoreAssetToCache(config)` | `fetch` | Для URL из `config.assets`: отдам ресурс из кеша или запрашиваем по сети, затем в кладем кго в кеш. Иначе — undefined. |
|
|
492
|
-
| cacheFirst(config)`
|
|
513
|
+
| `cacheFirst(config)` | `fetch` | Отдаем ресурс из кэша `config.cacheName`: при отсутствии его в кэше — делаем запрос на сервер и затем кладем ответ в кэш. |
|
|
493
514
|
| `networkFirst(config)` | `fetch` | Делаем запрос на сервер, при успехе — кладем его в кэш. При ошибке — отдаем из кэша. Иначе - `undefined`. |
|
|
494
|
-
| `staleWhileRevalidate(config)` | fetch
|
|
515
|
+
| `staleWhileRevalidate(config)` | `fetch` | Отдаёт из кэша, в фоне обновляет кэш. |
|
|
495
516
|
|
|
496
517
|
#### Композиция примитивов
|
|
497
518
|
|
|
@@ -499,32 +520,29 @@ function authPlugin(config: {
|
|
|
499
520
|
|
|
500
521
|
**Пример: claimAndReloadClients как композиция двух примитивов**
|
|
501
522
|
|
|
502
|
-
Плагин **claimAndReloadClients**
|
|
523
|
+
Плагин **claimAndReloadClients** вызывает существующие примитивы **claim** и **reloadClients** по очереди:
|
|
503
524
|
|
|
504
525
|
```typescript
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
self.clients.claim().then(() =>
|
|
508
|
-
self.clients
|
|
509
|
-
.matchAll({ type: 'window' })
|
|
510
|
-
.then((list) =>
|
|
511
|
-
Promise.all(list.map((client) => client.navigate(client.url)))
|
|
512
|
-
)
|
|
513
|
-
),
|
|
514
|
-
```
|
|
526
|
+
import { claim } from '@budarin/pluggable-serviceworker/plugins';
|
|
527
|
+
import { reloadClients } from '@budarin/pluggable-serviceworker/plugins';
|
|
515
528
|
|
|
516
|
-
|
|
529
|
+
const claimPlugin = claim();
|
|
530
|
+
const reloadPlugin = reloadClients();
|
|
517
531
|
|
|
518
|
-
|
|
519
|
-
|
|
532
|
+
activate: (event, logger) =>
|
|
533
|
+
Promise.resolve(claimPlugin.activate(event, logger)).then(() =>
|
|
534
|
+
reloadPlugin.activate(event, logger)
|
|
535
|
+
),
|
|
520
536
|
```
|
|
521
537
|
|
|
522
538
|
**Пример: кастомный кэш и логика по URL**
|
|
523
539
|
|
|
524
|
-
Фабрика `postsSwrPlugin(config)` возвращает плагин, который применяет stale-while-revalidate только к запросам, подходящим под `pathPattern`.
|
|
540
|
+
Фабрика `postsSwrPlugin(config)` возвращает плагин, который применяет `stale-while-revalidate`(SWR) только к запросам, подходящим под `pathPattern`.
|
|
525
541
|
|
|
526
542
|
```typescript
|
|
543
|
+
// postsSwrPlugin.ts
|
|
527
544
|
import type { Plugin } from '@budarin/pluggable-serviceworker';
|
|
545
|
+
|
|
528
546
|
import {
|
|
529
547
|
precache,
|
|
530
548
|
serveFromCache,
|
|
@@ -536,28 +554,39 @@ function postsSwrPlugin(config: {
|
|
|
536
554
|
pathPattern?: RegExp;
|
|
537
555
|
}): Plugin {
|
|
538
556
|
const { cacheName, pathPattern = /\/api\/posts(\/|$)/ } = config;
|
|
557
|
+
|
|
539
558
|
return {
|
|
540
559
|
name: 'postsSwr',
|
|
541
560
|
order: 0,
|
|
561
|
+
|
|
542
562
|
fetch: async (event) => {
|
|
543
|
-
if (!pathPattern.test(new URL(event.request.url).pathname))
|
|
563
|
+
if (!pathPattern.test(new URL(event.request.url).pathname)) {
|
|
544
564
|
return undefined;
|
|
565
|
+
}
|
|
566
|
+
|
|
545
567
|
const cache = await caches.open(cacheName);
|
|
546
568
|
const cached = await cache.match(event.request);
|
|
547
569
|
const revalidate = fetch(event.request).then(async (response) => {
|
|
548
|
-
if (response.ok)
|
|
570
|
+
if (response.ok) {
|
|
549
571
|
await cache.put(event.request, response.clone());
|
|
572
|
+
}
|
|
573
|
+
|
|
550
574
|
return response;
|
|
551
575
|
});
|
|
576
|
+
|
|
552
577
|
if (cached) {
|
|
553
578
|
void revalidate;
|
|
554
579
|
return cached;
|
|
555
580
|
}
|
|
581
|
+
|
|
556
582
|
return revalidate;
|
|
557
583
|
},
|
|
558
584
|
};
|
|
559
585
|
}
|
|
586
|
+
```
|
|
560
587
|
|
|
588
|
+
```typescript
|
|
589
|
+
// sw.ts
|
|
561
590
|
const staticCache = 'static-v1';
|
|
562
591
|
const assets = ['/', '/main.js'];
|
|
563
592
|
|
|
@@ -580,7 +609,6 @@ initServiceWorker(
|
|
|
580
609
|
| `offlineFirst(config)` | `precache(config) + serveFromCache(config)` | Статика из кеша, при отсутствии ресурса в кэше — делаем запрос к серверу. |
|
|
581
610
|
|
|
582
611
|
Конфиг пресета: `OfflineFirstConfig` (cacheName, assets). Импорт из `@budarin/pluggable-serviceworker/presets`.
|
|
583
|
-
|
|
584
612
|
Стратегии **networkFirst**, **staleWhileRevalidate** и др. доступны как примитивы — собирайте свой кастомный сервис-воркер из примитивов и пресетов.
|
|
585
613
|
|
|
586
614
|
### Типовые сервис-воркеры (из коробки)
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
import { claim } from './claim.js';
|
|
2
|
+
import { reloadClients } from './reloadClients.js';
|
|
1
3
|
export function claimAndReloadClients() {
|
|
4
|
+
const claimPlugin = claim();
|
|
5
|
+
const reloadPlugin = reloadClients();
|
|
2
6
|
return {
|
|
3
7
|
name: 'claimAndReloadClients',
|
|
4
|
-
activate: () =>
|
|
5
|
-
.claim()
|
|
6
|
-
.then(() => self.clients
|
|
7
|
-
.matchAll({ type: 'window' })
|
|
8
|
-
.then((list) => Promise.all(list.map((client) => client.navigate(client.url))))),
|
|
8
|
+
activate: (event, logger) => Promise.resolve(claimPlugin.activate(event, logger)).then(() => reloadPlugin.activate(event, logger)),
|
|
9
9
|
};
|
|
10
10
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ServiceWorkerInitOptions } from '../index.js';
|
|
2
|
-
import {
|
|
2
|
+
import type { OfflineFirstConfig } from '../presets/offlineFirst.js';
|
|
3
3
|
export interface ActivateImmediatelyOptions extends ServiceWorkerInitOptions, OfflineFirstConfig {
|
|
4
4
|
}
|
|
5
5
|
export declare function activateImmediatelyServiceWorker(options: ActivateImmediatelyOptions): void;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { claim } from '../plugins/claim.js';
|
|
1
2
|
import { initServiceWorker } from '../index.js';
|
|
2
|
-
import { offlineFirst, } from '../presets/offlineFirst.js';
|
|
3
3
|
import { skipWaiting } from '../plugins/skipWaiting.js';
|
|
4
|
-
import {
|
|
4
|
+
import { offlineFirst } from '../presets/offlineFirst.js';
|
|
5
5
|
export function activateImmediatelyServiceWorker(options) {
|
|
6
6
|
initServiceWorker([...offlineFirst(options), skipWaiting, claim], options);
|
|
7
7
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ServiceWorkerInitOptions } from '../index.js';
|
|
2
|
-
import {
|
|
2
|
+
import type { OfflineFirstConfig } from '../presets/offlineFirst.js';
|
|
3
3
|
export interface ActivateOnNextVisitOptions extends ServiceWorkerInitOptions, OfflineFirstConfig {
|
|
4
4
|
}
|
|
5
5
|
export declare function activateOnNextVisitServiceWorker(options: ActivateOnNextVisitOptions): void;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { initServiceWorker } from '../index.js';
|
|
2
|
-
import { offlineFirst
|
|
2
|
+
import { offlineFirst } from '../presets/offlineFirst.js';
|
|
3
3
|
export function activateOnNextVisitServiceWorker(options) {
|
|
4
4
|
initServiceWorker(offlineFirst(options), options);
|
|
5
5
|
}
|