Compare commits

..

149 commits
v0.0.2 ... main

Author SHA1 Message Date
21765054e0 chore: update gh discussions link on readme 2025-04-27 18:51:33 +02:00
15a8becfe4 feat: initial undefined options handling 2025-04-07 23:52:24 +01:00
6e64d75b1c feat: update type names and make serviceWorker options optional) 2025-04-07 23:48:39 +01:00
2ae36ffb81 feat: organize preset exports 2025-04-07 23:44:01 +01:00
8d7e5df515 feat: add presets stale-while-revalidate & delete-old-caches 2025-04-07 23:22:56 +01:00
00e382dc70 chore: organize workspace scripts 2025-04-07 20:59:58 +01:00
e5f644e241 refactor: js -> ts 2025-04-07 20:51:55 +01:00
6d2a090387 feat: use AstroIntegration.assets instead of deprecated routes 2025-04-06 18:47:12 +02:00
e3c72fe50a chore: update readme background 2025-04-06 17:03:20 +02:00
7474425106 chore: remove email tickets option 2025-04-06 17:01:36 +02:00
ba9a8aa36b chore: update readme 2025-04-06 15:38:36 +02:00
294a3b25fc chore: remove strategies dir; add sample sw 2025-04-06 14:42:54 +02:00
bd3370f868 chore: organize workspaces 2025-04-06 14:33:57 +02:00
1db93f0c8f feat: initial strategies dir 2025-04-05 21:01:58 +02:00
bbb1bf4445 chore: update deps; astro v5 migrate 'hybrid' -> 'static' rendering 2025-04-05 11:03:51 +02:00
f5466082c2 chore: update repo info 2025-02-09 15:03:57 +01:00
beddea0157 chore: add auto mirror to github build script 2025-02-09 14:49:26 +01:00
81886bd763 chore: update cozy link on readme 2025-01-21 19:55:24 +01:00
c3b9a3bac0 chore: gitignore eslint cache 2024-12-27 16:56:36 +01:00
96e9ca07d5 chore: add check script and use in precommit 2024-12-27 16:56:12 +01:00
bb056b09f5 chore: set up eslint, prettier, husky 2024-12-26 00:54:23 +01:00
75b29eddc5 chore: set up prettier & format code 2024-12-26 00:40:32 +01:00
e174121ab3 0.8.14 2024-12-25 21:39:42 +01:00
1a86a2b2ff chore: update readme 2024-12-25 21:39:33 +01:00
f79f590dc3 0.8.13 2024-12-25 21:34:43 +01:00
4aeb0c3fef chore: update readme with eslint globals instructions 2024-12-25 21:34:36 +01:00
302ec14f39 0.8.12 2024-12-25 21:29:00 +01:00
48931143a8 fix: package.json exports 2024-12-25 21:28:54 +01:00
c0433660aa 0.8.11 2024-12-25 21:27:23 +01:00
c78512d03e fix: malformed exports in package.json 2024-12-25 21:27:14 +01:00
e7616e1a09 0.8.10 2024-12-25 21:20:12 +01:00
291da42cd3 feat: rename astro-plugin-astro-sw to globals 2024-12-25 21:20:06 +01:00
0a0d257f4c 0.8.9 2024-12-25 21:17:06 +01:00
8f4806820a feat: add initial eslint plugin 2024-12-25 21:16:58 +01:00
42e14b8c52 chore(deps): update 2024-09-03 20:52:08 +02:00
240703ba6a 0.8.8 2024-08-25 14:04:05 +02:00
d57affe4ac feat: remove output check in ssr step 2024-08-25 14:03:57 +02:00
a0047af491 0.8.7 2024-08-25 13:45:47 +02:00
e5d0a5e65c chore: format code 2024-08-25 13:45:38 +02:00
536372d895 refactor: simpler ssr step 2024-08-25 13:44:26 +02:00
0cd090b442 feat: add link to post on test app 2024-08-25 13:43:40 +02:00
e25f2b5b59 0.8.6 2024-08-24 21:30:49 +02:00
c4fd32b407 chore: update readme 2024-08-24 21:30:40 +02:00
e35d2e9115 chore: update readme 2024-08-24 21:29:01 +02:00
7de664c451 chore: update readme and package descriptions 2024-08-24 21:26:58 +02:00
41532fca6d 0.8.5 2024-08-24 21:17:52 +02:00
50c675b4e1 refactor: use node:path, remove experimental strategies 2024-08-24 21:17:39 +02:00
0c8d5edd77 feat: new experimental Strategies export 2024-08-19 13:53:19 +02:00
2e94226b1c feat: new experimental strategies option 2024-08-19 13:52:51 +02:00
258c400ccf chore: new packages directory 2024-08-19 12:50:10 +02:00
fcf24aeb5b 0.8.4 2024-08-18 19:44:01 +02:00
975777fffa feat: don't use random version 2024-08-18 19:43:54 +02:00
8e7bb178f8 chore: change test app to hybrid output 2024-08-18 12:56:37 +02:00
21a45d2610 0.8.3 2024-08-18 12:48:38 +02:00
14ccfd3379 chore: fix readme example 2024-08-18 12:48:33 +02:00
260eac3462 0.8.2 2024-08-18 12:11:31 +02:00
f5f4203c6e fix: catch undefined registrationHooks callbacks 2024-08-18 12:11:23 +02:00
dbcc8e4541 0.8.1 2024-08-18 12:04:50 +02:00
191ba17ff9 chore: update readme for registrationHooks 2024-08-18 12:04:45 +02:00
8841a2898c 0.8.0 2024-08-18 11:42:13 +02:00
bd49663cee feat: expose registration hooks 2024-08-18 11:42:01 +02:00
2581b073d3 0.7.10 2024-08-17 21:18:38 +02:00
13978f8380 chore: move example_sw.js to src 2024-08-17 21:18:30 +02:00
574c14fb13 feat: add routes with ending slash 2024-08-17 21:16:40 +02:00
58433c8371 0.7.9 2024-08-17 20:52:13 +02:00
fb08ebc051 fix: injectedType for __assets 2024-08-17 20:52:08 +02:00
34d3cfb951 0.7.8 2024-08-17 12:49:25 +02:00
b89c5221ab chore: update integration name to @ayco/astro-sw 2024-08-17 12:49:17 +02:00
1b2b48b960 0.7.7 2024-08-17 12:31:10 +02:00
5d29f4d0af feat: show types of options on hover 2024-08-17 12:31:02 +02:00
426ab0a963 chore: add LICENSE 2024-08-17 12:25:29 +02:00
b28b5cc96a 0.7.6 2024-08-17 11:40:34 +02:00
ca5e59ebed feat: better logging 2024-08-17 11:40:30 +02:00
58ef2750f1 0.7.5 2024-08-17 11:38:25 +02:00
2504a24255 feat: better logs 2024-08-17 11:38:20 +02:00
879a12cc95 0.7.4 2024-08-17 11:33:45 +02:00
bd3a1fe84c feat: better logs & helpful messages 2024-08-17 11:33:39 +02:00
1bd968df58 0.7.3 2024-08-17 11:05:53 +02:00
6489efdd1a fix: assetCachePrefix undefined 2024-08-17 11:05:47 +02:00
050d5a7ac9 0.7.2 2024-08-17 11:01:26 +02:00
275d0f1836 chore(deps): move @astrojs/node to devDeps 2024-08-17 11:01:20 +02:00
d7ab0af62d chore(deps): move needed deps to prdDeps 2024-08-17 11:00:21 +02:00
0fde6ce38d 0.7.1 2024-08-17 10:37:00 +02:00
8c053f0d36 chore: update readme APIs and examples 2024-08-17 10:36:52 +02:00
2c72c49fa8 0.7.0 2024-08-17 10:14:48 +02:00
2b94e3c827 feat: inject types for __assets, __version, __prefix 2024-08-17 10:14:39 +02:00
b6e72d3d7e feat: inject type definitions for __assets, __prefix, __version 2024-08-17 09:59:40 +02:00
d5f92e71a2 chore: move sw file to test new config 2024-08-17 09:57:40 +02:00
0f2a41a838 Merge branch 'main' of github.com:ayoayco/astro-sw 2024-08-17 09:41:01 +02:00
dcc7e8ca9f feat: use esbuild to resolve imports & support typescript 2024-08-17 09:40:29 +02:00
2ed8253e33
Update README.md 2024-08-17 08:27:49 +02:00
d82fb3f5ea 0.6.3 2024-08-16 20:55:44 +02:00
89caf380e1 chore: update readme 2024-08-16 20:55:33 +02:00
707985cd01
chore: Update README.md 2024-08-16 19:59:32 +02:00
ee5021d78d
chore: update README.md 2024-08-16 18:25:40 +02:00
48406deafb 0.6.2 2024-08-16 14:16:27 +02:00
0c1895c9d7 feat: use astro integration logger 2024-08-16 14:16:21 +02:00
9b9ba45e45 0.6.1 2024-08-16 13:30:52 +02:00
cfb2d590dd chore: update readme with new APIs 2024-08-16 13:30:45 +02:00
321c762232 0.6.0 2024-08-16 13:26:56 +02:00
c3df8b33da feat: add excludeRoutes option 2024-08-16 13:17:18 +02:00
8e4083cb01 chore: add todo 2024-08-16 08:14:39 +02:00
2bc4ef1423 0.5.1 2024-08-15 21:42:04 +02:00
0e29dfbf15 feat: don't include index.html 2024-08-15 21:42:00 +02:00
93eaec2a38 0.5.0 2024-08-15 21:10:11 +02:00
a04cae6da8 feat: use unjs/pathe & extract public files for caching 2024-08-15 21:09:45 +02:00
9986976b38 0.4.0 2024-08-15 20:51:30 +02:00
9d9ae8e17b feat: implement customRoutes for caching 2024-08-15 20:51:15 +02:00
c17731ecbb chore(dep): update astro & @astrojs/node 2024-08-15 20:45:52 +02:00
802b3dfc64 0.3.1 2024-08-15 19:01:00 +02:00
4dd3941fd5 fix: do not add 404 page to prevent Cache.addAll() error 2024-08-15 19:00:48 +02:00
20bd911262 chore: add public image 2024-08-15 18:46:00 +02:00
12d720177a 0.3.0 2024-08-15 18:23:49 +02:00
e395600420 feat: support static output; remove files from assets 2024-08-15 18:23:43 +02:00
9e1e5f8c3b 0.2.2 2024-08-15 17:22:27 +02:00
0608f9c286 feat: add pages without ending slash for caching 2024-08-15 17:22:19 +02:00
ae645c62a6 0.2.1 2024-08-15 17:12:12 +02:00
f77c417e4d chore: remove console.log 2024-08-15 17:11:58 +02:00
efda4b0013 feat: add pages to assets for caching 2024-08-15 17:11:48 +02:00
d7bb02bbad 0.2.0 2024-08-15 16:43:15 +02:00
43dfab5173 feat: add routes to assets to be cached 2024-08-15 16:43:08 +02:00
bb1e215ed3 0.1.1 2024-08-13 20:41:32 +02:00
a6e8669f9b fix: make assets array unique to prevent cache.addAll error 2024-08-13 20:41:23 +02:00
aeeb2b8b9a chore: update deps 2024-08-13 20:34:26 +02:00
7a05fb56d4 0.1.0 2024-08-13 20:22:13 +02:00
4cddb04e33 feat: auto-registration of service-worker 2024-08-13 20:21:58 +02:00
b87c18d74b 0.0.10 2024-08-08 21:36:07 +02:00
3da39e28b4 chore: update readme 2024-08-08 21:36:01 +02:00
8d847a5769 chore: use local library; fix astro config 2024-08-08 21:28:53 +02:00
ffa756a825 0.0.9 2024-08-08 21:20:33 +02:00
88c1bbfc86 chore: setup test app 2024-08-08 21:20:25 +02:00
8cd30fcaa1 chore: update readme 2024-08-03 22:19:47 +02:00
ab9ba7f5d4 0.0.8 2024-08-03 22:09:15 +02:00
d5748f30bd chore: update readme & package exports 2024-08-03 22:08:55 +02:00
373145a9ed 0.0.7 2024-08-03 11:45:16 +02:00
ee6bacc76a chore: update package repo & homepage 2024-08-03 11:45:11 +02:00
048cc69113 chore: update readme w/ badges 2024-08-03 11:41:35 +02:00
8489d3cc17 chore: remove unneeded var 2024-08-03 11:38:13 +02:00
8ffe6b7fd3 0.0.6 2024-08-03 10:47:01 +02:00
0bdeff20d0 refactor: move declaration inside function 2024-08-03 10:46:54 +02:00
3e31306fc2 chore: update readme 2024-08-03 10:44:21 +02:00
4691740006 0.0.5 2024-08-03 10:40:39 +02:00
d44c39ce59 chore: update readme 2024-08-03 10:40:29 +02:00
fb8a4699cb chore: update comment in example sw 2024-08-03 10:31:50 +02:00
489c458de1 feat: add example sw.js 2024-08-03 10:31:08 +02:00
91781ce2bc 0.0.4 2024-08-03 09:11:50 +02:00
a1d8ba483b feat: fix imported typedef for JSdocs 2024-08-03 09:11:38 +02:00
f801e424a0 0.0.3 2024-08-03 09:02:52 +02:00
8d1cd7ca22 fix: package main export 2024-08-03 09:02:47 +02:00
40 changed files with 5709 additions and 1605 deletions

