mirror of
https://github.com/jcreek/LivingDexTracker.git
synced 2026-07-12 18:43:45 +00:00
chore(*): Add workbox service worker
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
import process from 'node:process'
|
||||
import AdapterNode from '@sveltejs/adapter-node';
|
||||
import AdpaterStatic from '@sveltejs/adapter-static';
|
||||
|
||||
export const nodeAdapter = process.env.NODE_ADAPTER === 'true'
|
||||
|
||||
export const adapter = nodeAdapter ? AdapterNode() : AdpaterStatic()
|
||||
@@ -0,0 +1,39 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
|
||||
test('Test offline and trailing slashes', async ({ browser }) => {
|
||||
// test offline + trailing slashes routes
|
||||
const context = await browser.newContext();
|
||||
const offlinePage = await context.newPage();
|
||||
await offlinePage.goto('/');
|
||||
const offlineSwURL = await offlinePage.evaluate(async () => {
|
||||
const registration = await Promise.race([
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
navigator.serviceWorker.ready,
|
||||
new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error('Service worker registration failed: time out')), 10000)
|
||||
)
|
||||
]);
|
||||
// @ts-expect-error registration is of type unknown
|
||||
return registration.active?.scriptURL;
|
||||
});
|
||||
const offlineSwName = 'sw.js';
|
||||
expect(offlineSwURL).toBe(`http://localhost:4173/${offlineSwName}`);
|
||||
await context.setOffline(true);
|
||||
const aboutAnchor = offlinePage.getByRole('link', { name: 'About' });
|
||||
expect(await aboutAnchor.getAttribute('href')).toBe('/about');
|
||||
await aboutAnchor.click({ noWaitAfter: false });
|
||||
const url = await offlinePage.evaluate(async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 3000));
|
||||
return location.href;
|
||||
});
|
||||
expect(url).toBe('http://localhost:4173/about');
|
||||
expect(offlinePage.locator('li[aria-current="page"] a').getByText('About')).toBeTruthy();
|
||||
await offlinePage.reload({ waitUntil: 'load' });
|
||||
expect(offlinePage.url()).toBe('http://localhost:4173/about');
|
||||
expect(offlinePage.locator('li[aria-current="page"] a').getByText('About')).toBeTruthy();
|
||||
// Dispose context once it's no longer needed.
|
||||
await context.close();
|
||||
});
|
||||
@@ -0,0 +1,56 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
|
||||
test('The service worker is registered and cache storage is present', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
const swURL = await page.evaluate(async () => {
|
||||
const registration = await Promise.race([
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
navigator.serviceWorker.ready,
|
||||
new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error('Service worker registration failed: time out')), 10000)
|
||||
)
|
||||
]);
|
||||
// @ts-expect-error registration is of type unknown
|
||||
return registration.active?.scriptURL;
|
||||
});
|
||||
const swName = 'sw.js';
|
||||
expect(swURL).toBe(`http://localhost:4173/${swName}`);
|
||||
|
||||
const cacheContents = await page.evaluate(async () => {
|
||||
const cacheState: Record<string, Array<string>> = {};
|
||||
for (const cacheName of await caches.keys()) {
|
||||
const cache = await caches.open(cacheName);
|
||||
cacheState[cacheName] = (await cache.keys()).map((req) => req.url);
|
||||
}
|
||||
return cacheState;
|
||||
});
|
||||
|
||||
expect(Object.keys(cacheContents).length).toEqual(1);
|
||||
|
||||
const key = 'workbox-precache-v2-http://localhost:4173/';
|
||||
|
||||
expect(Object.keys(cacheContents)[0]).toEqual(key);
|
||||
|
||||
const urls = cacheContents[key].map((url) => url.slice('http://localhost:4173/'.length));
|
||||
|
||||
/*
|
||||
'http://localhost:4173/about?__WB_REVISION__=38251751d310c9b683a1426c22c135a2',
|
||||
'http://localhost:4173/?__WB_REVISION__=073370aa3804305a787b01180cd6b8aa',
|
||||
'http://localhost:4173/manifest.webmanifest?__WB_REVISION__=27df2fa4f35d014b42361148a2207da3'
|
||||
*/
|
||||
expect(urls.some((url) => url.startsWith('manifest.webmanifest?__WB_REVISION__='))).toEqual(true);
|
||||
expect(urls.some((url) => url.startsWith('?__WB_REVISION__='))).toEqual(true);
|
||||
expect(urls.some((url) => url.startsWith('about?__WB_REVISION__='))).toEqual(true);
|
||||
// dontCacheBustURLsMatching: any asset in _app/immutable folder shouldn't have a revision (?__WB_REVISION__=)
|
||||
expect(urls.some((url) => url.startsWith('_app/immutable/') && url.endsWith('.css'))).toEqual(
|
||||
true
|
||||
);
|
||||
expect(urls.some((url) => url.startsWith('_app/immutable/') && url.endsWith('.js'))).toEqual(
|
||||
true
|
||||
);
|
||||
expect(urls.some((url) => url.includes('_app/version.json?__WB_REVISION__='))).toEqual(true);
|
||||
});
|
||||
Generated
+5827
-5
File diff suppressed because it is too large
Load Diff
+26
-7
@@ -4,22 +4,41 @@
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "npx tailwindcss -i ./static/input.css -o ./static/output.css && vite dev",
|
||||
"dev-generate": "GENERATE_SW=true npx tailwindcss -i ./static/input.css -o ./static/output.css && vite dev",
|
||||
"dev-generate-suppress-w": "GENERATE_SW=true SUPPRESS_WARNING=true npx tailwindcss -i ./static/input.css -o ./static/output.css && vite dev",
|
||||
"build-generate-sw": "GENERATE_SW=true vite build",
|
||||
"build-generate-sw-node": "NODE_ADAPTER=true GENERATE_SW=true vite build",
|
||||
"build": "npx tailwindcss -i ./static/input.css -o ./static/output.css && vite build",
|
||||
"preview": "vite preview",
|
||||
"build-inject-manifest-node": "NODE_ADAPTER=true vite build",
|
||||
"build-self-destroying": "SELF_DESTROYING_SW=true vite build",
|
||||
"preview": "vite preview --port=4173",
|
||||
"preview-node": "PORT=4173 node build",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"lint": "prettier --check . && eslint .",
|
||||
"lint-fix": "npm run lint --fix",
|
||||
"format": "prettier --write .",
|
||||
"tailwind": "npx tailwindcss -i ./static/input.css -o ./static/output.css"
|
||||
"tailwind": "npx tailwindcss -i ./static/input.css -o ./static/output.css",
|
||||
"test-generate-sw": "npm run build-generate-sw && GENERATE_SW=true vitest run && GENERATE_SW=true playwright test",
|
||||
"test-generate-sw-node": "npm run build-generate-sw-node && NODE_ADAPTER=true GENERATE_SW=true vitest run && NODE_ADAPTER=true GENERATE_SW=true playwright test",
|
||||
"test-inject-manifest": "npm run build-inject-manifest && vitest run && playwright test",
|
||||
"test-inject-manifest-node": "npm run build-inject-manifest-node && NODE_ADAPTER=true vitest run && NODE_ADAPTER=true playwright test",
|
||||
"test": "npm run test-generate-sw && npm run test-generate-sw-node && npm run test-inject-manifest && npm run test-inject-manifest-node"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.37.1",
|
||||
"@sveltejs/adapter-auto": "^3.0.0",
|
||||
"@sveltejs/adapter-netlify": "^4.1.0",
|
||||
"@sveltejs/adapter-node": "^2.0.0",
|
||||
"@sveltejs/adapter-static": "^3.0.0",
|
||||
"@sveltejs/kit": "^2.0.6",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.0.0",
|
||||
"@types/cookie": "^0.6.0",
|
||||
"@types/eslint": "^8.56.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
||||
"@typescript-eslint/parser": "^7.0.0",
|
||||
"@vite-pwa/assets-generator": "^0.2.4",
|
||||
"@vite-pwa/sveltekit": "^0.4.0",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"daisyui": "^4.10.1",
|
||||
"eslint": "^8.56.0",
|
||||
@@ -28,12 +47,12 @@
|
||||
"postcss": "^8.4.38",
|
||||
"prettier": "^3.1.1",
|
||||
"prettier-plugin-svelte": "^3.1.2",
|
||||
"svelte": "^4.2.7",
|
||||
"svelte-check": "^3.6.0",
|
||||
"svelte": "^4.2.8",
|
||||
"svelte-check": "^3.6.2",
|
||||
"tailwindcss": "^3.4.3",
|
||||
"tslib": "^2.4.1",
|
||||
"typescript": "^5.0.0",
|
||||
"vite": "^5.0.3"
|
||||
"tslib": "^2.6.2",
|
||||
"typescript": "^5.3.3",
|
||||
"vitest": "^1.0.4"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
import { defineConfig, devices } from '@playwright/test'
|
||||
|
||||
const url = 'http://localhost:4173'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
import { nodeAdapter } from './adapter.mjs'
|
||||
|
||||
/**
|
||||
* Read environment variables from file.
|
||||
* https://github.com/motdotla/dotenv
|
||||
*/
|
||||
// require('dotenv').config();
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig({
|
||||
testDir: './client-test',
|
||||
/* Folder for test artifacts such as screenshots, videos, traces, etc. */
|
||||
outputDir: 'test-results/',
|
||||
timeout: 5 * 1000,
|
||||
expect: {
|
||||
/**
|
||||
* Maximum time expect() should wait for the condition to be met.
|
||||
* For example in `await expect(locator).toHaveText();`
|
||||
*/
|
||||
timeout: 1000,
|
||||
},
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: true,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: !!process.env.CI,
|
||||
/* Retry on CI only */
|
||||
retries: 0,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: 'line',
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
|
||||
actionTimeout: 0,
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
baseURL: url,
|
||||
//offline: true,
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: 'on-first-retry',
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: { ...devices['Desktop Chrome'] },
|
||||
},
|
||||
|
||||
// {
|
||||
// name: 'firefox',
|
||||
// use: { ...devices['Desktop Firefox'] },
|
||||
// },
|
||||
|
||||
// {
|
||||
// name: 'webkit',
|
||||
// use: { ...devices['Desktop Safari'] },
|
||||
// },
|
||||
|
||||
/* Test against mobile viewports. */
|
||||
// {
|
||||
// name: 'Mobile Chrome',
|
||||
// use: { ...devices['Pixel 5'] },
|
||||
// },
|
||||
// {
|
||||
// name: 'Mobile Safari',
|
||||
// use: { ...devices['iPhone 12'] },
|
||||
// },
|
||||
|
||||
/* Test against branded browsers. */
|
||||
// {
|
||||
// name: 'Microsoft Edge',
|
||||
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
||||
// },
|
||||
// {
|
||||
// name: 'Google Chrome',
|
||||
// use: { ..devices['Desktop Chrome'], channel: 'chrome' },
|
||||
// },
|
||||
],
|
||||
|
||||
/* Run your local dev server before starting the tests */
|
||||
webServer: {
|
||||
command: nodeAdapter ? 'pnpm run preview-node' : 'pnpm run preview',
|
||||
url,
|
||||
reuseExistingServer: !process.env.CI,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
import {
|
||||
createAppleSplashScreens,
|
||||
defineConfig,
|
||||
minimal2023Preset
|
||||
} from '@vite-pwa/assets-generator/config';
|
||||
|
||||
export default defineConfig({
|
||||
headLinkOptions: {
|
||||
preset: '2023'
|
||||
},
|
||||
preset: {
|
||||
...minimal2023Preset,
|
||||
appleSplashScreens: createAppleSplashScreens(
|
||||
{
|
||||
padding: 0.3,
|
||||
resizeOptions: { fit: 'contain', background: 'white' },
|
||||
darkResizeOptions: { fit: 'contain', background: 'black' },
|
||||
linkMediaOptions: {
|
||||
log: true,
|
||||
addMediaScreen: true,
|
||||
xhtml: true
|
||||
}
|
||||
},
|
||||
['iPad Air 9.7"']
|
||||
)
|
||||
},
|
||||
images: 'static/favicon.png'
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
import process from 'node:process'
|
||||
|
||||
export const generateSW = process.env.GENERATE_SW === 'true'
|
||||
Vendored
+19
-7
@@ -1,13 +1,25 @@
|
||||
import 'vite-plugin-pwa/svelte';
|
||||
import 'vite-plugin-pwa/info';
|
||||
import 'vite-plugin-pwa/pwa-assets';
|
||||
|
||||
// See https://kit.svelte.dev/docs/types#app
|
||||
// for information about these interfaces
|
||||
// and what to do when importing types
|
||||
declare global {
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
// interface Locals {}
|
||||
// interface PageData {}
|
||||
// interface PageState {}
|
||||
// interface Platform {}
|
||||
}
|
||||
const __DATE__: string;
|
||||
const __RELOAD_SW__: boolean;
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
interface Locals {
|
||||
userid: string;
|
||||
buildDate: string;
|
||||
periodicUpdates: boolean;
|
||||
}
|
||||
|
||||
// interface PageData {}
|
||||
// interface PageState {}
|
||||
// interface Platform {}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
|
||||
@@ -4,20 +4,6 @@
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
<meta name="theme-color" content="#f00000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="A free and open source web app to track completion of a living Pokédex, which works offline."
|
||||
/>
|
||||
<meta property="og:title" content="Living Dex Tracker - A free Pokédex completion tool" />
|
||||
<meta property="og:url" content="https://pokedex.jcreek.co.uk" />
|
||||
<meta
|
||||
property="og:description"
|
||||
content="A free and open source web app to track completion of a living Pokédex, which works offline."
|
||||
/>
|
||||
<link rel="canonical" href="https://pokedex.jcreek.co.uk" />
|
||||
<link rel="apple-touch-icon" href="%sveltekit.assets%/apple-touch-icon.png" />
|
||||
%sveltekit.head%
|
||||
<link rel="stylesheet" href="%sveltekit.assets%/output.css" />
|
||||
</head>
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
<script lang="ts">
|
||||
import { useRegisterSW } from 'virtual:pwa-register/svelte';
|
||||
const { needRefresh, updateServiceWorker, offlineReady } = useRegisterSW({
|
||||
onRegistered(r) {
|
||||
// uncomment following code if you want check for updates
|
||||
// r && setInterval(() => {
|
||||
// console.log('Checking for sw update')
|
||||
// r.update()
|
||||
// }, 20000 /* 20s for testing purposes */)
|
||||
console.log(`SW Registered: ${r}`);
|
||||
},
|
||||
onRegisterError(error) {
|
||||
console.log('SW registration error', error);
|
||||
}
|
||||
});
|
||||
const close = () => {
|
||||
offlineReady.set(false);
|
||||
needRefresh.set(false);
|
||||
};
|
||||
$: toast = $offlineReady || $needRefresh;
|
||||
</script>
|
||||
|
||||
{#if toast}
|
||||
<div class="pwa-toast" role="alert">
|
||||
<div class="message">
|
||||
{#if $offlineReady}
|
||||
<span> App ready to work offline </span>
|
||||
{:else}
|
||||
<span> New content available, click on reload button to update. </span>
|
||||
{/if}
|
||||
</div>
|
||||
{#if $needRefresh}
|
||||
<button on:click={() => updateServiceWorker(true)}> Reload </button>
|
||||
{/if}
|
||||
<button on:click={close}> Close </button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.pwa-toast {
|
||||
position: fixed;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
margin: 16px;
|
||||
padding: 12px;
|
||||
border: 1px solid #8885;
|
||||
border-radius: 4px;
|
||||
z-index: 2;
|
||||
text-align: left;
|
||||
box-shadow: 3px 4px 5px 0 #8885;
|
||||
background-color: white;
|
||||
}
|
||||
.pwa-toast .message {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.pwa-toast button {
|
||||
border: 1px solid #8885;
|
||||
outline: none;
|
||||
margin-right: 5px;
|
||||
border-radius: 2px;
|
||||
padding: 3px 10px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,32 @@
|
||||
/// <reference lib="WebWorker" />
|
||||
/// <reference types="vite/client" />
|
||||
/// <reference no-default-lib="true"/>
|
||||
/// <reference lib="esnext" />
|
||||
import {
|
||||
cleanupOutdatedCaches,
|
||||
createHandlerBoundToURL,
|
||||
precacheAndRoute,
|
||||
precache
|
||||
} from 'workbox-precaching';
|
||||
import { NavigationRoute, registerRoute } from 'workbox-routing';
|
||||
|
||||
declare let self: ServiceWorkerGlobalScope;
|
||||
|
||||
self.addEventListener('message', (event) => {
|
||||
if (event.data && event.data.type === 'SKIP_WAITING') self.skipWaiting();
|
||||
});
|
||||
|
||||
// Add root route to precache manifest
|
||||
precache([{ url: '/', revision: null }]);
|
||||
|
||||
// self.__WB_MANIFEST is default injection point
|
||||
precacheAndRoute(self.__WB_MANIFEST);
|
||||
|
||||
// clean old assets
|
||||
cleanupOutdatedCaches();
|
||||
|
||||
let allowlist: undefined | RegExp[];
|
||||
if (import.meta.env.DEV) allowlist = [/^\/$/];
|
||||
|
||||
// to allow work offline
|
||||
registerRoute(new NavigationRoute(createHandlerBoundToURL('/'), { allowlist }));
|
||||
@@ -6,6 +6,11 @@
|
||||
import SignIn from '$lib/components/SignIn.svelte';
|
||||
import SignOut from '$lib/components/SignOut.svelte';
|
||||
|
||||
import { pwaInfo } from 'virtual:pwa-info';
|
||||
import { pwaAssetsHead } from 'virtual:pwa-assets/head';
|
||||
|
||||
$: webManifestLink = pwaInfo ? pwaInfo.webManifest.linkTag : '';
|
||||
|
||||
export let data;
|
||||
let { supabase } = data;
|
||||
$: ({ supabase } = data);
|
||||
@@ -18,6 +23,24 @@
|
||||
|
||||
onMount(async () => {
|
||||
await getUser();
|
||||
|
||||
if (pwaInfo) {
|
||||
const { registerSW } = await import('virtual:pwa-register');
|
||||
registerSW({
|
||||
immediate: true,
|
||||
onRegistered(r) {
|
||||
// uncomment following code if you want check for updates
|
||||
// r && setInterval(() => {
|
||||
// console.log('Checking for sw update')
|
||||
// r.update()
|
||||
// }, 20000 /* 20s for testing purposes */)
|
||||
console.log(`SW Registered: ${r}`);
|
||||
},
|
||||
onRegisterError(error) {
|
||||
console.log('SW registration error', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
async function getUser() {
|
||||
@@ -37,6 +60,34 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
{#if pwaAssetsHead.themeColor}
|
||||
<meta name="theme-color" content={pwaAssetsHead.themeColor.content} />
|
||||
{/if}
|
||||
{#if pwaAssetsHead.description}
|
||||
<meta name="description" content={pwaAssetsHead.description.content} />
|
||||
{/if}
|
||||
{#each pwaAssetsHead.links as link}
|
||||
<link {...link} />
|
||||
{/each}
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
{@html webManifestLink}
|
||||
|
||||
<!-- <meta
|
||||
name="description"
|
||||
content="A free and open source web app to track completion of a living Pokédex, which works offline."
|
||||
/> -->
|
||||
<meta property="og:title" content="Living Dex Tracker - A free Pokédex completion tool" />
|
||||
<meta property="og:url" content="https://pokedex.jcreek.co.uk" />
|
||||
<meta
|
||||
property="og:description"
|
||||
content="A free and open source web app to track completion of a living Pokédex, which works offline."
|
||||
/>
|
||||
<link rel="canonical" href="https://pokedex.jcreek.co.uk" />
|
||||
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
||||
<!-- <link rel="apple-touch-icon" href="%sveltekit.assets%/apple-touch-icon.png" /> -->
|
||||
</svelte:head>
|
||||
|
||||
<header>
|
||||
<div class="navbar bg-base-100">
|
||||
<div class="flex-1">
|
||||
@@ -104,3 +155,7 @@
|
||||
<a class="link link-hover">Cookie policy</a>
|
||||
</nav>
|
||||
</footer>
|
||||
|
||||
{#await import('$lib/components/pwa/ReloadPrompt.svelte') then { default: ReloadPrompt }}
|
||||
<ReloadPrompt />
|
||||
{/await}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<svelte:head>
|
||||
<title>About</title>
|
||||
<meta name="description" content="About this app" />
|
||||
</svelte:head>
|
||||
|
||||
<div class="content">
|
||||
<h1>About this app</h1>
|
||||
|
||||
<p>
|
||||
This is a <a href="https://kit.svelte.dev">SvelteKit</a> app. You can make your own by typing the
|
||||
following into your command line and following the prompts:
|
||||
</p>
|
||||
|
||||
<pre>npm create svelte@latest</pre>
|
||||
|
||||
<p>
|
||||
The page you're looking at is purely static HTML, with no client-side interactivity needed.
|
||||
Because of that, we don't need to load any JavaScript. Try viewing the page's source, or opening
|
||||
the devtools network panel and reloading.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.content {
|
||||
width: 100%;
|
||||
max-width: var(--column-width);
|
||||
margin: var(--column-margin-top) auto 0 auto;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,9 @@
|
||||
import { dev } from '$app/environment';
|
||||
|
||||
// we don't need any JS on this page, though we'll load
|
||||
// it in dev so that we get hot module replacement...
|
||||
export const csr = dev;
|
||||
|
||||
// since there's no dynamic data here, we can prerender
|
||||
// it so that it gets served as a static asset in prod
|
||||
export const prerender = true;
|
||||
@@ -1,80 +0,0 @@
|
||||
/// <reference types="@sveltejs/kit" />
|
||||
import { build, files, version } from '$service-worker';
|
||||
|
||||
// Create a unique cache name for this deployment
|
||||
const CACHE = `cache-${version}`;
|
||||
|
||||
const ASSETS = [
|
||||
...build, // the app itself
|
||||
...files // everything in `static`
|
||||
];
|
||||
|
||||
self.addEventListener('install', (event) => {
|
||||
// Create a new cache and add all files to it
|
||||
async function addFilesToCache() {
|
||||
const cache = await caches.open(CACHE);
|
||||
await cache.addAll(ASSETS);
|
||||
}
|
||||
|
||||
event.waitUntil(addFilesToCache());
|
||||
});
|
||||
|
||||
self.addEventListener('activate', (event) => {
|
||||
// Remove previous cached data from disk
|
||||
async function deleteOldCaches() {
|
||||
for (const key of await caches.keys()) {
|
||||
if (key !== CACHE) await caches.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
event.waitUntil(deleteOldCaches());
|
||||
});
|
||||
|
||||
self.addEventListener('fetch', (event) => {
|
||||
// ignore POST requests etc
|
||||
if (event.request.method !== 'GET') return;
|
||||
|
||||
async function respond() {
|
||||
const url = new URL(event.request.url);
|
||||
const cache = await caches.open(CACHE);
|
||||
|
||||
// `build`/`files` can always be served from the cache
|
||||
if (ASSETS.includes(url.pathname)) {
|
||||
const response = await cache.match(url.pathname);
|
||||
|
||||
if (response) {
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
// for everything else, try the network first, but
|
||||
// fall back to the cache if we're offline
|
||||
try {
|
||||
const response = await fetch(event.request);
|
||||
|
||||
// if we're offline, fetch can return a value that is not a Response
|
||||
// instead of throwing - and we can't pass this non-Response to respondWith
|
||||
if (!(response instanceof Response)) {
|
||||
throw new Error('invalid response from fetch');
|
||||
}
|
||||
|
||||
if (response.status === 200) {
|
||||
cache.put(event.request, response.clone());
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (err) {
|
||||
const response = await cache.match(event.request);
|
||||
|
||||
if (response) {
|
||||
return response;
|
||||
}
|
||||
|
||||
// if there's no cache, then just error out
|
||||
// as there is nothing we can do to respond to this request
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
event.respondWith(respond());
|
||||
});
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
@@ -0,0 +1,3 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"name": "Living Dex Tracker",
|
||||
"short_name": "Living Dex Tracker",
|
||||
"description": "A tool to enable you to track your progress in completing the living Pokédex in Pokémon games.",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "any maskable"
|
||||
}
|
||||
],
|
||||
"start_url": "/",
|
||||
"theme_color": "#f00000",
|
||||
"background_color": "#f0f0f0",
|
||||
"display": "standalone"
|
||||
}
|
||||
+10
-1
@@ -1,5 +1,7 @@
|
||||
import adapter from '@sveltejs/adapter-netlify';
|
||||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
||||
// you don't need to do this if you're using generateSW strategy in your app
|
||||
import { generateSW } from './pwa.mjs';
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
const config = {
|
||||
@@ -17,7 +19,14 @@ const config = {
|
||||
// instead of creating a single one for the entire app.
|
||||
// if `edge` is true, this option cannot be used
|
||||
split: false
|
||||
})
|
||||
}),
|
||||
serviceWorker: {
|
||||
register: true
|
||||
},
|
||||
files: {
|
||||
// you don't need to do this if you're using generateSW strategy in your app
|
||||
serviceWorker: generateSW ? undefined : 'src/prompt-sw.ts'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { existsSync, readFileSync } from 'node:fs'
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
import { generateSW } from '../pwa.mjs'
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
import { nodeAdapter } from '../adapter.mjs'
|
||||
|
||||
describe(`test-build: ${nodeAdapter ? 'node' : 'static'} adapter`, () => {
|
||||
it(`service worker is generated: ${generateSW ? 'sw.js' : 'prompt-sw.js'}`, () => {
|
||||
const swName = `./build/${nodeAdapter ? 'client/': ''}${generateSW ? 'sw.js' : 'prompt-sw.js'}`
|
||||
expect(existsSync(swName), `${swName} doesn't exist`).toBeTruthy()
|
||||
const webManifest = `./build/${nodeAdapter ? 'client/': ''}manifest.webmanifest`
|
||||
expect(existsSync(webManifest), `${webManifest} doesn't exist`).toBeTruthy()
|
||||
const swContent = readFileSync(swName, 'utf-8')
|
||||
let match: RegExpMatchArray | null
|
||||
if (generateSW) {
|
||||
match = swContent.match(/define\(\['\.\/(workbox-\w+)'/)
|
||||
expect(match && match.length === 2, `workbox-***.js entry not found in ${swName}`).toBeTruthy()
|
||||
const workboxName = `./build/${nodeAdapter ? 'client/': ''}${match?.[1]}.js`
|
||||
expect(existsSync(workboxName),`${workboxName} doesn't exist`).toBeTruthy()
|
||||
}
|
||||
match = swContent.match(/"url":\s*"manifest\.webmanifest"/)
|
||||
expect(match && match.length === 1, 'missing manifest.webmanifest in sw precache manifest').toBeTruthy()
|
||||
match = swContent.match(/"url":\s*"\/"/)
|
||||
expect(match && match.length === 1, 'missing entry point route (/) in sw precache manifest').toBeTruthy()
|
||||
match = swContent.match(/"url":\s*"about"/)
|
||||
expect(match && match.length === 1,'missing about route (/about) in sw precache manifest').toBeTruthy()
|
||||
if (nodeAdapter) {
|
||||
match = swContent.match(/"url":\s*"server\//)
|
||||
expect(match === null, 'found server/ entries in sw precache manifest').toBeTruthy()
|
||||
}
|
||||
})
|
||||
})
|
||||
+9
-2
@@ -9,8 +9,15 @@
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"moduleResolution": "bundler"
|
||||
}
|
||||
"moduleResolution": "bundler",
|
||||
"types": [
|
||||
"vite-plugin-pwa/svelte",
|
||||
"vite-plugin-pwa/vanillajs",
|
||||
"vite-plugin-pwa/info",
|
||||
"virtual:pwa-assets"
|
||||
]
|
||||
},
|
||||
"include": ["node_modules/vite-plugin-pwa/client.d.ts", "node_modules/**/*.d.ts"]
|
||||
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
|
||||
//
|
||||
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
||||
|
||||
+60
-1
@@ -1,6 +1,65 @@
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import { defineConfig } from 'vite';
|
||||
import { SvelteKitPWA } from '@vite-pwa/sveltekit';
|
||||
// you don't need to do this if you're using generateSW strategy in your app
|
||||
import { generateSW } from './pwa.mjs';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [sveltekit()]
|
||||
plugins: [
|
||||
sveltekit(),
|
||||
SvelteKitPWA({
|
||||
srcDir: './src',
|
||||
mode: 'development',
|
||||
// you don't need to do this if you're using generateSW strategy in your app
|
||||
strategies: generateSW ? 'generateSW' : 'injectManifest',
|
||||
// you don't need to do this if you're using generateSW strategy in your app
|
||||
filename: generateSW ? undefined : 'prompt-sw.ts',
|
||||
scope: '/',
|
||||
base: '/',
|
||||
selfDestroying: process.env.SELF_DESTROYING_SW === 'true',
|
||||
pwaAssets: {
|
||||
config: true
|
||||
},
|
||||
manifest: {
|
||||
name: 'Living Dex Tracker',
|
||||
short_name: 'Living Dex Tracker',
|
||||
description:
|
||||
'A tool to enable you to track your progress in completing the living Pokédex in Pokémon games.',
|
||||
icons: [
|
||||
{
|
||||
src: '/android-chrome-192x192.png',
|
||||
sizes: '192x192',
|
||||
type: 'image/png'
|
||||
},
|
||||
{
|
||||
src: '/android-chrome-512x512.png',
|
||||
sizes: '512x512',
|
||||
type: 'image/png',
|
||||
purpose: 'any maskable'
|
||||
}
|
||||
],
|
||||
start_url: '/',
|
||||
scope: '/',
|
||||
display: 'standalone',
|
||||
theme_color: '#f00000',
|
||||
background_color: '#f0f0f0'
|
||||
},
|
||||
injectManifest: {
|
||||
globPatterns: ['client/**/*.{js,css,ico,png,svg,webp,woff,woff2,webmanifest}']
|
||||
},
|
||||
workbox: {
|
||||
globPatterns: ['client/**/*.{js,css,ico,png,svg,webp,woff,woff2,webmanifest}']
|
||||
},
|
||||
devOptions: {
|
||||
enabled: false,
|
||||
suppressWarnings: process.env.SUPPRESS_WARNING === 'true',
|
||||
type: 'module',
|
||||
navigateFallback: '/'
|
||||
},
|
||||
// if you have shared info in svelte config file put in a separate module and use it also here
|
||||
kit: {
|
||||
includeVersionFile: true
|
||||
}
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { defineConfig } from 'vitest/config'
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
include: ['test/*.test.ts']
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user