@budarin/pluggable-serviceworker 1.2.1 → 1.5.1
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 +347 -159
- package/dist/index.d.ts +16 -23
- package/dist/index.js +61 -17
- package/dist/plugins/cacheFirst.d.ts +5 -3
- package/dist/plugins/cacheFirst.js +22 -19
- package/dist/plugins/claim.d.ts +2 -3
- package/dist/plugins/claim.js +6 -7
- package/dist/plugins/claimAndReloadClients.d.ts +2 -0
- package/dist/plugins/claimAndReloadClients.js +10 -0
- package/dist/plugins/index.d.ts +9 -5
- package/dist/plugins/index.js +9 -5
- package/dist/plugins/networkFirst.d.ts +5 -3
- package/dist/plugins/networkFirst.js +18 -16
- package/dist/plugins/precache.d.ts +6 -3
- package/dist/plugins/precache.js +10 -8
- package/dist/plugins/precacheAndNotify.d.ts +6 -0
- package/dist/plugins/precacheAndNotify.js +13 -0
- package/dist/plugins/precacheMissing.d.ts +6 -0
- package/dist/plugins/precacheMissing.js +16 -0
- package/dist/plugins/pruneStaleCache.d.ts +6 -0
- package/dist/plugins/pruneStaleCache.js +17 -0
- package/dist/plugins/reloadClients.d.ts +2 -0
- package/dist/plugins/reloadClients.js +9 -0
- package/dist/plugins/restoreAssetToCache.d.ts +6 -3
- package/dist/plugins/restoreAssetToCache.js +22 -37
- package/dist/plugins/serveFromCache.d.ts +5 -3
- package/dist/plugins/serveFromCache.js +10 -11
- package/dist/plugins/skipWaiting.d.ts +2 -3
- package/dist/plugins/skipWaiting.js +8 -7
- package/dist/plugins/skipWaitingOnMessage.d.ts +5 -0
- package/dist/plugins/skipWaitingOnMessage.js +13 -0
- package/dist/plugins/staleWhileRevalidate.d.ts +5 -3
- package/dist/plugins/staleWhileRevalidate.js +20 -18
- package/dist/presets/index.d.ts +1 -2
- package/dist/presets/index.js +0 -1
- package/dist/presets/offlineFirst.d.ts +6 -3
- package/dist/presets/offlineFirst.js +3 -5
- package/dist/sw/activateImmediately.d.ts +5 -3
- package/dist/sw/activateImmediately.js +4 -5
- package/dist/sw/activateOnNextVisit.d.ts +5 -3
- package/dist/sw/activateOnNextVisit.js +2 -3
- package/dist/sw/activateOnSignal.d.ts +6 -3
- package/dist/sw/activateOnSignal.js +11 -5
- package/dist/sw/index.d.ts +0 -1
- package/dist/sw/index.js +0 -1
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.js +2 -0
- package/dist/utils/isAssetRequest.d.ts +1 -0
- package/dist/utils/isAssetRequest.js +10 -0
- package/dist/utils/isRequestUrlInAssets.d.ts +1 -0
- package/dist/utils/isRequestUrlInAssets.js +10 -0
- package/dist/utils/normalizeUrl.d.ts +1 -0
- package/dist/utils/normalizeUrl.js +3 -0
- package/dist/utils/notifyClients.d.ts +1 -0
- package/dist/utils/notifyClients.js +4 -0
- package/dist/utils/openCacheAndMatch.d.ts +4 -0
- package/dist/utils/openCacheAndMatch.js +5 -0
- package/package.json +11 -8
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/plugins/cacheFirst.d.ts.map +0 -1
- package/dist/plugins/cacheFirst.js.map +0 -1
- package/dist/plugins/claim.d.ts.map +0 -1
- package/dist/plugins/claim.js.map +0 -1
- package/dist/plugins/claimOnMessage.d.ts +0 -3
- package/dist/plugins/claimOnMessage.d.ts.map +0 -1
- package/dist/plugins/claimOnMessage.js +0 -11
- package/dist/plugins/claimOnMessage.js.map +0 -1
- package/dist/plugins/index.d.ts.map +0 -1
- package/dist/plugins/index.js.map +0 -1
- package/dist/plugins/networkFirst.d.ts.map +0 -1
- package/dist/plugins/networkFirst.js.map +0 -1
- package/dist/plugins/networkOnly.d.ts +0 -3
- package/dist/plugins/networkOnly.d.ts.map +0 -1
- package/dist/plugins/networkOnly.js +0 -5
- package/dist/plugins/networkOnly.js.map +0 -1
- package/dist/plugins/precache.d.ts.map +0 -1
- package/dist/plugins/precache.js.map +0 -1
- package/dist/plugins/restoreAssetToCache.d.ts.map +0 -1
- package/dist/plugins/restoreAssetToCache.js.map +0 -1
- package/dist/plugins/serveFromCache.d.ts.map +0 -1
- package/dist/plugins/serveFromCache.js.map +0 -1
- package/dist/plugins/skipWaiting.d.ts.map +0 -1
- package/dist/plugins/skipWaiting.js.map +0 -1
- package/dist/plugins/staleWhileRevalidate.d.ts.map +0 -1
- package/dist/plugins/staleWhileRevalidate.js.map +0 -1
- package/dist/presets/index.d.ts.map +0 -1
- package/dist/presets/index.js.map +0 -1
- package/dist/presets/offlineFirst.d.ts.map +0 -1
- package/dist/presets/offlineFirst.js.map +0 -1
- package/dist/sw/activateImmediately.d.ts.map +0 -1
- package/dist/sw/activateImmediately.js.map +0 -1
- package/dist/sw/activateOnNextVisit.d.ts.map +0 -1
- package/dist/sw/activateOnNextVisit.js.map +0 -1
- package/dist/sw/activateOnSignal.d.ts.map +0 -1
- package/dist/sw/activateOnSignal.js.map +0 -1
- package/dist/sw/index.d.ts.map +0 -1
- package/dist/sw/index.js.map +0 -1
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
### ⚡ **Оптимизированная логика выполнения**
|
|
25
25
|
|
|
26
26
|
- **Параллельно** для `install`, `activate`, `message`, `sync` - независимые задачи выполняются одновременно
|
|
27
|
-
- **Последовательно** для `fetch
|
|
27
|
+
- **Последовательно** для `fetch` — первый успешный результат прерывает цепочку; для `push` — вызываются все плагины, показывается уведомление по каждому payload (или одно от библиотеки только когда **все** плагины вернули `undefined`)
|
|
28
28
|
- **Производительность** - правильный выбор стратегии для каждого типа события
|
|
29
29
|
|
|
30
30
|
### 🛡️ **Централизованная обработка ошибок**
|
|
@@ -59,84 +59,103 @@ pnpm add @budarin/pluggable-serviceworker
|
|
|
59
59
|
```typescript
|
|
60
60
|
// sw.js
|
|
61
61
|
import {
|
|
62
|
-
|
|
62
|
+
type PluginContext,
|
|
63
63
|
type ServiceWorkerPlugin,
|
|
64
|
-
|
|
64
|
+
initServiceWorker,
|
|
65
65
|
} from '@budarin/pluggable-serviceworker';
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
interface PrecacheAndServeContext extends SwContext {
|
|
69
|
-
assets: string[];
|
|
67
|
+
function precacheAndServePlugin(config: {
|
|
70
68
|
cacheName: string;
|
|
69
|
+
assets: string[];
|
|
70
|
+
}): ServiceWorkerPlugin<PluginContext> {
|
|
71
|
+
const { cacheName, assets } = config;
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
name: 'precache-and-serve',
|
|
75
|
+
|
|
76
|
+
install: async (_event, logger) => {
|
|
77
|
+
if (import.meta.env.DEV) {
|
|
78
|
+
logger.debug('precache-and-serve: cache assets');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const cache = await caches.open(cacheName);
|
|
82
|
+
await cache.addAll(assets);
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
fetch: async (event, logger) => {
|
|
86
|
+
const cache = await caches.open(cacheName);
|
|
87
|
+
const asset = await cache.match(event.request);
|
|
88
|
+
|
|
89
|
+
if (!asset && import.meta.env.DEV) {
|
|
90
|
+
logger.debug(
|
|
91
|
+
`precache-and-serve: asset ${event.request.url} not found in cache!`
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return asset ?? undefined;
|
|
96
|
+
},
|
|
97
|
+
};
|
|
71
98
|
}
|
|
72
99
|
|
|
73
|
-
|
|
74
|
-
|
|
100
|
+
initServiceWorker([
|
|
101
|
+
precacheAndServePlugin({
|
|
102
|
+
cacheName: 'my-cache-v1',
|
|
103
|
+
assets: ['/', '/styles.css', '/script.js'],
|
|
104
|
+
}),
|
|
105
|
+
]);
|
|
106
|
+
```
|
|
75
107
|
|
|
76
|
-
|
|
77
|
-
const cache = await caches.open(context.cacheName);
|
|
78
|
-
await cache.addAll(context.assets);
|
|
79
|
-
},
|
|
108
|
+
**Важно:**
|
|
80
109
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
return cache.match(event.request) ?? undefined;
|
|
84
|
-
},
|
|
85
|
-
};
|
|
110
|
+
- для `fetch` плагину не нужно самому вызывать `fetch(event.request)` — если все плагины вернули `undefined`, фреймворк сам идёт в сеть.
|
|
111
|
+
- конфиг плагина задаётся по месту вызова фабрики; в `options` только `logger?` и `onError?`.
|
|
86
112
|
|
|
87
|
-
|
|
88
|
-
initServiceWorker([precacheAndServePlugin], {
|
|
89
|
-
logger: console,
|
|
90
|
-
assets: ['/', '/styles.css', '/script.js'],
|
|
91
|
-
cacheName: 'my-cache-v1',
|
|
92
|
-
});
|
|
93
|
-
```
|
|
113
|
+
### Фабрика плагинов
|
|
94
114
|
|
|
95
|
-
|
|
115
|
+
**Плагин** — это объект с полем `name` и опциональными обработчиками (`install`, `fetch`, `activate` и т.д.). В массив `initServiceWorker(plugins, options)` передаются именно такие объекты.
|
|
96
116
|
|
|
97
|
-
|
|
117
|
+
**Фабрика плагина** — функция, которая принимает конфиг и возвращает плагин (объект). Например: `precache(config)`, `serveFromCache(config)` или собственная `precacheAndServePlugin(config)` из примера выше. Конфиг задаётся по месту вызова фабрики; в общий `options` попадают только `logger?` и `onError?`.
|
|
98
118
|
|
|
99
|
-
|
|
119
|
+
## Демо
|
|
100
120
|
|
|
101
|
-
|
|
121
|
+
В папке [demo/](demo/) — приложение **React + Vite** с пресетом **offlineFirst** и типовым сервис-воркером **activateOnSignal**. Запуск из корня: `pnpm start`. Подробности — в [demo/README.md](demo/README.md).
|
|
102
122
|
|
|
103
123
|
## `initServiceWorker(plugins, options)`
|
|
104
124
|
|
|
105
|
-
`initServiceWorker` — точка входа:
|
|
125
|
+
`initServiceWorker` — точка входа: регистрирует обработчики событий Service Worker (`install`, `activate`, `fetch`, …) и прогоняет их через список плагинов.
|
|
106
126
|
|
|
107
|
-
-
|
|
108
|
-
-
|
|
127
|
+
- **`plugins`** — массив плагинов (объектов). Плагины с конфигом получаются вызовом **фабрик** по месту использования (см. раздел «Фабрика плагинов»).
|
|
128
|
+
- **`options`** — только `logger?` и `onError?`. В обработчики плагинов вторым аргументом передаётся **logger** (из `options` или `console`).
|
|
109
129
|
|
|
110
130
|
**Пример:**
|
|
111
131
|
|
|
112
132
|
```typescript
|
|
113
|
-
initServiceWorker(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
})
|
|
133
|
+
initServiceWorker(
|
|
134
|
+
[
|
|
135
|
+
precache({ cacheName: 'v1', assets: ['/'] }),
|
|
136
|
+
serveFromCache({ cacheName: 'v1' }),
|
|
137
|
+
],
|
|
138
|
+
{ logger: serverLogger, onError: handleError }
|
|
139
|
+
);
|
|
117
140
|
```
|
|
118
141
|
|
|
119
|
-
## ⚙️
|
|
142
|
+
## ⚙️ Опции initServiceWorker (logger, onError)
|
|
120
143
|
|
|
121
|
-
|
|
144
|
+
Второй параметр `options` типа `ServiceWorkerInitOptions`: в нём только `logger?` и `onError?`. В обработчики плагинов передаётся только **logger** (второй аргумент); если `logger` не указан, используется `console`. Поле `onError` нужно только библиотеке, в плагины не передаётся.
|
|
145
|
+
|
|
146
|
+
Тип `PluginContext` в API используется для типизации (содержит `logger?`); «богатого контекста» плагинам не передаётся.
|
|
122
147
|
|
|
123
148
|
```typescript
|
|
124
|
-
interface
|
|
149
|
+
interface PluginContext {
|
|
125
150
|
logger?: Logger; // по умолчанию console
|
|
126
|
-
// сюда можно добавлять свои поля: version, assets, cacheName и т.д.
|
|
127
151
|
}
|
|
128
152
|
|
|
129
|
-
|
|
130
|
-
interface ServiceWorkerInitOptions extends SwContext {
|
|
153
|
+
interface ServiceWorkerInitOptions extends PluginContext {
|
|
131
154
|
onError?: (error, event, errorType?) => void; // только для библиотеки, в плагины не передаётся
|
|
132
155
|
}
|
|
133
156
|
```
|
|
134
157
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
Формируйте объект `options` в своём сервис-воркере (контекст для плагинов + при необходимости `onError`) и передавайте его в `initServiceWorker`. В плагины передаётся тот же объект как контекст — плагины получают доступ к полям контекста, а `onError` остаётся внутренним делом библиотеки.
|
|
138
|
-
|
|
139
|
-
### Поля конфигурации
|
|
158
|
+
### Поля options
|
|
140
159
|
|
|
141
160
|
#### `logger?: Logger` (опциональное)
|
|
142
161
|
|
|
@@ -155,17 +174,15 @@ interface Logger {
|
|
|
155
174
|
**Пример:**
|
|
156
175
|
|
|
157
176
|
```typescript
|
|
158
|
-
const logger = console; // Использование стандартного console
|
|
159
|
-
|
|
160
177
|
const options = {
|
|
161
|
-
logger,
|
|
178
|
+
logger: customLogger, // Использование кастомного логгера
|
|
162
179
|
// или
|
|
163
180
|
logger: {
|
|
164
|
-
trace: (...data) =>
|
|
165
|
-
debug: (...data) =>
|
|
166
|
-
info: (...data) =>
|
|
167
|
-
warn: (...data) =>
|
|
168
|
-
error: (...data) =>
|
|
181
|
+
trace: (...data) => customLogger('TRACE', ...data),
|
|
182
|
+
debug: (...data) => customLogger('DEBUG', ...data),
|
|
183
|
+
info: (...data) => customLogger('INFO', ...data),
|
|
184
|
+
warn: (...data) => customLogger('WARN', ...data),
|
|
185
|
+
error: (...data) => customLogger('ERROR', ...data),
|
|
169
186
|
},
|
|
170
187
|
};
|
|
171
188
|
```
|
|
@@ -186,7 +203,7 @@ const options = {
|
|
|
186
203
|
|
|
187
204
|
```typescript
|
|
188
205
|
// Без onError - ошибки будут проигнорированы
|
|
189
|
-
initServiceWorker([cachePlugin]
|
|
206
|
+
initServiceWorker([cachePlugin]);
|
|
190
207
|
|
|
191
208
|
// С onError - ошибки будут обработаны
|
|
192
209
|
initServiceWorker([cachePlugin], {
|
|
@@ -270,74 +287,63 @@ initServiceWorker(
|
|
|
270
287
|
|
|
271
288
|
## 🔌 Интерфейс плагина
|
|
272
289
|
|
|
273
|
-
|
|
290
|
+
Плагин — объект, реализующий интерфейс `ServiceWorkerPlugin`. Во все обработчики вторым аргументом передаётся **logger** (всегда определён: из `options` или `console`). Специфичный для плагина конфиг задаётся при вызове **фабрики** плагина; в `options` только `logger?` и `onError?`. Параметр типа `_C` (например `PluginContext`) используется для типизации; по умолчанию контекст содержит только `logger`.
|
|
274
291
|
|
|
275
292
|
```typescript
|
|
276
|
-
interface ServiceWorkerPlugin<
|
|
293
|
+
interface ServiceWorkerPlugin<_C extends PluginContext = PluginContext> {
|
|
277
294
|
name: string;
|
|
295
|
+
|
|
278
296
|
order?: number;
|
|
279
297
|
|
|
280
|
-
install?: (event: ExtendableEvent,
|
|
281
|
-
|
|
298
|
+
install?: (event: ExtendableEvent, logger: Logger) => Promise<void> | void;
|
|
299
|
+
|
|
300
|
+
activate?: (event: ExtendableEvent, logger: Logger) => Promise<void> | void;
|
|
301
|
+
|
|
282
302
|
fetch?: (
|
|
283
303
|
event: FetchEvent,
|
|
284
|
-
|
|
304
|
+
logger: Logger
|
|
285
305
|
) => Promise<Response | undefined> | Response | undefined;
|
|
286
|
-
|
|
287
|
-
|
|
306
|
+
|
|
307
|
+
message?: (event: SwMessageEvent, logger: Logger) => void;
|
|
308
|
+
|
|
309
|
+
sync?: (event: SyncEvent, logger: Logger) => Promise<void> | void;
|
|
310
|
+
|
|
288
311
|
push?: (
|
|
289
312
|
event: PushEvent,
|
|
290
|
-
|
|
313
|
+
logger: Logger
|
|
291
314
|
) =>
|
|
292
315
|
| Promise<PushNotificationPayload | void>
|
|
293
316
|
| PushNotificationPayload
|
|
294
317
|
| void;
|
|
318
|
+
|
|
295
319
|
periodicsync?: (
|
|
296
320
|
event: PeriodicSyncEvent,
|
|
297
|
-
|
|
321
|
+
logger: Logger
|
|
298
322
|
) => Promise<void> | void;
|
|
299
323
|
}
|
|
300
324
|
```
|
|
301
325
|
|
|
302
|
-
Плагин может объявить требуемый контекст через дженерик: `ServiceWorkerPlugin<SwContext & { assets: string[]; cacheName: string }>`. Тогда TypeScript потребует передать в `initServiceWorker` объект `options` с полями `assets` и `cacheName` (при вызове с литералом массива плагинов тип `options` выводится автоматически).
|
|
303
|
-
|
|
304
326
|
### 📝 Описание методов
|
|
305
327
|
|
|
306
|
-
| Метод | Событие | Возвращает
|
|
307
|
-
| -------------- | -------------- |
|
|
308
|
-
| `install` | `install` | `void`
|
|
309
|
-
| `activate` | `activate` | `void`
|
|
310
|
-
| `fetch` | `fetch` | `Response \| undefined`
|
|
311
|
-
| `message` | `message` | `void`
|
|
312
|
-
| `sync` | `sync` | `void`
|
|
313
|
-
| `push` | `push` | `PushNotificationPayload \|
|
|
314
|
-
| `periodicsync` | `periodicsync` | `void`
|
|
328
|
+
| Метод | Событие | Возвращает | Описание |
|
|
329
|
+
| -------------- | -------------- | ----------------------------------------------- | ------------------------------------------- |
|
|
330
|
+
| `install` | `install` | `void` | Инициализация плагина при установке SW |
|
|
331
|
+
| `activate` | `activate` | `void` | Активация плагина при обновлении SW |
|
|
332
|
+
| `fetch` | `fetch` | `Response \| undefined` | Обработка сетевых запросов |
|
|
333
|
+
| `message` | `message` | `void` | Обработка сообщений от основного потока |
|
|
334
|
+
| `sync` | `sync` | `void` | Синхронизация данных в фоне |
|
|
335
|
+
| `push` | `push` | `PushNotificationPayload \| false \| undefined` | Обработка и отображение сетевой нотификации |
|
|
336
|
+
| `periodicsync` | `periodicsync` | `void` | Периодические фоновые задачи |
|
|
315
337
|
|
|
316
338
|
### 🎯 Особенности обработчиков
|
|
317
339
|
|
|
318
|
-
- Во все методы вторым аргументом передаётся
|
|
340
|
+
- Во все методы вторым аргументом передаётся **logger** (всегда определён: из `options` или `console`).
|
|
319
341
|
- **`fetch`**: Возвращает `Response` для завершения цепочки или `undefined` для передачи следующему плагину. Если все плагины вернули `undefined`, фреймворк вызывает `fetch(event.request)`.
|
|
320
|
-
- **`push`**:
|
|
342
|
+
- **`push`**: Возвращает `PushNotificationPayload` (объект для [Notification API](https://developer.mozilla.org/en-US/docs/Web/API/Notification)), `false` (не отображать уведомление) или `undefined` (решение об отображении отдаётся библиотеке). Вызываются все плагины с `push`. Для каждого возврата типа `PushNotificationPayload` вызывается `showNotification`. Уведомление не показывается, если все вернули `false` или смесь `undefined` и `false` без payload. Библиотека отображает одно уведомление **только когда все** плагины вернули `undefined` (и в данных есть что показывать).
|
|
321
343
|
- **Остальные обработчики** (`install`, `activate`, `message`, `sync`, `periodicsync`): возвращаемое значение не используется; фреймворк вызывает метод каждого плагина по очереди, цепочка не прерывается.
|
|
322
344
|
- **Все обработчики опциональны** — реализуйте только нужные события.
|
|
323
345
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
Библиотека **не вызывает** `skipWaiting()` и `clients.claim()` — это поведение задаётся индивидуально в каждом проекте и оставлено на усмотрение плагинов. При необходимости вызывайте их в своих обработчиках `install` и `activate` (в библиотеке реализованы данные примитивы - смотри ниже):
|
|
327
|
-
|
|
328
|
-
```typescript
|
|
329
|
-
const updatePlugin = {
|
|
330
|
-
name: 'update-plugin',
|
|
331
|
-
install: (event) => {
|
|
332
|
-
self.skipWaiting();
|
|
333
|
-
},
|
|
334
|
-
activate: (event) => {
|
|
335
|
-
event.waitUntil(self.clients.claim());
|
|
336
|
-
},
|
|
337
|
-
};
|
|
338
|
-
```
|
|
339
|
-
|
|
340
|
-
## 🎯 Порядок выполнения
|
|
346
|
+
## 🎯 Порядок выполнения плагинов
|
|
341
347
|
|
|
342
348
|
Плагины выполняются в следующем порядке:
|
|
343
349
|
|
|
@@ -349,16 +355,16 @@ const updatePlugin = {
|
|
|
349
355
|
```typescript
|
|
350
356
|
const plugins = [
|
|
351
357
|
{ name: 'first' }, // без order - выполняется первым
|
|
352
|
-
{ name: '
|
|
358
|
+
{ name: 'fifth', order: 4 },
|
|
359
|
+
{ name: 'fourth', order: 3 },
|
|
353
360
|
{ name: 'second' }, // без order - выполняется вторым
|
|
354
|
-
{ name: 'third', order:
|
|
355
|
-
{ name: 'fifth' }, // без order - выполняется третьим
|
|
361
|
+
{ name: 'third', order: 2 },
|
|
356
362
|
];
|
|
357
363
|
|
|
358
|
-
// Порядок выполнения: first → second →
|
|
364
|
+
// Порядок выполнения: first → second → third → fourth → fifth
|
|
359
365
|
```
|
|
360
366
|
|
|
361
|
-
**Преимущества
|
|
367
|
+
**Преимущества системы:**
|
|
362
368
|
|
|
363
369
|
- 🎯 **Предсказуемость** - плагины без `order` всегда выполняются первыми
|
|
364
370
|
- 🔧 **Простота** - не нужно знать, какие номера уже заняты
|
|
@@ -375,21 +381,24 @@ const plugins = [
|
|
|
375
381
|
Все обработчики выполняются **одновременно** с помощью `Promise.all()`:
|
|
376
382
|
|
|
377
383
|
```typescript
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
},
|
|
384
|
-
};
|
|
385
|
-
const installPlugin2 = {
|
|
386
|
-
name: 'cache-ext',
|
|
387
|
-
install: async () => {
|
|
388
|
-
/* кэширование вспомогательных ресурсов */
|
|
389
|
-
},
|
|
390
|
-
};
|
|
384
|
+
import {
|
|
385
|
+
precache,
|
|
386
|
+
skipWaiting,
|
|
387
|
+
precacheMissing,
|
|
388
|
+
} from '@budarin/pluggable-serviceworker/plugins';
|
|
391
389
|
|
|
392
|
-
|
|
390
|
+
import { customLogger } from '../customLogger';
|
|
391
|
+
import { initServiceWorker } from '@budarin/pluggable-serviceworker';
|
|
392
|
+
|
|
393
|
+
// Все install-обработчики (precache, precacheMissing, skipWaiting) выполнятся параллельно
|
|
394
|
+
initServiceWorker(
|
|
395
|
+
[
|
|
396
|
+
precache({ cacheName: 'app-v1', assets: ['/', '/main.js'] }),
|
|
397
|
+
precacheMissing({ cacheName: 'ext-v1', assets: ['/worker.js'] }),
|
|
398
|
+
skipWaiting,
|
|
399
|
+
],
|
|
400
|
+
{ logger: customLogger }
|
|
401
|
+
);
|
|
393
402
|
```
|
|
394
403
|
|
|
395
404
|
**Почему параллельно:**
|
|
@@ -403,39 +412,62 @@ const installPlugin2 = {
|
|
|
403
412
|
|
|
404
413
|
**События:** `fetch`, `push`
|
|
405
414
|
|
|
406
|
-
Обработчики выполняются **по
|
|
415
|
+
Обработчики выполняются **по очереди**:
|
|
416
|
+
|
|
417
|
+
#### Fetch — с прерыванием цепочки
|
|
407
418
|
|
|
408
|
-
|
|
419
|
+
Обработчики `fetch` вызываются **по очереди**. Плагин может вернуть `Response` — тогда цепочка прерывается и этот ответ уходит клиенту. Либо вернуть `undefined` — тогда запрос передаётся следующему плагину. Если **все** плагины вернули `undefined`, фреймворк сам выполняет `fetch(event.request)`.
|
|
420
|
+
|
|
421
|
+
Пример фабрики, которая прерывает цепочку при неавторизованном доступе к защищённым путям:
|
|
409
422
|
|
|
410
423
|
```typescript
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
}
|
|
420
|
-
|
|
424
|
+
import type {
|
|
425
|
+
PluginContext,
|
|
426
|
+
ServiceWorkerPlugin,
|
|
427
|
+
} from '@budarin/pluggable-serviceworker';
|
|
428
|
+
|
|
429
|
+
function authPlugin(config: {
|
|
430
|
+
protectedPaths: string[];
|
|
431
|
+
}): ServiceWorkerPlugin<PluginContext> {
|
|
432
|
+
const { protectedPaths } = config;
|
|
433
|
+
|
|
434
|
+
return {
|
|
435
|
+
name: 'auth',
|
|
436
|
+
|
|
437
|
+
fetch: async (event, logger) => {
|
|
438
|
+
const path = new URL(event.request.url).pathname;
|
|
439
|
+
|
|
440
|
+
if (protectedPaths.some((p) => path.startsWith(p))) {
|
|
441
|
+
if (needsAuth(event.request)) {
|
|
442
|
+
logger.warn('auth: unauthorized', event.request.url);
|
|
443
|
+
|
|
444
|
+
return new Response('Unauthorized', { status: 401 }); // Прерывает цепочку
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
return undefined; // Передаёт следующему плагину
|
|
448
|
+
},
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Использование: authPlugin({ protectedPaths: ['/api/'] })
|
|
421
453
|
```
|
|
422
454
|
|
|
423
455
|
**Почему последовательно:**
|
|
424
456
|
|
|
425
|
-
- **fetch**: Нужен только один
|
|
426
|
-
- **push**: Плагин может вернуть
|
|
457
|
+
- **fetch**: Нужен только один ответ на текущий запрос браузера, первый успешный прерывает цепочку. Если никто не вернул ответ — выполняется `fetch(event.request)`
|
|
458
|
+
- **push**: Плагин может вернуть `PushNotificationPayload`, `false` (не показывать) или `undefined` (решение отдаётся библиотеке). Библиотека вызывает `showNotification` для каждого payload. Не показываем, если все вернули `false` или смесь без payload. Библиотека показывает нотификацию и в случае когда **все** плагины вернули `undefined`.
|
|
427
459
|
|
|
428
460
|
### 📋 Сводная таблица
|
|
429
461
|
|
|
430
|
-
| Событие | Выполнение
|
|
431
|
-
| -------------- |
|
|
432
|
-
| `install` |
|
|
433
|
-
| `activate` |
|
|
434
|
-
| `fetch` |
|
|
435
|
-
| `message` |
|
|
436
|
-
| `sync` |
|
|
437
|
-
| `periodicsync` |
|
|
438
|
-
| `push` |
|
|
462
|
+
| Событие | Выполнение | Прерывание | Причина |
|
|
463
|
+
| -------------- | ----------------- | ---------- | -------------------------------------- |
|
|
464
|
+
| `install` | `Параллельно` | `Нет` | Независимая инициализация |
|
|
465
|
+
| `activate` | `Параллельно` | `Нет` | Независимая активация |
|
|
466
|
+
| `fetch` | `Последовательно` | `Да` | Нужен один ответ |
|
|
467
|
+
| `message` | `Параллельно` | `Нет` | Независимые обработчики сообщений |
|
|
468
|
+
| `sync` | `Параллельно` | `Нет` | Независимые задачи |
|
|
469
|
+
| `periodicsync` | `Параллельно` | `Нет` | Независимые периодические задачи |
|
|
470
|
+
| `push` | `Последовательно` | `Нет` | Отображение всех необходимых сообщений |
|
|
439
471
|
|
|
440
472
|
## Примитивы, пресеты и типовые сервис-воркеры
|
|
441
473
|
|
|
@@ -443,59 +475,215 @@ const authPlugin = {
|
|
|
443
475
|
|
|
444
476
|
Один примитив — одна операция. Импорт: `@budarin/pluggable-serviceworker/plugins`.
|
|
445
477
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
|
449
|
-
|
|
|
450
|
-
|
|
|
451
|
-
|
|
|
452
|
-
|
|
|
453
|
-
|
|
|
454
|
-
|
|
|
455
|
-
|
|
|
456
|
-
|
|
|
478
|
+
Примитивы с конфигом — **фабрики плагинов** (см. раздел «Фабрика плагинов»): конфиг передаётся при вызове по месту использования; в `options` в `initServiceWorker` попадают только `logger?` и `onError?`. Примитивы без конфига (`skipWaiting`, `claim`, …) — готовые объекты плагинов.
|
|
479
|
+
|
|
480
|
+
| Название | Событие | Описание |
|
|
481
|
+
| ------------------------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------- |
|
|
482
|
+
| `precache*(config)` | `install` | Кеширует список ресурсов из `config.assets` в кеш `config.cacheName`. |
|
|
483
|
+
| `precacheAndNotify(config)` | `install` | Как **precache**, затем отправляет активным клиентам сообщение `{ type: config.messageType }` (по умолчанию `SW_INSTALLED`). |
|
|
484
|
+
| `precacheMissing(config)` | `install` | Добавляет в кеш только те ресурсы из `config.assets`, которых ещё нет в кеше. |
|
|
485
|
+
| `pruneStaleCache(config)` | `activate` | Удаляет из кеша записи, чей URL не входит в `config.assets`. |
|
|
486
|
+
| `skipWaiting` | `install` | Вызывает `skipWaiting()`. |
|
|
487
|
+
| `claim` | `activate` | Вызывает `clients.claim()`. |
|
|
488
|
+
| `reloadClients` | `activate` | Перезагружает все окна-клиенты через `client.navigate(client.url)`. |
|
|
489
|
+
| `claimAndReloadClients` | `activate` | Композиция **claim** + **reloadClients**: сначала claim, затем перезагрузка (порядок гарантирован — один плагин). |
|
|
490
|
+
| `skipWaitingOnMessage(config?)` | `message` | При сообщении с `event.data.type === 'SW_MSG_SKIP_WAITING'` вызывает `skipWaiting()`. |
|
|
491
|
+
| `serveFromCache(config)` | `fetch` | Отдаёт ресурс из кеша `config.cacheName`; при отсутствии его в кэше — undefined. |
|
|
492
|
+
| `restoreAssetToCache(config)` | `fetch` | Для URL из `config.assets`: отдам ресурс из кеша или запрашиваем по сети, затем в кладем кго в кеш. Иначе — undefined. |
|
|
493
|
+
| `cacheFirst(config)` | `fetch` | Отдаем ресурс из кэша `config.cacheName`: при отсутствии его в кэше — делаем запрос на сервер и затем кладем ответ в кэш. |
|
|
494
|
+
| `networkFirst(config)` | `fetch` | Делаем запрос на сервер, при успехе — кладем его в кэш. При ошибке — отдаем из кэша. Иначе - `undefined`. |
|
|
495
|
+
| `staleWhileRevalidate(config)` | `fetch` | Отдаёт из кэша, в фоне обновляет кэш. |
|
|
496
|
+
|
|
497
|
+
#### Композиция примитивов
|
|
498
|
+
|
|
499
|
+
Обработчики одного типа (`install`, `activate` и т.д.) у разных плагинов выполняются **параллельно**. Если нужна строгая последовательность (например «сначала claim, потом перезагрузка клиентов»), соберите один плагин, который по очереди вызывает логику примитивов — для гарантии порядка.
|
|
500
|
+
|
|
501
|
+
**Пример: claimAndReloadClients как композиция двух примитивов**
|
|
502
|
+
|
|
503
|
+
Плагин **claimAndReloadClients** вызывает существующие примитивы **claim** и **reloadClients** по очереди:
|
|
504
|
+
|
|
505
|
+
```typescript
|
|
506
|
+
import { claim } from '@budarin/pluggable-serviceworker/plugins';
|
|
507
|
+
import { reloadClients } from '@budarin/pluggable-serviceworker/plugins';
|
|
508
|
+
|
|
509
|
+
const claimPlugin = claim();
|
|
510
|
+
const reloadPlugin = reloadClients();
|
|
511
|
+
|
|
512
|
+
activate: (event, logger) =>
|
|
513
|
+
Promise.resolve(claimPlugin.activate(event, logger)).then(() =>
|
|
514
|
+
reloadPlugin.activate(event, logger)
|
|
515
|
+
),
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
**Пример: кастомный кэш и логика по URL**
|
|
519
|
+
|
|
520
|
+
Фабрика `postsSwrPlugin(config)` возвращает плагин, который применяет `stale-while-revalidate`(SWR) только к запросам, подходящим под `pathPattern`.
|
|
521
|
+
|
|
522
|
+
```typescript
|
|
523
|
+
// postsSwrPlugin.ts
|
|
524
|
+
import type { Plugin } from '@budarin/pluggable-serviceworker';
|
|
457
525
|
|
|
458
|
-
|
|
526
|
+
import {
|
|
527
|
+
precache,
|
|
528
|
+
serveFromCache,
|
|
529
|
+
} from '@budarin/pluggable-serviceworker/plugins';
|
|
530
|
+
import { initServiceWorker } from '@budarin/pluggable-serviceworker';
|
|
531
|
+
|
|
532
|
+
function postsSwrPlugin(config: {
|
|
533
|
+
cacheName: string;
|
|
534
|
+
pathPattern?: RegExp;
|
|
535
|
+
}): Plugin {
|
|
536
|
+
const { cacheName, pathPattern = /\/api\/posts(\/|$)/ } = config;
|
|
537
|
+
|
|
538
|
+
return {
|
|
539
|
+
name: 'postsSwr',
|
|
540
|
+
order: 0,
|
|
541
|
+
|
|
542
|
+
fetch: async (event) => {
|
|
543
|
+
if (!pathPattern.test(new URL(event.request.url).pathname)) {
|
|
544
|
+
return undefined;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
const cache = await caches.open(cacheName);
|
|
548
|
+
const cached = await cache.match(event.request);
|
|
549
|
+
const revalidate = fetch(event.request).then(async (response) => {
|
|
550
|
+
if (response.ok) {
|
|
551
|
+
await cache.put(event.request, response.clone());
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
return response;
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
if (cached) {
|
|
558
|
+
void revalidate;
|
|
559
|
+
return cached;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
return revalidate;
|
|
563
|
+
},
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
```typescript
|
|
569
|
+
// sw.ts
|
|
570
|
+
const staticCache = 'static-v1';
|
|
571
|
+
const assets = ['/', '/main.js'];
|
|
572
|
+
|
|
573
|
+
initServiceWorker(
|
|
574
|
+
[
|
|
575
|
+
precache({ cacheName: staticCache, assets }),
|
|
576
|
+
serveFromCache({ cacheName: staticCache }),
|
|
577
|
+
postsSwrPlugin({ cacheName: 'posts' }),
|
|
578
|
+
],
|
|
579
|
+
{ logger: console }
|
|
580
|
+
);
|
|
581
|
+
```
|
|
459
582
|
|
|
460
583
|
### Пресеты
|
|
461
584
|
|
|
462
585
|
Комбинации примитивов (стратегии кеширования). Импорт: `@budarin/pluggable-serviceworker/presets`.
|
|
463
586
|
|
|
464
|
-
| Название
|
|
465
|
-
|
|
|
466
|
-
|
|
|
587
|
+
| Название | Состав | Назначение |
|
|
588
|
+
| ---------------------- | ------------------------------------------- | ------------------------------------------------------------------------- |
|
|
589
|
+
| `offlineFirst(config)` | `precache(config) + serveFromCache(config)` | Статика из кеша, при отсутствии ресурса в кэше — делаем запрос к серверу. |
|
|
467
590
|
|
|
591
|
+
Конфиг пресета: `OfflineFirstConfig` (cacheName, assets). Импорт из `@budarin/pluggable-serviceworker/presets`.
|
|
468
592
|
Стратегии **networkFirst**, **staleWhileRevalidate** и др. доступны как примитивы — собирайте свой кастомный сервис-воркер из примитивов и пресетов.
|
|
469
593
|
|
|
470
594
|
### Типовые сервис-воркеры (из коробки)
|
|
471
595
|
|
|
472
596
|
Готовые точки входа по **моменту активации** (все с кешированием offline-first). Импорт: `@budarin/pluggable-serviceworker/sw`.
|
|
473
597
|
|
|
474
|
-
| Название
|
|
475
|
-
|
|
|
476
|
-
|
|
|
477
|
-
|
|
|
478
|
-
|
|
|
598
|
+
| Название | Описание |
|
|
599
|
+
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
|
600
|
+
| `activateOnNextVisitServiceWorker` | Кеширующий SW, активируется и обновляется при следующем визите на страницу (перезагрузке) после загрузки нового сервисворкера. |
|
|
601
|
+
| `activateImmediatelyServiceWorker` | Кеширующий SW, активируется и вступает в действие сразу при 1-й загрузке и при обновлении. |
|
|
602
|
+
| `activateOnSignalServiceWorker` | Кеширующий SW, устанавливается сразу, но активируется при обновлении только по сигналу со страницы (сообщение `SW_MSG_SKIP_WAITING`). |
|
|
479
603
|
|
|
480
604
|
Пример использования типового SW:
|
|
481
605
|
|
|
482
606
|
```typescript
|
|
483
607
|
// sw.js — точка входа вашего сервис-воркера
|
|
608
|
+
import { customLogger } from './customLogger';
|
|
484
609
|
import { activateOnNextVisitServiceWorker } from '@budarin/pluggable-serviceworker/sw';
|
|
485
610
|
|
|
486
611
|
activateOnNextVisitServiceWorker({
|
|
487
612
|
assets: ['/', '/styles.css', '/script.js'],
|
|
488
613
|
cacheName: 'my-cache-v1',
|
|
489
|
-
logger:
|
|
490
|
-
onError: (err, event, type) =>
|
|
614
|
+
logger: customLogger,
|
|
615
|
+
onError: (err, event, type) => customLogger.error(type, err),
|
|
491
616
|
});
|
|
492
617
|
```
|
|
493
618
|
|
|
494
619
|
На странице регистрируйте этот файл: `navigator.serviceWorker.register('/sw.js')` (или путь, по которому сборка отдаёт ваш sw.js).
|
|
495
620
|
|
|
621
|
+
### Публикуемые утилиты
|
|
622
|
+
|
|
623
|
+
Утилиты, доступные для использования в своих плагинах. Импорт: `@budarin/pluggable-serviceworker/utils`.
|
|
624
|
+
|
|
625
|
+
| Название | Описание |
|
|
626
|
+
| ---------------------------- | ------------------------------------------------------------------------ |
|
|
627
|
+
| `normalizeUrl(url)` | Нормализует URL (относительный → абсолютный по origin SW) для сравнения. |
|
|
628
|
+
| `notifyClients(messageType)` | Отправляет сообщение `{ type: messageType }` всем окнам-клиентам (SW). |
|
|
629
|
+
|
|
630
|
+
## Разработка пакета плагина
|
|
631
|
+
|
|
632
|
+
Типы для описания плагина экспортируются из этого пакета. Отдельный пакет с плагином не публикует свои типы — он объявляет зависимость от `@budarin/pluggable-serviceworker` и импортирует типы оттуда.
|
|
633
|
+
|
|
634
|
+
**1. Зависимости в пакете плагина**
|
|
635
|
+
|
|
636
|
+
В `package.json` своего пакета добавьте:
|
|
637
|
+
|
|
638
|
+
```json
|
|
639
|
+
{
|
|
640
|
+
"peerDependencies": {
|
|
641
|
+
"@budarin/pluggable-serviceworker": "^1.0.0"
|
|
642
|
+
},
|
|
643
|
+
"devDependencies": {
|
|
644
|
+
"@budarin/pluggable-serviceworker": "^1.5.0"
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
`peerDependencies` — чтобы плагин работал с той версией библиотеки, которую установил пользователь; в `devDependencies` — для сборки и типов.
|
|
650
|
+
|
|
651
|
+
**2. Импорт типов в коде плагина**
|
|
652
|
+
|
|
653
|
+
Импортируйте тип **`Plugin`** (алиас для `ServiceWorkerPlugin<PluginContext>`); при необходимости — `Logger`, `SwMessageEvent`, `PushNotificationPayload` и др.
|
|
654
|
+
|
|
655
|
+
```typescript
|
|
656
|
+
import type { Plugin } from '@budarin/pluggable-serviceworker';
|
|
657
|
+
|
|
658
|
+
export interface MyPluginConfig {
|
|
659
|
+
cacheName: string;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
export function myPlugin(config: MyPluginConfig): Plugin {
|
|
663
|
+
const { cacheName } = config;
|
|
664
|
+
|
|
665
|
+
return {
|
|
666
|
+
name: 'my-plugin',
|
|
667
|
+
|
|
668
|
+
install: async (_event, logger) => {
|
|
669
|
+
logger.info('my-plugin: install');
|
|
670
|
+
const cache = await caches.open(cacheName);
|
|
671
|
+
await cache.add('/offline.html');
|
|
672
|
+
},
|
|
673
|
+
|
|
674
|
+
fetch: async (event, _logger) => {
|
|
675
|
+
const cached = await caches.match(event.request);
|
|
676
|
+
return cached ?? undefined;
|
|
677
|
+
},
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
Пользователь подключает плагин так: `initServiceWorker([..., myPlugin({ cacheName: 'my-v1' })], options)`.
|
|
683
|
+
|
|
496
684
|
## Режим разработки
|
|
497
685
|
|
|
498
|
-
|
|
686
|
+
В режиме разработки - используйте `import.meta.env.DEV` для Vite для условного использования кода.
|
|
499
687
|
|
|
500
688
|
## 📄 Лицензия
|
|
501
689
|
|