A tiny ServiceWorker for secure web applications.
- Free to use under MIT license
- Small and modular (up to 1KB minify + brotli)
- Tree-shaking supported
- Zero (0) dependencies
- GDPR Compliant
- improve test coverage
- more e2e tests
- documentation
- session management
- Push events
- Range Requests strategy
- precache extract zip
- workbox comparison (~1KB vs ~70KB)
npm install @work-bee/core
import { compileConfig, installEvent, activateEvent, fetchEvent, strategyCacheFirst } from '@work-bee/core'
const config = compileConfig({
cachePrefix, '1-',
//precache: ['/path/to/file.ext'],
routes: [
{
methods: ['GET'],
pathPattern: new RegExp('/img/(.+)$'),
cacheName: 'img',
strategy: strategyCacheFirst
},
...
]
})
addEventListener('install', (event) => {
installEvent(event, config)
})
addEventListener('activate', (event) => {
activateEvent(event, config)
})
addEventListener('fetch', (event) => {
fetchEvent(event, config)
})
import { backgroundFetchSuccessEvent, backgroundFetchFailEvent } from '@work-bee/core'
...
self.addEventListener('backgroundfetchsuccess', (event) => {
backgroundFetchSuccessEvent(event, config)
})
self.addEventListener('backgroundfetchfail', (event) => {
backgroundFetchFailEvent(event, config)
})
sequenceDiagram
participant Page
participant ServiceWorker
participant Cache
participant Network
autonumber
Page->>ServiceWorker: request
ServiceWorker->>Network: fetch
Network->>ServiceWorker: 200: OK
ServiceWorker->>Page: response
sequenceDiagram
participant Page
participant ServiceWorker
participant Cache
participant Network
autonumber
Page->>ServiceWorker: request
ServiceWorker->>Cache: match
Cache->>ServiceWorker: resolve
ServiceWorker->>Page: response
sequenceDiagram
participant Page
participant ServiceWorker
participant Cache
participant Network
autonumber
Page->>ServiceWorker: request
ServiceWorker->>Network: fetch
Network-->>ServiceWorker: 404: Not Found
ServiceWorker->>Cache: match
Cache->>ServiceWorker: resolve
ServiceWorker->>Page: response
sequenceDiagram
participant Page
participant ServiceWorker
participant Cache
participant Network
autonumber
Page->>ServiceWorker: request
ServiceWorker->>Cache: Cache: Not Found
ServiceWorker->>Network: fetch
Network->>ServiceWorker: 200: OK
ServiceWorker->>Page: response
sequenceDiagram
participant Page
participant ServiceWorker
participant Cache
participant Network
autonumber
Page->>ServiceWorker: request
par
ServiceWorker->>strategyCacheFirst: fetch
strategyCacheFirst->>ServiceWorker: 200: OK
ServiceWorker->>Page: response
and Revalidate
ServiceWorker->>strategy: fetch
strategy->>ServiceWorker: 200: OK
ServiceWorker->>Cache: put
end
sequenceDiagram
participant Page
participant ServiceWorker
participant Cache
participant Network
autonumber
Page->>ServiceWorker: request
par routes[0]
ServiceWorker->>strategy: fetch
strategy->>ServiceWorker: 200: OK
and routes[...]
ServiceWorker->>strategy: fetch
strategy->>ServiceWorker: 200: OK
end
ServiceWorker->>Page: response
Breaks a page into parts that can each have their own strategy. ie <head>
, <header>
, <main>
, and <footer>
where only the <main>
may need to be requested when multiple pages are being viewed (<main>
in this case should bootstrap the <head>
using js).
sequenceDiagram
participant Page
participant ServiceWorker
participant Cache
participant Network
autonumber
Page->>ServiceWorker: request
par header.html
ServiceWorker->>Cache: match
Cache->>ServiceWorker: resolve
and main.html
ServiceWorker->>Network: fetch
Network->>ServiceWorker: 200: OK
and footer.html
ServiceWorker->>Cache: match
Cache->>ServiceWorker: resolve
end
ServiceWorker->>Page: response
import { compileConfig } @work-bee/core
import { installEvent, activateEvent, fetchEvent } from '@work-bee/events'
import { strategyCacheFirst, strategyPartition } from '@work-bee/strategies'
const config = compileConfig({
cachePrefix, 'sw-VERSION-',
routes: [
{
methods: ['GET'],
pathPattern: new RegExp('/api/data$'),
cacheName: 'data',
strategy: strategyPartition(compileConfig({
strategy: strategyCacheFirst,
cacheName: 'strategyPartition',
makeRequests: () => []
}),
cacheMaxAgeInSeconds: -1
},
...
]
})
addEventListener('install', (event) => {
installEvent(event, config)
})
addEventListener('activate', async (event) => {
await activateEvent(event, config)
})
addEventListener('fetch', (event) => {
fetchEvent(event, config)
})
sequenceDiagram
participant Page
participant ServiceWorker
participant Cache
participant Network
autonumber
Page->>ServiceWorker: request
par ?year=2000
ServiceWorker->>Cache: match
Cache->>ServiceWorker: resolve
and ?year=2001
ServiceWorker->>Cache: no-match
ServiceWorker->>Network: fetch
Network->>ServiceWorker: 200: OK
end
ServiceWorker->>Page: response
Choose strategy based on if Save-Data is enabled.
https://serviceworke.rs https://github.com/mdn/serviceworker-cookbook
/* eslint-env: serviceworker */
import { strategyNetworkFirst } from '@work-bee/core'
const config = {
strategy: strategyNetworkFirst
}
addEventListener('install', (event) => {
eventInstall(event, config)
})
addEventListener('activate', (event) => {
eventActivate(event, config)
})
addEventListener('fetch', (event) => {
eventFetch(event, config)
})
/* eslint-env: serviceworker */
import { strategyCacheOnly } from '@work-bee/core'
const config = {
strategy: strategyCacheOnly
}
addEventListener('install', (event) => {
eventInstall(event, config)
})
addEventListener('activate', (event) => {
eventActivate(event, config)
})
addEventListener('fetch', (event) => {
eventFetch(event, config)
})
/* eslint-env: serviceworker */
import { strategyStaleWhileRevalidate } from '@work-bee/core'
const config = {
strategy: strategyStaleWhileRevalidate
}
addEventListener('install', (event) => {
eventInstall(event, config)
})
addEventListener('activate', (event) => {
eventActivate(event, config)
})
addEventListener('fetch', (event) => {
eventFetch(event, config)
})
TODO refresh middleware, add to strategyStaleWhileRevalidate?
/* eslint-env: serviceworker */
import { strategyNetworkOnly, strategyCacheOnly } from '@work-bee/core'
import fallbackMiddleware from '@work-bee/fallback'
const fallback = fallbackMiddleware({ path: '/path/to/fallback' })
const config = {
precache: {
routes: [
{
path: '/path/to/fallback'
}
],
strategy: strategyCacheOnly
},
strategy: strategyNetworkOnly,
after: [fallback.after]
}
addEventListener('install', (event) => {
eventInstall(event, config)
})
addEventListener('activate', (event) => {
eventActivate(event, config)
})
addEventListener('fetch', (event) => {
eventFetch(event, config)
})
/* eslint-env: serviceworker */
import { strategyNetworkOnly, strategyCacheOnly } from '@work-bee/core'
import fallbackMiddleware from '@work-bee/fallback'
const fallback = fallbackMiddleware({
path: '/path/to/offline',
statusCodes: [503, 504] // or Error
})
const config = {
precache: {
routes: [
{
path: '/path/to/offline'
}
],
strategy: strategyCacheOnly
},
strategy: strategyNetworkOnly,
after: [fallback.after]
}
addEventListener('install', (event) => {
eventInstall(event, config)
})
addEventListener('activate', (event) => {
eventActivate(event, config)
})
addEventListener('fetch', (event) => {
eventFetch(event, config)
})
/* eslint-env: serviceworker */
import { strategyCacheFirst, strategyCacheOnly } from '@work-bee/core'
const config = {
precache: {
routes: [
{
path: '/path/to/required'
},
...
],
eventType: 'precache'
},
strategy: strategyCacheFirst
}
addEventListener('install', (event) => {
eventInstall(event, config)
})
addEventListener('activate', (event) => {
eventActivate(event, config)
})
addEventListener('fetch', (event) => {
eventFetch(event, config)
})
/* eslint-env: serviceworker */
import { strategyCacheFirst, strategyCacheOnly } from '@work-bee/core'
const config = {
precache: {
routes: '/path/to/precache.json',
extract: // TODO
},
strategy: strategyCacheFirst
}
addEventListener('install', (event) => {
eventInstall(event, config)
})
addEventListener('activate', (event) => {
eventActivate(event, config)
})
addEventListener('fetch', (event) => {
eventFetch(event, config)
})
/* eslint-env: serviceworker */
/*import { strategyLocalDownload } from '@work-bee/core'
const config = {
routes: [
{
pathPattern: new Regexp('/path/to/download/.*$'),
strategy: strategyLocalDownload
}
]
}
addEventListener('install', (event) => {
eventInstall(event, config)
})
addEventListener('activate', (event) => {
eventActivate(event, config)
})
addEventListener('fetch', (event) => {
eventFetch(event, config)
})*/
TODO 3rd party middleware - ie plausible
TODO new middleware.before - group external domains to allow request path replace & ping all to pick best
TODO new plugin - return empty response, iterate over contents and cachePut
TODO need to understand more
/* eslint-env: serviceworker */
import { strategyCacheFirst } from '@work-bee/core'
import { offlineMiddleware } from '@work-bee/offline'
const offline = offlineMiddleware({ pollDelay: 0 })
const config = {
strategy: strategyCacheFirst,
middlewares: [offline]
}
addEventListener('install', (event) => {
eventInstall(event, config)
})
addEventListener('activate', (event) => {
eventActivate(event, config)
})
addEventListener('fetch', (event) => {
eventFetch(event, config)
})
addEventListener('message', (event) => {
const { data } = event
event.waitUntil(messageEvents[data.type](data))
})
const messageEvents = {
online: offline.onlineEvent
}
/* eslint-env: serviceworker */
import { strategyCacheFirst } from '@work-bee/core'
const config = {
strategy: strategyCacheFirst
}
addEventListener('install', (event) => {
eventInstall(event, config)
})
addEventListener('activate', (event) => {
eventActivate(event, config)
})
addEventListener('fetch', (event) => {
eventFetch(event, config)
})
/* eslint-env: serviceworker */
import {
compileConfig,
eventInstall,
eventActivate,
eventFetch,
cacheOverrideEvent
} from '@work-bee/core'
const config = compileConfig({
strategy: strategyNetworkFirst
})
addEventListener('install', (event) => {
eventInstall(event, config)
})
addEventListener('activate', (event) => {
eventActivate(event, config)
})
addEventListener('fetch', (event) => {
eventFetch(event, config)
})
addEventListener('message', (event) => {
const { data } = event
/* data = {
type: 'cache',
request: new Request('/path/to/template', {method:'GET'}),
response: new Response('')
}*/
event.waitUntil(messageEvents[data.type](data))
})
const messageEvents = {
cache: cacheOverrideEvent(config)
}