Skip to content

Commit 83ed1b1

Browse files
committed
Remove adhoc manifest.json page, update Vite PWA config, implement PWA E2E tess
1 parent 7e60005 commit 83ed1b1

25 files changed

Lines changed: 377 additions & 465 deletions

File tree

‎.gitignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# build output
22
dist/
3+
dev-dist/
34

45
# generated types
56
.astro/

‎@types/virtual-pwa-register.d.ts‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
declare module 'virtual:pwa-register' {
2+
export interface RegisterSWOptions {
3+
immediate?: boolean
4+
onNeedRefresh?: () => void
5+
onOfflineReady?: () => void
6+
onRegistered?: (registration: ServiceWorkerRegistration | undefined) => void
7+
onRegisterError?: (error: Error) => void
8+
}
9+
10+
export function registerSW(options?: RegisterSWOptions): (reloadPage?: boolean) => Promise<void>
11+
}

‎@types/window.d.ts‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
* Global Window interface extensions
33
* Centralizes all custom properties added to the window object
44
*/
5+
import type { registerSW } from 'virtual:pwa-register'
56
import type { EvaluationError } from '@test/errors/EvaluationError'
67

78
/**
@@ -89,6 +90,11 @@ declare global {
8990
*/
9091
__astroPageLoadCounter?: number
9192
__astroPageLoadListenerAttached?: boolean
93+
94+
/**
95+
* Indicates whether the window is running inside a web worker context
96+
*/
97+
__pwaUpdateSW?: ReturnType<typeof registerSW> | null
9298
}
9399
}
94100

‎_TODO.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## See the @TODO: about enabling the PWA to be used as a share target for other apps in src/pages/manifest.json.ts
44

5-
## Make sure all of the pages/testing files are wrapped in BaseLayout for consistency.
5+
## Make sure all of the pages/testing files are wrapped in BaseLayout for consistency
66

77
## Analytics
88

@@ -70,7 +70,7 @@ Shared Types vs Swagger / Keeping Docs in Sync
7070

7171
2. Code-first OpenAPI (Zod or TS schemas → OpenAPI)
7272

73-
- Define schemas in Zod/Valibot (or ts-rest) alongside the endpoint. Generate OpenAPI JSON plus TypeScript types from those schemas. Docs (Swagger UI/Redoc) and any client SDKs come from the generated spec, so they’re always in sync.
73+
- Define schemas in Zod/Valibot (or ts-rest) alongside the endpoint. Generate OpenAPI JSON plus TypeScript types from those schemas. Docs (Swagger UI/Redoc) and any client SDKs come from the generated spec, so they're always in sync.
7474
- Guarantees: CI regenerates the spec and fails when the checked-in artifact is stale; endpoint handlers reuse the same schema for runtime validation, so a mismatch cannot compile.
7575

7676
3. Spec-first OpenAPI + Swagger Codegen