11
.build.yml Normal file
View file

@ -0,0 +1,11 @@
image: alpine/edge
secrets:
- bbfcb6dc-7c4a-42ee-a11a-022f0339a133
environment:
REPO: astro-sw
GH_USER: ayoayco
tasks:
- push-mirror: |
cd ~/"${REPO}"
git config --global credential.helper store
git push --mirror "https://github.com/${GH_USER}/${REPO}"

1
.gitignore vendored
View file

@ -7,3 +7,4 @@ package-lock.json
*swo
*swp
.eslintcache

1
.husky/pre-commit Normal file
View file

@ -0,0 +1 @@
npm run check

7
.prettierignore Normal file
View file

@ -0,0 +1,7 @@
# someday let's think about formatting html
**/*.html
**/*.md
**/*.css
**/*.yml
**/*.yaml

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Ayo Ayco
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

150
README.md Normal file
View file

@ -0,0 +1,150 @@
# Astro SW
[![Package information: NPM version](https://img.shields.io/npm/v/@ayco/astro-sw)](https://www.npmjs.com/package/@ayco/astro-sw)
[![Package information: NPM license](https://img.shields.io/npm/l/@ayco/astro-sw)](https://www.npmjs.com/package/@ayco/astro-sw)
Use your own authored [service worker](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API) with Astro.
The integration accepts the path to your service worker and automatically injects dynamic variables such as `__assets` generated by Astro for caching.
It works on all Astro output options: `static`, `server`, or `hybrid`, and lets developers retain the flexibility for various [caching strategies](https://developer.chrome.com/docs/workbox/caching-strategies-overview/).
## Background
This integration was originally developed to support the Caching strategy needs of [Cozy](https://cozy.pub) -- the modern reading companion for the Web. You can find [an example service worker in the repository](https://github.com/ayoayco/Cozy/blob/main/src/sw.mjs).
There is a work in progress adding `presets` for using common caching strategies and customizing the behavior of the service worker via config options. This aims to reduce the need for writing the service worker script by hand for most use cases.
Get in touch:
1. Chat via Discord: [Ayo's Projects](https://discord.gg/kkvW7GYNAp)
1. Submit tickets via [SourceHut todo](https://todo.sr.ht/~ayoayco/astro-sw)
1. Start a [GitHub discussion](https://github.com/ayoayco/astro-sw/discussions)
1. Email me: [ayo@ayco.io](mailto:ayo@ayco.io)
## Installation
In your [Astro](https://astro.build) project:
```bash
# if using npm
$ npm i @ayco/astro-sw
# if using pnpm
$ pnpm add @ayco/astro-sw
```
## Minimal Usage
Here's an example `astro.config.mjs` file:
```js
import { defineConfig } from "astro/config";
import serviceWorker from "@ayco/astro-sw";
export default defineConfig({
integrations: [
serviceWorker({
path: "./src/sw.ts",
}),
],
});
```
For more options available, see the [API](#api).
## TypeScript support
We use `esbuild` to resolve service worker `imports` and build TS files! You can customize the build options by providing it to the `esbuild` configuration property.
```js
import { defineConfig } from "astro/config";
import serviceWorker from "@ayco/astro-sw";
export default defineConfig({
integrations: [
serviceWorker({
path: "./src/sw.ts",
esbuild: {
minify: true,
},
}),
],
});
```
## Injected variables
The most important variable your service worker will have access to is `__assets`, which contains all routes and public assets that Astro includes in your build. Additionally, you will also get `__prefix` and `__version` you can use for naming & invalidating your Cache storage (useful for debugging purposes).
## `eslint` globals
Because of the injected variables not being defined in your script, you might get `eslint` errors for the undefined variables when you have the `no-undef` rule. To prevent this, you can use our exported `globals` object in your eslint config as follows:
```js
import astroSwGlobals from "@ayco/astro-sw/globals";
export default [
{
languageOptions: {
globals: {
...astroSwGlobals,
},
},
},
// add more generic rule sets here, such as:
// jsPlugin.configs.recommended,
];
```
## Registration Hooks
Hooks are provided for adding custom logic that triggers in various service worker registration events.
The following properties are available for the `registrationHooks` configuration:
1. `installing` - when the registration is 'installing'
1. `waiting` - when the registration is 'waiting'
1. `active` - when the registration is 'active'
1. `error` - when the registration throws an error
1. `unsupported` - when the service workers are unsupported
1. `afterRegistration` - after the registration succeeds
```js
import { defineConfig } from "astro/config";
import serviceWorker from "@ayco/astro-sw";
export default defineConfig({
integrations: [
serviceWorker({
path: "./src/sw.ts",
registrationHooks: {
afterRegistration: async () => {
const sw = await navigator.serviceWorker.getRegistration();
console.log(">>> registrered", sw);
},
installing: () => console.log("installing..."),
waiting: () => console.log("waiting..."),
active: () => console.log("active..."),
error: (error) => console.error(error),
unsupported: () => console.log(":("),
},
}),
],
});
```
## API
The integration accepts a configuration object with the following properties
| property | type | required? | notes |
| ------------------- | ---------------------------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| path | string | required | path to your _own_ service worker script; no surprises & easy debugging |
| assetCachePrefix | string | optional | cache storage name prefix |
| assetCacheVersionID | string | optional | cache storage name versioning; by default, a random UUID is used |
| customRoutes | string[] | optional | list of custom routes you want to be cached. Beware that non-existent routes that result to HTTP Error404 will cause the service worker to fail |
| excludeRoutes | string[] | optional | list of routes you want to be ignored/removed from assets |
| logAssets | boolean | optional | set to see a list of the assets found; defaults to false |
| esbuild | [BuildOptions](https://esbuild.github.io/api/) | optional | custom build options for your service worker script |
| registrationHooks | object | optional | provide callbacks for various registration events; see section on [Registration Hooks](#registration-hooks) |

39
demo/astro.config.mjs Normal file
View file

@ -0,0 +1,39 @@
// @ts-check
import { defineConfig } from 'astro/config'
import node from '@astrojs/node'
import serviceWorker from '@ayco/astro-sw'
import { deleteOldCaches, staleWhileRevalidate } from '@ayco/astro-sw/presets'
export default defineConfig({
output: 'static',
adapter: node({
mode: 'middleware',
}),
site: 'https://ayo.ayco.io',
integrations: [
serviceWorker({
path: './src/example_sw.js',
presets: [staleWhileRevalidate(), deleteOldCaches()],
customRoutes: [
// '/threads'
],
excludeRoutes: ['/exclude'],
assetCachePrefix: 'hey',
logAssets: true,
esbuild: {
minify: true,
},
registrationHooks: {
installing: () => console.log('>>> installing...'),
waiting: () => console.log('>>> waiting...'),
active: () => console.log('>>> active...'),
error: (error) => console.error('>>> error', error),
afterRegistration: async () => {
const sw = await navigator.serviceWorker.getRegistration()
console.log('>>> registrered', sw)
},
},
}),
],
})

25
demo/package.json Normal file
View file

@ -0,0 +1,25 @@
{
"name": "demo",
"private": true,
"version": "1.0.0",
"main": "index.js",
"scripts": {
"start": "astro dev",
"dev": "astro dev",
"build": "astro build",
"build:preview:static": "astro build && astro preview",
"build:preview": "astro build && node ./server.mjs"
},
"author": "Ayo Ayco",
"license": "MIT",
"description": "",
"devDependencies": {
"astro": "^5.6.1",
"@astrojs/node": "^9.1.3",
"@fastify/middie": "^9.0.3",
"@fastify/static": "^8.1.1",
"astro-eslint-parser": "^1.2.2",
"fastify": "^5.2.2",
"@ayco/astro-sw": "workspace:*"
}
}

BIN
demo/public/Thanos.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

View file

@ -0,0 +1 @@
asset

18
demo/server.mjs Executable file
View file

@ -0,0 +1,18 @@
#!/usr/bin/env node
import Fastify from 'fastify'
import fastifyMiddie from '@fastify/middie'
import fastifyStatic from '@fastify/static'
import { fileURLToPath } from 'node:url'
import { handler as ssrHandler } from './dist/server/entry.mjs'
const app = Fastify({ logger: true })
await app
.register(fastifyStatic, {
root: fileURLToPath(new URL('./dist/client', import.meta.url)),
})
.register(fastifyMiddie)
app.use(ssrHandler)
app.listen({ port: 4321 })

View file

@ -0,0 +1,66 @@
---
title: Building a Cozy Web
description: Let us build the web we want!
pubDate: 'Aug 14 2024'
heroImage: '/cozy.jpg'
---
> This was originally posted on [Ayo's Blog](https://ayos.blog/building-a-cozy-web).
Have you ever clicked a link to an article, all hyped up to read the content, only to be slapped in the face with popups over popups of requests to subscribe and asking consent to track you with cookies?
Do you sometimes wish you can have a consistent experience when opening articles... a place to save all your favorites, and possibly get helpful insights?
Ah, well you're not alone. 🤣
This is exactly why I started [**Cozy** 🧸](https://cozy.ayco.io/).
It's a simple web page that can make any web page content-focused! 🎉
It uses a library called [@extractus/article-extractor](https://www.npmjs.com/package/@extractus/article-extractor) to fetch and extract just the content.
Then with [Astro](https://astro.build), we can server-side render the page so your browser only gets clean HTML!
No nonsense. No headaches.
The project and the road map for features are all public on my [GitHub](https://github.com/ayoayco/cozy-reader)
## Cozy Features
Right now, it successfully extracts the content and delivers a clean page to your browser.
I'm working toward bringing the following in the coming weeks:
1. Save favorites to a library
2. Offline access
3. Smart Insights about the article
4. Easier usage (browser extensions or apps?)
## Coziest Usage
The most convenient way to use it right now is through what we call a browser bookmarklet.
Basically you can have a button there beside your other bookmarks that will open the current page in Cozy.
You can create this new bookmark titled 'Get cozy!' and put the following as value for the URL:
```js
javascript:(function(){ window.open('https://cozy.ayco.io/?url=%27 + window.location.href, %27_self%27); })();
```
This is possible on all major browsers, including Safari on iOS (where I personally use this often). Some screenshots:
| Firefox | Chrome |
| --- | --- |
| ![Screenshot from 2023-05-13 08-31-41](https://github.com/ayoayco/cozy-reader/assets/4262489/9b296d4f-2722-483a-bbc2-431c6b2ae996) | ![Screenshot from 2023-05-12 23-32-08](https://github.com/ayoayco/cozy-reader/assets/4262489/144b74f8-3949-46b9-849c-351e4af0ac12) |
## Join the Project!
I'm sure this looks very simple, but I think this is the most exciting hobby project I've started yet.
There's a lot that happened and a lot of problems could have been avoided if people were equipped to assess the content they find online.
I think there's lots of good a simple tool could bring if it allows users to cut-through all the distractions and are presented with unbiased and accurate information.
This project is a groundwork for this experience.
Let's build the web we want! 🧸

View file

@ -0,0 +1,16 @@
import { defineCollection, z } from 'astro:content'
const blog = defineCollection({
type: 'content',
// Type-check frontmatter using a schema
schema: z.object({
title: z.string(),
description: z.string(),
// Transform string to Date object
pubDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(),
heroImage: z.string().optional(),
}),
})
export const collections = { blog }

2
demo/src/env.d.ts vendored Normal file
View file

@ -0,0 +1,2 @@
/// <reference path="../.astro/types.d.ts" />
/// <reference types="astro/client" />

91
demo/src/example_sw.js Normal file
View file

@ -0,0 +1,91 @@
/**
* Note: @ayco/astro-sw integration injects variables `__prefix`, `__version`, & `__assets`
* -- find usage in package readme; `astro.config.mjs` integrations
* @see https://ayco.io/n/@ayco/astro-sw
*/
const cacheName = `${__prefix ?? 'app'}-v${__version ?? '000'}`
const addResourcesToCache = async (resources) => {
const cache = await caches.open(cacheName)
console.log('adding resources to cache...', resources)
await cache.addAll(resources)
}
console.log('test log', { hello: 'world' })
const putInCache = async (request, response) => {
const cache = await caches.open(cacheName)
console.log('adding one response to cache...', request)
await cache.put(request, response)
}
const cacheFirst = async ({ request, preloadResponsePromise, fallbackUrl }) => {
// First try to get the resource from the cache
const responseFromCache = await caches.match(request)
if (responseFromCache) {
return responseFromCache
}
// Next try to use the preloaded response, if it's there
// NOTE: Chrome throws errors regarding preloadResponse, see:
// https://bugs.chromium.org/p/chromium/issues/detail?id=1420515
// https://github.com/mdn/dom-examples/issues/145
// To avoid those errors, remove or comment out this block of preloadResponse
// code along with enableNavigationPreload() and the "activate" listener.
const preloadResponse = await preloadResponsePromise
if (preloadResponse) {
console.info('using preload response', preloadResponse)
putInCache(request, preloadResponse.clone())
return preloadResponse
}
// Next try to get the resource from the network
try {
const responseFromNetwork = await fetch(request.clone())
// response may be used only once
// we need to save clone to put one copy in cache
// and serve second one
putInCache(request, responseFromNetwork.clone())
return responseFromNetwork
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (error) {
const fallbackResponse = await caches.match(fallbackUrl)
if (fallbackResponse) {
return fallbackResponse
}
// when even the fallback response is not available,
// there is nothing we can do, but we must always
// return a Response object
return new Response('Network error happened', {
status: 408,
headers: { 'Content-Type': 'text/plain' },
})
}
}
const enableNavigationPreload = async () => {
if (self.registration.navigationPreload) {
// Enable navigation preloads!
await self.registration.navigationPreload.enable()
}
}
self.addEventListener('activate', (event) => {
console.log('activating...', event)
event.waitUntil(enableNavigationPreload())
})
self.addEventListener('install', (event) => {
console.log('installing...', event)
event.waitUntil(addResourcesToCache(__assets ?? []))
})
self.addEventListener('fetch', (event) => {
console.log('fetch happened', event.request)
event.respondWith(
cacheFirst({
request: event.request,
preloadResponsePromise: event.preloadResponse,
fallbackUrl: './',
})
)
})

5
demo/src/pages/404.astro Normal file
View file

@ -0,0 +1,5 @@
---
---
404

View file

@ -0,0 +1,17 @@
---
import { type CollectionEntry, getCollection } from 'astro:content'
export async function getStaticPaths() {
const posts = await getCollection('blog')
return posts.map((post) => ({
params: { slug: post.slug },
props: post,
}))
}
type Props = CollectionEntry<'blog'>
const post = Astro.props
const { Content } = await post.render()
---
<Content />

View file

@ -0,0 +1,7 @@
---
---
blog index
<a href="/blog/building-a-cozy-web">post</a>

View file

@ -0,0 +1,5 @@
---
---
exclude

View file

@ -0,0 +1,14 @@
---
export const prerender = false
---
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Hello</title>
</head>
<body> Hello </body>
</html>
<!-- <Fragment set:html={content} /> -->

5
demo/tsconfig.json Normal file
View file

@ -0,0 +1,5 @@
{
"extends": "astro/tsconfigs/strict",
"include": [".astro/types.d.ts", "**/*"],
"exclude": ["dist"]
}

41
eslint.config.mjs Normal file
View file

@ -0,0 +1,41 @@
import globals from 'globals'
import eslintPluginAstro from 'eslint-plugin-astro'
import jsPlugin from '@eslint/js'
import tseslint from 'typescript-eslint'
import astroSwGlobals from '@ayco/astro-sw/globals'
import astroParser from 'astro-eslint-parser'
export default [
{
languageOptions: {
globals: {
...globals.browser,
...globals.node,
...astroSwGlobals,
},
},
},
// add more generic rule sets here, such as:
jsPlugin.configs.recommended,
...tseslint.configs.recommended,
...eslintPluginAstro.configs['recommended'],
...eslintPluginAstro.configs['jsx-a11y-recommended'],
{
ignores: [
'**/dist/*',
'**/.output/*',
'**/.astro/*',
'**/node_modules/*',
'**/env.d.ts',
],
},
{
files: ['**/*.astro'],
languageOptions: {
parser: astroParser,
parserOptions: {
parser: tseslint.parser,
},
},
},
]

View file

@ -1,64 +0,0 @@
import { AstroIntegration } from 'astro';
import { readFile, writeFile } from 'node:fs/promises';
import { fileURLToPath } from 'node:url';
import path from 'node:path';
import { randomUUID } from "node:crypto";
/**
* @type {Array<string>}
*/
let assets = [];
/**
* @typedef {{
* assetCachePrefix?: string,
* assetCacheVersionID?: string,
* path: string,
* }} ServiceWorkerConfig
*/
const plugin_dir = path.resolve(path.dirname('.'));
/**
*
* @param {ServiceWorkerConfig} config
* @returns {AstroIntegration}
*/
export default function serviceWorker(config) {
let {
assetCachePrefix,
assetCacheVersionID = randomUUID(),
path: serviceWorkerPath
} = config;
console.log('[astro-sw] dir', plugin_dir)
return {
'name': 'astro-sw',
'hooks': {
'astro:build:ssr': ({ manifest }) => {
assets = manifest.assets.filter(ass => !ass.includes('sw.js'))
},
'astro:build:done': async ({ dir }) => {
const outFile = fileURLToPath(new URL('./sw.js', dir));
let originalScript;
try {
const __dirname = path.resolve(path.dirname('.'));
const swPath = path.join(__dirname, serviceWorkerPath ?? '');
console.log('[astro-sw] Using service worker:', swPath);
originalScript = await readFile(swPath);
} catch {
throw Error('[astro-sw] ERROR: service worker script not found!')
}
const assetsDeclaration = `const __assets = ${JSON.stringify(assets)};\n`;
const versionDeclaration = `const __version = ${JSON.stringify(assetCacheVersionID)};\n`;
const prefixDeclaration = `const __prefix = ${JSON.stringify(assetCachePrefix)};\n`;
await writeFile(
outFile,
assetsDeclaration + versionDeclaration + prefixDeclaration + originalScript
);
}
}
}
};

View file

@ -1,22 +1,37 @@
{
"name": "@ayco/astro-sw",
"version": "0.0.2",
"description": "Simple Astro integration to use your own authored service-worker",
"main": "index.ts",
"type": "module",
"engines": {
"node": ">=18.0.0"
},
"name": "astro-sw-monorepo",
"version": "1.0.0",
"private": true,
"description": "> [!NOTE] > This project moved to [SourceHut](https://git.sr.ht/~ayoayco/astro-sw).",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"format": "prettier . --write",
"lint": "eslint . --config eslint.config.mjs --cache",
"check": "npm run format && npm run lint",
"prepare": "husky",
"test": "pnpm -F @ayco/astro-sw test",
"build": "pnpm -F @ayco/astro-sw build",
"demo": "pnpm run build && pnpm -F demo build:preview"
},
"repository": {
"type": "git",
"url": "git+ssh://git@git.sr.ht/~ayoayco/astro-sw.git"
},
"keywords": [
"withastro",
"perf"
],
"author": "Ayo Ayco",
"license": "MIT",
"homepage": "https://ayco.io/n/@ayco/astro-sw#readme",
"devDependencies": {
"astro": "^4.13.1"
"@ayco/astro-sw": "workspace:*",
"@eslint/js": "^9.24.0",
"eslint": "^9.24.0",
"eslint-plugin-astro": "^1.3.1",
"eslint-plugin-jsx-a11y": "^6.10.2",
"globals": "^16.0.0",
"husky": "^9.1.7",
"prettier": "^3.5.3",
"prettier-plugin-astro": "^0.14.1",
"tsup": "^8.4.0",
"typescript": "^5.8.3",
"typescript-eslint": "^8.29.0",
"vitest": "^3.1.1"
}
}

