diff --git a/demo-static/astro.config.mjs b/demo-static/astro.config.mjs new file mode 100644 index 0000000..98d511d --- /dev/null +++ b/demo-static/astro.config.mjs @@ -0,0 +1,36 @@ +// @ts-check + +import { defineConfig } from 'astro/config' +import serviceWorker from '@ayco/astro-sw' +// import { deleteOldCaches, staleWhileRevalidate } from '@ayco/astro-sw/presets' + +import * as pkg from './package.json' + +export default defineConfig({ + output: 'static', + site: 'https://ayo.ayco.io', + integrations: [ + serviceWorker({ + path: './src/sw.ts', + assetCachePrefix: 'AstroSWTest', + assetCacheVersionID: pkg.version, + // presets: [staleWhileRevalidate(), deleteOldCaches()], + exclude: ['/exclude'], + // include: ['/components/web-component.js'], + 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) + }, + }, + }), + ], +}) diff --git a/demo-static/package.json b/demo-static/package.json new file mode 100644 index 0000000..8a2a31f --- /dev/null +++ b/demo-static/package.json @@ -0,0 +1,24 @@ +{ + "name": "demo-static", + "private": true, + "version": "1.0.3", + "main": "index.js", + "scripts": { + "start": "astro dev", + "dev": "astro dev", + "build": "astro build", + "build:preview:static": "astro build && astro preview", + "build:preview": "astro build && astro preview" + }, + "author": "Ayo Ayco", + "license": "MIT", + "description": "", + "devDependencies": { + "@astrojs/node": "^10.0.4", + "@ayco/astro-sw": "workspace:*", + "@fastify/middie": "^9.3.1", + "@fastify/static": "^9.0.0", + "astro": "^6.1.3", + "fastify": "^5.8.4" + } +} diff --git a/demo-static/public/Thanos.jpg b/demo-static/public/Thanos.jpg new file mode 100644 index 0000000..e6b48fe Binary files /dev/null and b/demo-static/public/Thanos.jpg differ diff --git a/demo-static/public/components/web-component.js b/demo-static/public/components/web-component.js new file mode 100644 index 0000000..cb5d505 --- /dev/null +++ b/demo-static/public/components/web-component.js @@ -0,0 +1,14 @@ +function register(){ + if ('customElements' in window) + window.customElements.define('web-component', WebComponent) +} + +class WebComponent extends HTMLElement { + connectedCallback() { + console.log('hello') + } +} + +register() + +export default WebComponent \ No newline at end of file diff --git a/demo-static/public/favicon.ico b/demo-static/public/favicon.ico new file mode 100644 index 0000000..3afdf7c Binary files /dev/null and b/demo-static/public/favicon.ico differ diff --git a/demo-static/public/sample.asset.txt b/demo-static/public/sample.asset.txt new file mode 100644 index 0000000..a95e94f --- /dev/null +++ b/demo-static/public/sample.asset.txt @@ -0,0 +1 @@ +asset \ No newline at end of file diff --git a/demo-static/src/env.d.ts b/demo-static/src/env.d.ts new file mode 100644 index 0000000..acef35f --- /dev/null +++ b/demo-static/src/env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/demo-static/src/pages/404.astro b/demo-static/src/pages/404.astro new file mode 100644 index 0000000..c08259f --- /dev/null +++ b/demo-static/src/pages/404.astro @@ -0,0 +1,5 @@ +--- + +--- + +404 diff --git a/demo-static/src/pages/blog/[...slug].astro b/demo-static/src/pages/blog/[...slug].astro new file mode 100644 index 0000000..e164c8c --- /dev/null +++ b/demo-static/src/pages/blog/[...slug].astro @@ -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() +--- + + diff --git a/demo-static/src/pages/blog/index.astro b/demo-static/src/pages/blog/index.astro new file mode 100644 index 0000000..d41545b --- /dev/null +++ b/demo-static/src/pages/blog/index.astro @@ -0,0 +1,7 @@ +--- + +--- + +blog index + +post diff --git a/demo-static/src/pages/exclude.astro b/demo-static/src/pages/exclude.astro new file mode 100644 index 0000000..1b491ff --- /dev/null +++ b/demo-static/src/pages/exclude.astro @@ -0,0 +1,5 @@ +--- + +--- + +exclude diff --git a/demo-static/src/pages/index.astro b/demo-static/src/pages/index.astro new file mode 100644 index 0000000..93d3550 --- /dev/null +++ b/demo-static/src/pages/index.astro @@ -0,0 +1,15 @@ +--- +// export const prerender = false +--- + + + + + + + Hello + + + Hello + + diff --git a/demo-static/src/sw.ts b/demo-static/src/sw.ts new file mode 100644 index 0000000..028d25d --- /dev/null +++ b/demo-static/src/sw.ts @@ -0,0 +1,135 @@ +/** + * 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 forceLogging = true + +/** + * Cleans up old service worker caches by deleting any cache that doesn't match the current cache name. + * This ensures only the current version of the application's cache is retained. + * @async + * @function cleanOldCaches + * @returns {Promise} A promise that resolves when old caches have been deleted + */ +const cleanOldCaches = async () => { + const allowCacheNames = [cacheName] + const allCaches = await caches.keys() + allCaches.forEach((key) => { + if (!allowCacheNames.includes(key)) { + console.info('Deleting old cache', key) + caches + .delete(key) + .then(() => { + console.info('Successfully deleted cache:', key) + }) + .catch((error) => { + console.warn('Failed to delete old cache:', key, error) + }) + } + }) +} + +/** + * Adds specified resources to the service worker cache. + * This function is used to cache static assets for offline access. + * @async + * @function addResourcesToCache + * @param {Array} resources - An array of URLs representing the resources to be cached. + * @returns {Promise} A promise that resolves when all resources have been successfully added to the cache. + */ +const addResourcesToCache = async (resources) => { + const cache = await caches.open(cacheName) + console.info('adding resources to cache...', { + force: !!forceLogging, + context: 'ayco-sw', + data: 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', + error + ) + } +} + +/** + * Puts a response in the cache. + * @async + * @function putInCache + * @param {Request} request - The request to be cached. + * @param {Response} response - The response to be cached. + * @returns {Promise} A promise that resolves when the response has been added to the cache. + */ +const putInCache = async (request, response) => { + const cache = await caches.open(cacheName) + + if (response.ok) { + console.info('adding one response to cache...', request.url) + cache.put(request, response) + } +} + +const networkFirst = async ({ request, fallbackUrl }) => { + const cache = await caches.open(cacheName) + + try { + // Try to get the resource from the network for 5 seconds + const responseFromNetwork = await fetch(request.clone()) + 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 get the resource from the cache + const responseFromCache = await cache.match(request) + if (responseFromCache) { + console.info('using cached response...', responseFromCache.url) + return responseFromCache + } + + // If fallback is provided, try to use it, otherwise return error + if (fallbackUrl) { + 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( + networkFirst({ + request: event.request, + fallbackUrl: './', + }) + ) +}) \ No newline at end of file diff --git a/demo-static/tsconfig.json b/demo-static/tsconfig.json new file mode 100644 index 0000000..8bf91d3 --- /dev/null +++ b/demo-static/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "astro/tsconfigs/strict", + "include": [".astro/types.d.ts", "**/*"], + "exclude": ["dist"] +}