Merge pull request #28 from jcreek/feat/workbox

chore(*): Add workbox service worker
This commit is contained in:
Josh Creek
2024-04-14 16:06:49 +01:00
committed by GitHub
24 changed files with 6414 additions and 139 deletions
+7
View File
@@ -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()
+39
View File
@@ -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();
});
+56
View File
@@ -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);
});
+5827 -5
View File
File diff suppressed because it is too large Load Diff
+26 -7
View File
@@ -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": {
+96
View File
@@ -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,
},
});
+28
View File
@@ -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'
});
+3
View File
@@ -0,0 +1,3 @@
import process from 'node:process'
export const generateSW = process.env.GENERATE_SW === 'true'
+19 -7
View File
@@ -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 {};
-14
View File
@@ -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>
+32
View File
@@ -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 }));
+55
View File
@@ -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}
+30
View File
@@ -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>
+9
View File
@@ -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;
-80
View File
@@ -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

+3
View File
@@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:
-22
View File
@@ -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
View File
@@ -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'
}
}
};
+35
View File
@@ -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
View File
@@ -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
View File
@@ -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
}
})
]
});
+7
View File
@@ -0,0 +1,7 @@
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
include: ['test/*.test.ts']
}
})