58
package/package.json Normal file
View file

@ -0,0 +1,58 @@
{
"name": "@ayco/astro-sw",
"version": "0.9.0",
"description": "Use your own authored service worker with Astro",
"homepage": "https://ayco.io/n/@ayco/astro-sw",
"repository": {
"type": "git",
"url": "https://git.sr.ht/~ayoayco/astro-sw"
},
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"./globals": {
"types": "./dist/eslint/globals.d.ts",
"default": "./dist/eslint/globals.js"
},
"./presets": {
"types": "./dist/presets/index.d.ts",
"default": "./dist/presets/index.js"
},
"./presets/*": {
"types": "./dist/presets/*/index.d.ts",
"default": "./dist/presets/*/index.js"
}
},
"files": [
"dist"
],
"main": "./astro-sw.js",
"type": "module",
"engines": {
"node": ">=18.0.0"
},
"scripts": {
"build": "tsup src/index.ts src/presets/index.ts src/presets/**/index.ts src/eslint/globals.ts --format esm --dts --clean",
"test": "vitest run",
"version:patch": "npm version patch",
"version:minor": "npm version minor",
"version:major": "npm version major"
},
"keywords": [
"withastro",
"perf"
],
"author": "Ayo Ayco",
"license": "MIT",
"dependencies": {
"esbuild": "^0.25.2"
},
"peerDependencies": {
"astro": "^5.6"
},
"devDependencies": {
"@types/node": "^22.14.0"
}
}