‎astro.config.ts‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { defineConfig } from 'astro/config'
1010
import { fileURLToPath } from 'node:url'
1111
import type { PluginOption } from 'vite'
1212
/**
13-
* You cannot use path aliases (@lib, @components, etc.) in files that are
13+
* You cannot use path aliases (`@lib`, `@components`, etc.) in files that are
1414
* imported by astro.config.ts, because the path alias resolution happens
1515
* AFTER the config is loaded, not before. This means that adding resolve.alias
1616
* paths to the vite section in astro.config.ts would not allow using path
@@ -24,21 +24,22 @@ import {
2424
isUnitTest,
2525
isVercel,
2626
markdownConfig,
27-
serviceWorkerConfig,
27+
pwaConfig,
2828
vercelConfig,
2929
} from './src/lib/config'
3030
import { callToActionValidator } from './src/integrations/CtaValidator'
3131
import { faviconGenerator } from './src/integrations/FaviconGenerator'
3232
import { packageRelease } from './src/integrations/PackageRelease'
3333
import { privacyPolicyVersion } from './src/integrations/PrivacyPolicyVersion'
34+
import { pwaDevAssetServer } from './src/lib/plugins/pwaDevAssetServer'
3435
import { createSerializeFunction, pagesJsonWriter } from './src/integrations/sitemapSerialize'
3536

3637
const sharedTestIntegrations = [
3738
icon(),
3839
]
3940

4041
const standardIntegrations = [
41-
AstroPWA(serviceWorkerConfig),
42+
AstroPWA(pwaConfig),
4243
...sharedTestIntegrations,
4344
mdx(markdownConfig),
4445
/** Generate favicons and PWA icons from source SVG */
@@ -104,6 +105,7 @@ export default defineConfig({
104105
/* @ts-expect-error - tailwindcss plugin type compatibility */
105106
plugins: [
106107
tailwindcss(),
108+
pwaDevAssetServer(),
107109
] as PluginOption[],
108110
resolve: {
109111
alias: {

‎eslint.config.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ export default [
161161
{
162162
ignore: [
163163
'^astro:.*', // Ignore Astro virtual modules
164+
'^virtual:pwa-register$',
164165
'^\\./.*', // Ignore relative imports (let TypeScript handle these)
165166
],
166167
},
@@ -429,6 +430,7 @@ export default [
429430

430431
{
431432
files: [
433+
'src/lib/config/pwa.ts',
432434
'src/lib/config/serviceWorker.ts',
433435
'src/components/scripts/store/__tests__/socialEmbeds.spec.ts',
434436
'src/pages/api/**/*',
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
---
2+
export interface Props {
3+
heading?: string
4+
subheading?: string
5+
description?: string
6+
ctaHref?: string
7+
ctaLabel?: string
8+
}
9+
10+
const {
11+
heading = '404',
12+
subheading = 'Page Not Found',
13+
description = "Sorry, the page you are looking for doesn't exist or has been moved.",
14+
ctaHref = '/',
15+
ctaLabel = 'Go Home',
16+
} = Astro.props
17+
---
18+
19+
<div class="mb-8 text-center">
20+
<h1 class="text-9xl font-bold text-text-muted mb-4">{heading}</h1>
21+
<h2 class="text-3xl md:text-4xl font-bold text-text mb-4">{subheading}</h2>
22+
<p class="text-lg text-text-muted mb-8">
23+
{description}
24+
</p>
25+
<div class="space-y-4">
26+
<a
27+
href={ctaHref}
28+
class="inline-flex items-center justify-center px-6 py-3 border border-transparent text-base font-medium rounded-lg text-white bg-primary hover:bg-primary-hover focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary transition-colors duration-200"
29+
>
30+
{ctaLabel}
31+
</a>
32+
</div>
33+
</div>

‎src/components/Head/Meta.astro‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,6 @@ const { slug, defaultTheme } = getMetaThemeData(path)
8585
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
8686
{/* Status bar style with values such as "default" or "black-translucent" */}
8787
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
88-
{/* Web manifest file for Progressive Web Application app usage */}
89-
<link rel="manifest" href={absoluteUrl('manifest.json', Astro.site)} />
9088
{/* Allows the web app to run in full-screen mode */}
9189
<meta name="mobile-web-app-capable" content="yes">
9290
{/* RSS Feed */}
@@ -96,6 +94,8 @@ const { slug, defaultTheme } = getMetaThemeData(path)
9694
href={absoluteUrl('rss.xml', Astro.site)}
9795
title={`RSS Feed for ${contactData.company.name}`}
9896
/>
97+
{/* Web App Manifest */}
98+
<link rel="manifest" href="/manifest.json" />
9999
{/* Webmentions */}
100100
<link rel="pingback" href={`https://webmention.io/${Astro.site!.hostname}/xmlrpc`} />
101101
<link rel="webmention" href={`https://webmention.io/${Astro.site!.hostname}/webmention`} />
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<script type="module" src="/src/components/Pwa/register-sw.ts"></script>

‎src/components/Pwa/register-sw.ts‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { registerSW } from 'virtual:pwa-register'
2+
3+
const registerServiceWorker = () => {
4+
const hasServiceWorker = 'serviceWorker' in navigator
5+
console.info('[pwa] register-sw start', { hasServiceWorker })
6+
7+
if (!hasServiceWorker) {
8+
return
9+
}
10+
11+
const updateSW = registerSW({
12+
immediate: false,
13+
onNeedRefresh() {
14+
window.dispatchEvent(new CustomEvent('pwa:need-refresh'))
15+
},
16+
onOfflineReady() {
17+
window.dispatchEvent(new CustomEvent('pwa:offline-ready'))
18+
},
19+
onRegistered(registration) {
20+
console.info('[pwa] service worker registered', {
21+
scope: registration?.scope,
22+
installing: Boolean(registration?.installing),
23+
waiting: Boolean(registration?.waiting),
24+
active: Boolean(registration?.active),
25+
})
26+
},
27+
onRegisterError(error) {
28+
console.error('[pwa] service worker registration error', error)
29+
},
30+
})
31+
32+
console.info('[pwa] register-sw hooked update handler')
33+
34+
window.__pwaUpdateSW = updateSW
35+
36+
}
37+
38+
registerServiceWorker()

0 commit comments

Comments
 (0)