View file

@ -0,0 +1,5 @@
export default {
__prefix: false,
__version: false,
__assets: false,
}

232
package/src/index.ts Normal file
View file

@ -0,0 +1,232 @@
/**
* @license MIT <https://opensource.org/licenses/MIT>
* @author Ayo Ayco <https://ayo.ayco.io>
*/
import { readFile, writeFile, readdir, unlink } from 'node:fs/promises'
import { fileURLToPath } from 'node:url'
import { resolve, dirname, join } from 'node:path'
import { build } from 'esbuild'
import type { AstroServiceWorkerConfig } from './types'
import type { AstroIntegration } from 'astro'
const ASTROSW = '@ayco/astro-sw'
/**
* TODO: update JSDoc
* Accepts configuration options with service worker path
* and injects needed variables such as `__assets` generated by Astro
*/
export default function serviceWorker(
// TODO handle options undefined
options?: AstroServiceWorkerConfig
): AstroIntegration {
const {
presets,
assetCachePrefix = ASTROSW,
assetCacheVersionID = '0',
path: serviceWorkerPath = undefined,
customRoutes = [],
excludeRoutes = [],
logAssets = false,
esbuild = {},
registrationHooks = {},
} = options ?? {}
const {
installing: installingFn = () => {},
waiting: waitingFn = () => {},
active: activeFn = () => {},
error: errorFn = () => {},
unsupported: unsupportedFn = () => {},
afterRegistration: afterRegistrationFn = () => {},
} = registrationHooks
// TODO use presets
console.log(presets)
/**
* @type {Array<string>}
*/
let manifestAssets: string[] = []
const registrationScript = `const registerSW = async () => {
if ("serviceWorker" in navigator) {
try {
const registration = await navigator.serviceWorker.register("/sw.js", {
scope: "/",
});
if (registration.installing) {
(${installingFn.toString()})();
} else if (registration.waiting) {
(${waitingFn.toString()})();
} else if (registration.active) {
(${activeFn.toString()})();
}
(${afterRegistrationFn.toString()})();
} catch (error) {
(${errorFn.toString()})(error);
}
} else {
(${unsupportedFn.toString()})();
}
}
registerSW();`
// let output = 'static'
const __dirname = resolve(dirname('.'))
return {
name: ASTROSW,
hooks: {
'astro:config:setup': async ({ injectScript, command, logger }) => {
if (!serviceWorkerPath || serviceWorkerPath === '') {
// REQUIRED OPTION IS MISSING
logger.error('Missing required path to service worker script')
}
// const transformedScript=await transform(registrationScript)
// output = _config.output
if (command === 'build') {
injectScript('page', registrationScript)
}
},
'astro:config:done': async ({ injectTypes }) => {
const injectedTypes = `
declare const __assets: string[];
declare const __version: string;
declare const __prefix: string;`
injectTypes({ filename: 'caching.d.ts', content: injectedTypes })
},
'astro:build:ssr': ({ manifest }) => {
manifestAssets = manifest.assets
},
'astro:build:done': async ({
dir,
assets: astroAssets,
pages,
logger,
}) => {
const outfile = fileURLToPath(new URL('./sw.js', dir))
const swPath =
serviceWorkerPath && serviceWorkerPath !== ''
? join(__dirname, serviceWorkerPath)
: undefined
let originalScript
const _publicFiles = (
(await readdir(dir, { withFileTypes: true })) ?? []
)
.filter((dirent) => dirent.isFile())
.map((dirent) => `/${dirent.name}`)
const _assets = Array.from(astroAssets.keys())
.filter((key) => !key.includes('[...slug]'))
.flatMap((key) => (key === '/' ? key : [key, `${key}/`]))
const _pages =
pages
.filter(({ pathname }) => pathname !== '')
.map(({ pathname }) => `/${pathname}`) ?? []
const _pagesWithoutEndSlash =
pages
.filter(({ pathname }) => pathname !== '')
.map(({ pathname }) => {
const lastChar = pathname.slice(-1)
const len = pathname.length
return lastChar === '/'
? `/${pathname.slice(0, len - 1)}`
: `/${pathname}`
})
.filter((pathname) => pathname !== '') ?? []
const _excludeRoutes = [
...excludeRoutes,
...excludeRoutes.map((route) => `${route}/`),
]
const __assets = [
...new Set([
...manifestAssets,
..._assets,
..._pages,
..._pagesWithoutEndSlash,
...customRoutes,
..._publicFiles,
]),
].filter(
(asset) =>
!!asset &&
asset !== '' &&
!asset.includes('404') &&
!asset.includes('index.html') &&
!_excludeRoutes.includes(asset)
)
if (logAssets) {
logger.info(
`${__assets.length} assets for caching: \n ▶ ${__assets.toString().replaceAll(',', '\n ▶ ')}\n`
)
} else {
logger.info(`${__assets.length} assets for caching.`)
}
try {
logger.info(`Using service worker in path: ${swPath}`)
// @ts-expect-error undefined error is caught via try-catch
originalScript = await readFile(swPath)
} catch (err: unknown) {
logger.error(JSON.stringify(err))
if (!swPath) {
logger.error(`
[${ASTROSW}] ERR: The 'path' option is required!
[${ASTROSW}] INFO: Please see service worker options in https://ayco.io/gh/astro-sw#readme
`)
}
}
const assetsDeclaration = `const __assets = ${JSON.stringify(__assets)};\n`
const versionDeclaration = `const __version = ${JSON.stringify(assetCacheVersionID)};\n`
const prefixDeclaration = `const __prefix = ${JSON.stringify(assetCachePrefix)};\n`
const tempFile = `${swPath}.tmp.ts`
try {
await writeFile(
tempFile,
assetsDeclaration +
versionDeclaration +
prefixDeclaration +
originalScript,
{ flag: 'w+' }
)
} catch (err) {
logger.error(JSON.stringify(err))
}
try {
await build({
bundle: true,
...esbuild,
outfile,
platform: 'browser',
entryPoints: [tempFile],
})
} catch (err) {
logger.error(JSON.stringify(err))
}
// remove temp file
try {
await unlink(tempFile)
} catch (err) {
logger.error(JSON.stringify(err))
}
},
},
}
}

View file

@ -0,0 +1,16 @@
import { AstroServiceWorkerPreset } from '../../types'
export const activateFn: AstroServiceWorkerPreset['activate'] = async ({
cacheName,
}) => {
const allowCacheNames = [cacheName]
const allCaches = await caches.keys()
allCaches.forEach((key) => {
if (!allowCacheNames.includes(key)) {
console.info('Deleting old cache', key)
caches.delete(key)
}
})
}
export default activateFn

View file

@ -0,0 +1,8 @@
import { AstroServiceWorkerPreset } from '../../types'
import activate from './activate'
export const deleteOldCaches: () => AstroServiceWorkerPreset = () => ({
activate,
})
export default deleteOldCaches

View file

@ -0,0 +1,2 @@
export { staleWhileRevalidate } from './stale-while-revalidate'
export { deleteOldCaches } from './delete-old-caches'

View file

@ -0,0 +1,93 @@
import { AstroServiceWorkerPreset } from '../../types'
export const fetchFn: AstroServiceWorkerPreset['fetch'] = ({
event,
cacheName,
}) => {
console.info('fetch happened', { data: event })
event.respondWith(
cacheAndRevalidate(
{
request: event.request,
fallbackUrl: './',
},
cacheName
)
)
}
export default fetchFn
// @ts-expect-error TODO fix types
const putInCache = async (request, response, cacheName) => {
const cache = await caches.open(cacheName)
if (response.ok) {
console.info('adding one response to cache...', request.url)
// if exists, replace
cache.keys().then((keys) => {
if (keys.includes(request)) {
cache.delete(request)
}
})
cache.put(request, response)
}
}
const cacheAndRevalidate = async (
// @ts-expect-error TODO fix types
{ request, fallbackUrl },
cacheName: string
) => {
const cache = await caches.open(cacheName)
// Try get the resource from the cache
const responseFromCache = await cache.match(request)
if (responseFromCache) {
console.info('using cached response...', responseFromCache.url)
// get network response for revalidation of cached assets
fetch(request.clone())
.then((responseFromNetwork) => {
if (responseFromNetwork) {
console.info('fetched updated resource...', responseFromNetwork.url)
putInCache(request, responseFromNetwork.clone(), cacheName)
}
})
.catch((error) => {
console.info('failed to fetch updated resource', error)
})
return responseFromCache
}
try {
// Try to get the resource from the network for 5 seconds
const responseFromNetwork = await fetch(request.clone())
// response may be used only once
// we need to save clone to put one copy in cache
// and serve second one
putInCache(request, responseFromNetwork.clone(), cacheName)
console.info('using network response', responseFromNetwork.url)
return responseFromNetwork
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (error) {
// Try the fallback
const fallbackResponse = await cache.match(fallbackUrl)
if (fallbackResponse) {
console.info('using fallback cached response...', fallbackResponse.url)
return fallbackResponse
}
// when even the fallback response is not available,
// there is nothing we can do, but we must always
// return a Response object
return new Response('Network error happened', {
status: 408,
headers: { 'Content-Type': 'text/plain' },
})
}
}

View file

@ -0,0 +1,14 @@
/**
* preset for stale-while-revalidate caching strategy
*/
import { AstroServiceWorkerPreset } from '../../types'
import install from './install'
import fetch from './fetch'
export const staleWhileRevalidate: () => AstroServiceWorkerPreset = () => ({
install,
fetch,
})
export default staleWhileRevalidate

View file

@ -0,0 +1,33 @@
import { AstroServiceWorkerPreset } from '../../types'
declare const self: ServiceWorkerGlobalScope
export const installFn: AstroServiceWorkerPreset['install'] = ({
event,
routes,
cacheName,
}) => {
console.info('installing service worker...')
self.skipWaiting() // go straight to activate
event.waitUntil(addResourcesToCache(routes ?? [], cacheName))
}
// @ts-expect-error TODO fix types
const addResourcesToCache = async (resources, cacheName: string) => {
const cache = await caches.open(cacheName)
console.info('adding resources to cache...', resources)
try {
await cache.addAll(resources)
} catch (error) {
console.error(
'failed to add resources to cache; make sure requests exists and that there are no duplicates',
{
resources,
error,
}
)
}
}
export default installFn

38
package/src/types.ts Normal file
View file

@ -0,0 +1,38 @@
import type { BuildOptions } from 'esbuild'
export type AstroServiceWorkerPreset = {
activate?: (options: { event: ExtendableEvent; cacheName: string }) => void
install?: (options: {
event: ExtendableEvent
routes: string[]
cacheName: string
}) => void
fetch?: (options: { event: FetchEvent; cacheName: string }) => void
}
export type AstroServiceWorkerConfig = {
path?: string
presets?: AstroServiceWorkerPreset[]
assetCachePrefix?: string
assetCacheVersionID?: string
customRoutes?: string[]
excludeRoutes?: string[]
logAssets?: true
esbuild?: BuildOptions
registrationHooks?: {
installing?: () => void
waiting?: () => void
active?: () => void
error?: (error: Error) => void
unsupported?: () => void
afterRegistration?: () => void
}
experimental?: {
strategy?: {
fetchFn: () => void
installFn: () => void
activateFn: () => void
waitFn: () => void
}
}
}

View file

@ -0,0 +1,6 @@
import { expect } from 'vitest'
import { test } from 'vitest'
test('astro-sw', () => {
expect(true).toBeTruthy()
})

File diff suppressed because it is too large Load diff

3
pnpm-workspace.yaml Normal file
View file

@ -0,0 +1,3 @@
packages:
- "package"
- "demo"

21
prettier.config.mjs Normal file
View file

@ -0,0 +1,21 @@
/**
* @see https://prettier.io/docs/en/configuration.html
* @type {import("prettier").Config}
*/
const config = {
trailingComma: 'es5',
tabWidth: 2,
semi: false,
singleQuote: true,
plugins: ['prettier-plugin-astro'],
overrides: [
{
files: '*.astro',
options: {
parser: 'astro',
},
},
],
}
export default config

125
sample-sw.js Normal file
View file

@ -0,0 +1,125 @@
/**
* Note: @ayco/astro-sw integration injects variables `__prefix`, `__version`, & `__assets`
* -- find usage in `astro.config.mjs` integrations
* @see https://ayco.io/n/@ayco/astro-sw
*/
const cacheName = `${__prefix ?? 'app'}-v${__version ?? '000'}`
const cleanOldCaches = async () => {
const allowCacheNames = ['cozy-reader', cacheName]
const allCaches = await caches.keys()
allCaches.forEach((key) => {
if (!allowCacheNames.includes(key)) {
console.info('Deleting old cache', key)
caches.delete(key)
}
})
}
const addResourcesToCache = async (resources) => {
const cache = await caches.open(cacheName)
console.info('adding resources to cache...', resources)
try {
await cache.addAll(resources)
} catch (error) {
console.error(
'failed to add resources to cache; make sure requests exists and that there are no duplicates',
{
resources,
error,
}
)
}
}
const putInCache = async (request, response) => {
const cache = await caches.open(cacheName)
if (response.ok) {
console.info('adding one response to cache...', request.url)
// if exists, replace
cache.keys().then((keys) => {
if (keys.includes(request)) {
cache.delete(request)
}
})
cache.put(request, response)
}
}
const cacheAndRevalidate = async ({ request, fallbackUrl }) => {
const cache = await caches.open(cacheName)
// Try get the resource from the cache
const responseFromCache = await cache.match(request)
if (responseFromCache) {
console.info('using cached response...', responseFromCache.url)
// get network response for revalidation of cached assets
fetch(request.clone())
.then((responseFromNetwork) => {
if (responseFromNetwork) {
console.info('fetched updated resource...', responseFromNetwork.url)
putInCache(request, responseFromNetwork.clone())
}
})
.catch((error) => {
console.info('failed to fetch updated resource', error)
})
return responseFromCache
}
try {
// Try to get the resource from the network for 5 seconds
const responseFromNetwork = await fetch(request.clone())
// response may be used only once
// we need to save clone to put one copy in cache
// and serve second one
putInCache(request, responseFromNetwork.clone())
console.info('using network response', responseFromNetwork.url)
return responseFromNetwork
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (error) {
// Try the fallback
const fallbackResponse = await cache.match(fallbackUrl)
if (fallbackResponse) {
console.info('using fallback cached response...', fallbackResponse.url)
return fallbackResponse
}
// when even the fallback response is not available,
// there is nothing we can do, but we must always
// return a Response object
return new Response('Network error happened', {
status: 408,
headers: { 'Content-Type': 'text/plain' },
})
}
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
self.addEventListener('activate', (event) => {
console.info('activating service worker...')
cleanOldCaches()
})
self.addEventListener('install', (event) => {
console.info('installing service worker...')
self.skipWaiting() // go straight to activate
event.waitUntil(addResourcesToCache(__assets ?? []))
})
self.addEventListener('fetch', (event) => {
console.info('fetch happened', { data: event })
event.respondWith(
cacheAndRevalidate({
request: event.request,
fallbackUrl: './',
})
)
})

12
tsconfig.json Normal file
View file

@ -0,0 +1,12 @@
{
"compilerOptions": {
"target": "ESNext" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
"module": "ESNext" /* Specify what module code is generated. */,
"moduleResolution": "bundler" /* Specify how TypeScript looks up a file from a given module specifier. */,
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
"strict": true /* Enable all strict type-checking options. */,
"lib": ["WebWorker", "ES2021.String"]
},
"exclude": ["./dist/**/*"]
}