@swoff/cli 0.3.7 → 0.3.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -606
- package/dist/__tests__/assemble-sw.test.js +8 -38
- package/dist/__tests__/assemble-sw.test.js.map +1 -1
- package/dist/__tests__/cli-integration.test.js +119 -11
- package/dist/__tests__/cli-integration.test.js.map +1 -1
- package/dist/__tests__/config-types.test.js +38 -18
- package/dist/__tests__/config-types.test.js.map +1 -1
- package/dist/__tests__/file-generators.test.js +2 -2
- package/dist/__tests__/file-generators.test.js.map +1 -1
- package/dist/__tests__/glob-matcher.test.js +86 -0
- package/dist/__tests__/glob-matcher.test.js.map +1 -0
- package/dist/__tests__/invalidation-tags.test.js +207 -23
- package/dist/__tests__/invalidation-tags.test.js.map +1 -1
- package/dist/__tests__/loader.test.js +12 -12
- package/dist/__tests__/loader.test.js.map +1 -1
- package/dist/__tests__/validator.test.js +170 -74
- package/dist/__tests__/validator.test.js.map +1 -1
- package/dist/index.js +12 -11
- package/dist/index.js.map +1 -1
- package/dist/lib/cli/help.js +0 -5
- package/dist/lib/cli/help.js.map +1 -1
- package/dist/lib/commands/add.js +52 -33
- package/dist/lib/commands/add.js.map +1 -1
- package/dist/lib/commands/clean.js +1 -15
- package/dist/lib/commands/clean.js.map +1 -1
- package/dist/lib/commands/generate-guide.js +5 -5
- package/dist/lib/commands/generate-guide.js.map +1 -1
- package/dist/lib/commands/generate.js +7 -21
- package/dist/lib/commands/generate.js.map +1 -1
- package/dist/lib/commands/info.js +37 -12
- package/dist/lib/commands/info.js.map +1 -1
- package/dist/lib/commands/init.js +2 -10
- package/dist/lib/commands/init.js.map +1 -1
- package/dist/lib/commands/validate.js +3 -3
- package/dist/lib/commands/validate.js.map +1 -1
- package/dist/lib/config/loader.js +3 -6
- package/dist/lib/config/loader.js.map +1 -1
- package/dist/lib/config/validator.js +192 -42
- package/dist/lib/config/validator.js.map +1 -1
- package/dist/lib/generators/file-generators/cache.js +2 -10
- package/dist/lib/generators/file-generators/cache.js.map +1 -1
- package/dist/lib/generators/file-generators/client-injector.js +51 -12
- package/dist/lib/generators/file-generators/client-injector.js.map +1 -1
- package/dist/lib/generators/file-generators/fetch-wrapper.js +127 -58
- package/dist/lib/generators/file-generators/fetch-wrapper.js.map +1 -1
- package/dist/lib/generators/file-generators/generate-hooks.js +6 -2
- package/dist/lib/generators/file-generators/generate-hooks.js.map +1 -1
- package/dist/lib/generators/file-generators/gql-wrapper.js +139 -0
- package/dist/lib/generators/file-generators/gql-wrapper.js.map +1 -0
- package/dist/lib/generators/file-generators/guide-generator.js +6 -436
- package/dist/lib/generators/file-generators/guide-generator.js.map +1 -1
- package/dist/lib/generators/file-generators/invalidation-tags.js +206 -19
- package/dist/lib/generators/file-generators/invalidation-tags.js.map +1 -1
- package/dist/lib/generators/file-generators/manifest.js +2 -2
- package/dist/lib/generators/file-generators/manifest.js.map +1 -1
- package/dist/lib/generators/file-generators/mutation-queue.js +140 -52
- package/dist/lib/generators/file-generators/mutation-queue.js.map +1 -1
- package/dist/lib/generators/file-generators/mutation-state.js +126 -0
- package/dist/lib/generators/file-generators/mutation-state.js.map +1 -0
- package/dist/lib/generators/file-generators/push.js +6 -7
- package/dist/lib/generators/file-generators/push.js.map +1 -1
- package/dist/lib/generators/file-generators/quick-readme.js +8 -75
- package/dist/lib/generators/file-generators/quick-readme.js.map +1 -1
- package/dist/lib/generators/file-generators/server-push.js +146 -0
- package/dist/lib/generators/file-generators/server-push.js.map +1 -0
- package/dist/lib/generators/file-generators/sw-generator-build.js +8 -9
- package/dist/lib/generators/file-generators/sw-generator-build.js.map +1 -1
- package/dist/lib/generators/file-generators/sw-injector.js +2 -1
- package/dist/lib/generators/file-generators/sw-injector.js.map +1 -1
- package/dist/lib/generators/file-generators/type-definitions.js +26 -1
- package/dist/lib/generators/file-generators/type-definitions.js.map +1 -1
- package/dist/lib/generators/sw-generator.js +8 -18
- package/dist/lib/generators/sw-generator.js.map +1 -1
- package/dist/lib/generators/sw-sections/activate-handler.js.map +1 -1
- package/dist/lib/generators/sw-sections/assemble-sw.js +19 -11
- package/dist/lib/generators/sw-sections/assemble-sw.js.map +1 -1
- package/dist/lib/generators/sw-sections/background-sync-handler.js +80 -24
- package/dist/lib/generators/sw-sections/background-sync-handler.js.map +1 -1
- package/dist/lib/generators/sw-sections/config-header.js +2 -2
- package/dist/lib/generators/sw-sections/config-header.js.map +1 -1
- package/dist/lib/generators/sw-sections/default-template.js +2 -1
- package/dist/lib/generators/sw-sections/default-template.js.map +1 -1
- package/dist/lib/generators/sw-sections/fetch-handler.js +461 -121
- package/dist/lib/generators/sw-sections/fetch-handler.js.map +1 -1
- package/dist/lib/generators/sw-sections/message-handler.js +55 -15
- package/dist/lib/generators/sw-sections/message-handler.js.map +1 -1
- package/dist/lib/generators/sw-sections/server-push-handler.js +114 -0
- package/dist/lib/generators/sw-sections/server-push-handler.js.map +1 -0
- package/dist/lib/generators/sw-sections/sw-push.js +67 -0
- package/dist/lib/generators/sw-sections/sw-push.js.map +1 -0
- package/dist/lib/generators/sw-sections/tag-management.js +62 -21
- package/dist/lib/generators/sw-sections/tag-management.js.map +1 -1
- package/dist/lib/generators/swoff-files-generator.js +12 -16
- package/dist/lib/generators/swoff-files-generator.js.map +1 -1
- package/dist/lib/shared/config-types.js +133 -45
- package/dist/lib/shared/config-types.js.map +1 -1
- package/dist/lib/shared/glob-matcher.js +77 -0
- package/dist/lib/shared/glob-matcher.js.map +1 -0
- package/dist/lib/utils/tty-status.js +21 -0
- package/dist/lib/utils/tty-status.js.map +1 -0
- package/docs/API.md +565 -0
- package/docs/ARCHITECTURE.md +374 -0
- package/docs/CLI.md +148 -0
- package/docs/COMPARISON.md +192 -0
- package/docs/CONFIG.md +277 -0
- package/docs/ECOSYSTEM.md +33 -0
- package/package.json +2 -1
- package/templates/hooks/useAuth.jsx +2 -2
- package/templates/hooks/useAuth.tsx +2 -2
- package/templates/hooks/useCachedFetch.jsx +21 -7
- package/templates/hooks/useCachedFetch.tsx +23 -11
- package/templates/hooks/useMutation.jsx +76 -0
- package/templates/hooks/useMutation.tsx +109 -0
- package/templates/hooks/useMutationQueue.jsx +20 -11
- package/templates/hooks/useMutationQueue.tsx +31 -15
- package/templates/hooks/useMutationState.jsx +13 -0
- package/templates/hooks/useMutationState.tsx +25 -0
- package/templates/hooks/usePrefetch.jsx +10 -0
- package/templates/hooks/usePrefetch.tsx +25 -0
- package/templates/hooks/usePushSubscription.jsx +3 -3
- package/templates/hooks/usePushSubscription.tsx +3 -3
package/README.md
CHANGED
|
@@ -2,642 +2,89 @@
|
|
|
2
2
|
|
|
3
3
|
CLI for [Swoff](https://swoff.netlify.app) — offline-first web apps made easy.
|
|
4
4
|
|
|
5
|
-
Swoff generates a **service worker** and **client-side utilities** that give your web app
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
- **Mutation queue** — writes performed offline replay when back online
|
|
9
|
-
- **Auth** — token management with automatic 401 handling
|
|
10
|
-
- **Cache invalidation** — tag-based cache busting after mutations
|
|
11
|
-
- **PWA** — install prompt and manifest
|
|
12
|
-
- **Push notifications** — subscription management with IndexedDB persistence and VAPID support
|
|
13
|
-
- **Cross-tab sync** — broadcast changes across open tabs
|
|
14
|
-
- **Background Sync API** — process mutations even after tab close
|
|
15
|
-
|
|
16
|
-
It lives in your project as a `swoff/` directory — everything is generated, auditable, and editable.
|
|
17
|
-
|
|
18
|
-
Source: [github.com/iamsuudi/swoff](https://github.com/iamsuudi/swoff)
|
|
19
|
-
|
|
20
|
-
---
|
|
21
|
-
|
|
22
|
-
## Quick Start
|
|
23
|
-
|
|
24
|
-
```bash
|
|
25
|
-
# Initialize configuration (creates swoff.config.json)
|
|
26
|
-
npx @swoff/cli init
|
|
27
|
-
|
|
28
|
-
# Generate service worker and supporting files
|
|
29
|
-
npx @swoff/cli generate
|
|
30
|
-
|
|
31
|
-
# Import the generated entry point in your app
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
```js
|
|
35
|
-
import { initServiceWorker } from "./swoff/client-injector.js";
|
|
36
|
-
|
|
37
|
-
initServiceWorker();
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
Then build your app:
|
|
41
|
-
|
|
42
|
-
```bash
|
|
43
|
-
npm run build
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
The build script auto-appends `node swoff/sw/generator.js` to finalize the service worker with your asset hashes.
|
|
47
|
-
|
|
48
|
-
---
|
|
49
|
-
|
|
50
|
-
## Commands
|
|
51
|
-
|
|
52
|
-
| Command | Description |
|
|
53
|
-
|---------|-------------|
|
|
54
|
-
| `init` | Create `swoff.config.json` with auto-detected framework and language |
|
|
55
|
-
| `generate` | Generate service worker and all supporting files |
|
|
56
|
-
| `add <feature>` | Enable a feature and regenerate |
|
|
57
|
-
| `validate` | Validate `swoff.config.json` |
|
|
58
|
-
| `info [feature]` | Show summary or per-feature details |
|
|
59
|
-
| `clean` | Remove all Swoff files (`swoff/`, config, `version.json`) |
|
|
60
|
-
| `help [command]` | Show help for a specific command |
|
|
61
|
-
|
|
62
|
-
### `init`
|
|
63
|
-
|
|
64
|
-
Creates `swoff.config.json` in your project root. Auto-detects:
|
|
65
|
-
|
|
66
|
-
- **Framework**: react, vue, svelte, or vanilla
|
|
67
|
-
- **Language**: TypeScript or JavaScript
|
|
68
|
-
- **Build tool**: Uses existing `package.json` build script
|
|
69
|
-
|
|
70
|
-
```bash
|
|
71
|
-
swoff init
|
|
72
|
-
swoff init --framework react # override detection
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
### `generate`
|
|
76
|
-
|
|
77
|
-
Generates the service worker (`dist/sw-<version>.js`) and all supporting files into `swoff/`. Also auto-appends the SW generator to your `package.json` build script.
|
|
78
|
-
|
|
79
|
-
```bash
|
|
80
|
-
swoff generate
|
|
81
|
-
swoff generate --sw-only # regenerate only the service worker
|
|
82
|
-
swoff generate --files-only # regenerate only supporting files
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
After generation, read `swoff/GUIDE.md` for the full integration guide for your project.
|
|
86
|
-
|
|
87
|
-
### `add <feature>`
|
|
88
|
-
|
|
89
|
-
Enables a feature in `swoff.config.json` and regenerates.
|
|
90
|
-
|
|
91
|
-
```bash
|
|
92
|
-
swoff add pwa
|
|
93
|
-
swoff add mutation-queue
|
|
94
|
-
swoff add auth
|
|
95
|
-
swoff add tag-invalidation
|
|
96
|
-
swoff add cross-tab
|
|
97
|
-
swoff add background-sync
|
|
98
|
-
swoff add push-notification
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
### `info`
|
|
102
|
-
|
|
103
|
-
Shows enabled features and file count, or detailed docs for a specific feature.
|
|
5
|
+
Swoff generates a **service worker** and **client-side utilities** that give your web app
|
|
6
|
+
server-state reactivity (stale-while-revalidate, auto-refetch, mutation tracking, dedup),
|
|
7
|
+
offline support, and PWA features — **zero runtime dependencies**, all generated and auditable.
|
|
104
8
|
|
|
105
9
|
```bash
|
|
106
|
-
swoff
|
|
107
|
-
swoff
|
|
108
|
-
swoff info mutation-queue # mutation queue details
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
### `clean`
|
|
112
|
-
|
|
113
|
-
Removes every generated file:
|
|
114
|
-
|
|
115
|
-
```bash
|
|
116
|
-
swoff clean
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
This deletes `swoff/`, `swoff.config.json`, `version.json`, and removes the SW generator from your build script.
|
|
120
|
-
|
|
121
|
-
---
|
|
122
|
-
|
|
123
|
-
## Configuration (`swoff.config.json`)
|
|
124
|
-
|
|
125
|
-
```json
|
|
126
|
-
{
|
|
127
|
-
"$schema": "https://swoff.netlify.app/schema/v1.json",
|
|
128
|
-
"enabled": true,
|
|
129
|
-
"framework": "react",
|
|
130
|
-
"build": {
|
|
131
|
-
"outputDir": "dist",
|
|
132
|
-
"swFilename": "sw"
|
|
133
|
-
},
|
|
134
|
-
"features": {
|
|
135
|
-
"pwa": {
|
|
136
|
-
"enabled": true,
|
|
137
|
-
"preventDefaultInstall": false
|
|
138
|
-
},
|
|
139
|
-
"serviceWorker": {
|
|
140
|
-
"version": {
|
|
141
|
-
"enabled": true,
|
|
142
|
-
"source": "from-package",
|
|
143
|
-
"minSupportedVersion": "1.0.0"
|
|
144
|
-
},
|
|
145
|
-
"autoUpdate": true,
|
|
146
|
-
"autoActivate": false,
|
|
147
|
-
"defaultStrategy": "cache-first",
|
|
148
|
-
"strategies": {
|
|
149
|
-
"/api/*": "network-first",
|
|
150
|
-
"/static/*": {
|
|
151
|
-
"strategy": "cache-first",
|
|
152
|
-
"maxCacheEntries": 50,
|
|
153
|
-
"maxCacheAge": 3600000
|
|
154
|
-
}
|
|
155
|
-
},
|
|
156
|
-
"cacheStrategy": "all",
|
|
157
|
-
"navigationPreload": true,
|
|
158
|
-
"maxCacheEntries": 100,
|
|
159
|
-
"maxCacheAge": 86400000,
|
|
160
|
-
"runtimeCacheName": "swoff-runtime",
|
|
161
|
-
"clearRuntimeOnUpdate": false,
|
|
162
|
-
"navigationMode": "spa",
|
|
163
|
-
"spaEntry": "/index.html"
|
|
164
|
-
},
|
|
165
|
-
"mutationQueue": false,
|
|
166
|
-
"backgroundSync": false,
|
|
167
|
-
"auth": {
|
|
168
|
-
"enabled": false,
|
|
169
|
-
"type": "bearer",
|
|
170
|
-
"refreshPath": "/api/refresh",
|
|
171
|
-
"userEndpoint": "/api/me"
|
|
172
|
-
},
|
|
173
|
-
"crossTabSync": true,
|
|
174
|
-
"tagInvalidation": true,
|
|
175
|
-
"pushNotifications": {
|
|
176
|
-
"enabled": false,
|
|
177
|
-
"vapidPublicKey": ""
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
| Field | Type | Default | Description |
|
|
184
|
-
|-------|------|---------|-------------|
|
|
185
|
-
| `$schema` | `string` | — | JSON Schema URL |
|
|
186
|
-
| `enabled` | `boolean` | `true` | Enable Swoff |
|
|
187
|
-
| `framework` | `"react"` \| `"vue"` \| `"svelte"` \| `"vanilla"` | auto | Your UI framework |
|
|
188
|
-
| `build.outputDir` | `string` | `"dist"` | Build output directory |
|
|
189
|
-
| `build.swFilename` | `string` | `"sw"` | Service worker filename prefix |
|
|
190
|
-
| `features.pwa.enabled` | `boolean` | `true` | PWA install + manifest |
|
|
191
|
-
| `features.pwa.preventDefaultInstall` | `boolean` | `false` | Prevent default install prompt |
|
|
192
|
-
| `features.serviceWorker.version.enabled` | `boolean` | `true` | Enable versioned SW |
|
|
193
|
-
| `features.serviceWorker.version.source` | `"from-package"` \| `"manual"` | `"from-package"` | Version source |
|
|
194
|
-
| `features.serviceWorker.version.value` | `string` | — | Manual version (required if source is `"manual"`) |
|
|
195
|
-
| `features.serviceWorker.version.minSupportedVersion` | `string` | `"0.0.0"` | Min supported SW version |
|
|
196
|
-
| `features.serviceWorker.autoUpdate` | `boolean` | `true` | Auto-update SW |
|
|
197
|
-
| `features.serviceWorker.autoActivate` | `boolean` | `false` | Auto-activate SW |
|
|
198
|
-
| `features.serviceWorker.defaultStrategy` | `string` | `"cache-first"` | Default cache strategy |
|
|
199
|
-
| `features.serviceWorker.strategies` | `object` | `{}` | Per-route caching strategies. Each value is a strategy name string, or `{ strategy, maxCacheEntries?, maxCacheAge? }` |
|
|
200
|
-
| `features.serviceWorker.cacheStrategy` | `"all"` \| `"explicit-only"` | `"all"` | When to apply caching strategies. `"all"`: every GET/HEAD; `"explicit-only"`: only if `X-SW-Cache-Strategy` header is present |
|
|
201
|
-
| `features.serviceWorker.navigationPreload` | `boolean` | `true` | Enable Navigation Preload API — reduces SW startup latency for navigation requests |
|
|
202
|
-
| `features.serviceWorker.maxCacheEntries` | `number` | — | Max runtime cache entries (0 = unlimited) |
|
|
203
|
-
| `features.serviceWorker.maxCacheAge` | `number` | — | Max runtime cache age in ms (0 = unlimited) |
|
|
204
|
-
| `features.serviceWorker.runtimeCacheName` | `string` | `"swoff-runtime"` | Runtime cache name |
|
|
205
|
-
| `features.serviceWorker.clearRuntimeOnUpdate` | `boolean` | `false` | Clear runtime cache on update |
|
|
206
|
-
| `features.serviceWorker.navigationMode` | `"spa"` \| `"default"` | `"spa"` | Navigation caching mode |
|
|
207
|
-
| `features.serviceWorker.spaEntry` | `string` | `"/index.html"` | SPA entry for nav fallback |
|
|
208
|
-
| `features.mutationQueue` | `boolean` | `false` | Offline write queue |
|
|
209
|
-
| `features.backgroundSync` | `boolean` | `false` | Background Sync API |
|
|
210
|
-
| `features.auth.enabled` | `boolean` | `false` | Auth module |
|
|
211
|
-
| `features.auth.type` | `"bearer"` \| `"cookie"` \| `"custom"` | `"bearer"` | Auth strategy |
|
|
212
|
-
| `features.auth.refreshPath` | `string` | `"/api/refresh"` | Token refresh endpoint |
|
|
213
|
-
| `features.auth.userEndpoint` | `string` | `"/api/me"` | Current user endpoint |
|
|
214
|
-
| `features.crossTabSync` | `boolean` | `false` | Cross-tab broadcast |
|
|
215
|
-
| `features.tagInvalidation` | `boolean` | `false` | Tag-based cache invalidation |
|
|
216
|
-
| `features.pushNotifications.enabled` | `boolean` | `false` | Push notification subscription management |
|
|
217
|
-
| `features.pushNotifications.vapidPublicKey` | `string` | `""` | VAPID public key (can also be passed at runtime) |
|
|
218
|
-
|
|
219
|
-
---
|
|
220
|
-
|
|
221
|
-
## Generated Files
|
|
222
|
-
|
|
223
|
-
```
|
|
224
|
-
swoff/
|
|
225
|
-
├── client-injector.ts # Orchestrator — wires all features together
|
|
226
|
-
│ Imports: sw/injector, pwa/install, mutation-queue
|
|
227
|
-
│ Exports: initServiceWorker()
|
|
228
|
-
│ Always generated
|
|
229
|
-
│
|
|
230
|
-
├── fetch-wrapper.ts # Unified fetch with caching, auth, offline queue
|
|
231
|
-
│ Imports: invalidation-tags, cache, auth/store, mutation-queue (conditional)
|
|
232
|
-
│ Exports: fetchWithCache(input, options?)
|
|
233
|
-
│ Always generated
|
|
234
|
-
│
|
|
235
|
-
├── cache.ts # Low-level cache invalidation
|
|
236
|
-
│ Exports: invalidateByTag(), invalidateByTags()
|
|
237
|
-
│ Generated when: tagInvalidation is true
|
|
238
|
-
│
|
|
239
|
-
├── invalidation-tags.ts # Tag generation helpers
|
|
240
|
-
│ Exports: generateTags(), invalidateUrl(), invalidateByMethod()
|
|
241
|
-
│ Generated when: tagInvalidation is true
|
|
242
|
-
│
|
|
243
|
-
├── mutation-queue.ts # Offline write queue
|
|
244
|
-
│ Exports: queueMutation(), processMutationQueue(), flushMutations(), getPendingCount()
|
|
245
|
-
│ Generated when: mutationQueue is true
|
|
246
|
-
│
|
|
247
|
-
├── push.ts # Push notification subscription management
|
|
248
|
-
│ Exports: subscribeToPush(), unsubscribeFromPush(), isSubscribed(), getPushSubscription(), requestNotificationPermission()
|
|
249
|
-
│ Generated when: pushNotifications.enabled is true
|
|
250
|
-
│
|
|
251
|
-
├── background-sync.ts # Background Sync API
|
|
252
|
-
│ Exports: syncWhenPossible(), retrySync()
|
|
253
|
-
│ Generated when: backgroundSync is true
|
|
254
|
-
│
|
|
255
|
-
├── swoff.d.ts # TypeScript declarations for all generated modules
|
|
256
|
-
│
|
|
257
|
-
├── auth/ # Generated when auth.enabled is true
|
|
258
|
-
│ ├── store.ts # Token/user persistence (memory + IndexedDB) + auth header helpers
|
|
259
|
-
│ │ Exports: setAuth(), getAuth(), clearAuth(), isAuthValid(), createAuthFromResponse(), ensureValidAuth(), withAuthHeaders()
|
|
260
|
-
│ ├── user.ts # User data caching
|
|
261
|
-
│ │ Exports: fetchCurrentUser(), getCachedUser(), cacheUser(), clearCachedUser()
|
|
262
|
-
│ └── state.ts # Online/offline × auth state detection
|
|
263
|
-
│ Exports: getAuthState()
|
|
264
|
-
│
|
|
265
|
-
├── hooks/ # Generated when framework is "react"
|
|
266
|
-
│ ├── useAuth.tsx # Reactive auth + connectivity state
|
|
267
|
-
│ ├── useCachedFetch.tsx # Auto-refetch on cache invalidation
|
|
268
|
-
│ ├── useMutationQueue.tsx # Queue status and sync results
|
|
269
|
-
│ ├── useSWUpdate.tsx # SW update management (+ useSWProgress in same file)
|
|
270
|
-
│ ├── useNetworkStatus.tsx # Reactive online/offline state
|
|
271
|
-
│ ├── useBackgroundSync.tsx # Background sync state and trigger
|
|
272
|
-
│ └── useCacheInvalidation.tsx # Cache invalidation helpers
|
|
273
|
-
│
|
|
274
|
-
├── pwa/
|
|
275
|
-
│ └── install.ts # PWA install prompt handling
|
|
276
|
-
│ Exports: setupPwaInstall(), isInstallable(), promptInstall()
|
|
277
|
-
│ Generated when: pwa.enabled is true
|
|
278
|
-
│
|
|
279
|
-
├── sw/
|
|
280
|
-
│ ├── template.js # Service worker source (runs in SW scope)
|
|
281
|
-
│ │ # Implements all 5 caching strategies in the SW
|
|
282
|
-
│ ├── injector.ts # SW registration logic
|
|
283
|
-
│ │ # Exports: initServiceWorker(), handleUpdateApproved(), skipWaiting()
|
|
284
|
-
│ └── generator.js # Build-time script — embeds asset hashes into SW
|
|
285
|
-
│
|
|
286
|
-
├── manifest.json
|
|
287
|
-
├── GUIDE.md # Full integration walkthrough
|
|
288
|
-
└── README.md # Quick reference
|
|
289
|
-
```
|
|
290
|
-
|
|
291
|
-
---
|
|
292
|
-
|
|
293
|
-
## Cache Strategies
|
|
294
|
-
|
|
295
|
-
The service worker applies a caching strategy to GET/HEAD requests based on `features.serviceWorker.cacheStrategy`:
|
|
296
|
-
|
|
297
|
-
| Mode | Behavior |
|
|
298
|
-
|------|----------|
|
|
299
|
-
| `"all"` (default) | All GET/HEAD requests go through the strategy system. Plain `fetch()` calls are cached by the SW just like `fetchWithCache()` calls. |
|
|
300
|
-
| `"explicit-only"` | Only requests with a `X-SW-Cache-Strategy` header are processed by the SW strategy system. Plain `fetch()` calls pass through the SW unmodified. `fetchWithCache()` sets this header automatically — use it for all API calls to ensure caching works. |
|
|
301
|
-
|
|
302
|
-
### Strategy resolution (3 tiers, highest to lowest priority)
|
|
303
|
-
|
|
304
|
-
1. **Per-request override** — set `strategy` or `staleWhileRevalidate` on any `fetchWithCache(options)`. Sends `X-SW-Strategy` header to the SW.
|
|
305
|
-
2. **URL pattern match** — `features.serviceWorker.strategies` object maps URL prefixes (e.g. `/api/*`) to strategies.
|
|
306
|
-
3. **Default** — `features.serviceWorker.defaultStrategy` (default: `"cache-first"`).
|
|
307
|
-
|
|
308
|
-
### Available strategies
|
|
309
|
-
|
|
310
|
-
| Strategy | Behavior | Best for |
|
|
311
|
-
|----------|----------|----------|
|
|
312
|
-
| `cache-first` | Return cached if available, else fetch + cache. Default. | Static assets, images, fonts, rarely-changing data |
|
|
313
|
-
| `network-first` | Try network. On success, cache. On failure, serve cache. | API endpoints, dynamic content where freshness matters |
|
|
314
|
-
| `stale-while-revalidate` | Return cached immediately, refresh cache in background. | Fast UI, non-critical data, content that can be slightly stale |
|
|
315
|
-
| `cache-only` | Serve from cache only. Returns 404 if missing. | Offline-critical assets that must always be available |
|
|
316
|
-
| `network-only` | Always fetch, never cache. | Sensitive or real-time data, payment flows |
|
|
317
|
-
|
|
318
|
-
### Request dispatch flow
|
|
319
|
-
|
|
320
|
-
Every GET/HEAD request goes through this flow in the SW:
|
|
321
|
-
|
|
322
|
-
```
|
|
323
|
-
navigation (SPA fallback) → precache hit? → strategy dispatch → pass-through
|
|
324
|
-
```
|
|
325
|
-
|
|
326
|
-
- **Navigation requests** (SPA mode): if no cached response is found, the SPA entry (`/index.html` by default) is served as a fallback.
|
|
327
|
-
- **Precache**: build assets cached at install time are checked first.
|
|
328
|
-
- **Strategy**: the resolved strategy (via the 3-tier priority above) determines how the request is cached and served.
|
|
329
|
-
- **Pass-through**: if no strategy matches, the request goes to the network.
|
|
330
|
-
|
|
331
|
-
---
|
|
332
|
-
|
|
333
|
-
## fetchWithCache API
|
|
334
|
-
|
|
335
|
-
A drop-in replacement for `fetch()` that communicates with the service worker about caching, handles offline mode, deduplicates in-flight requests, auto-generates cache tags, and auto-invalidates after mutations.
|
|
336
|
-
|
|
337
|
-
```js
|
|
338
|
-
import { fetchWithCache } from "./swoff/fetch-wrapper.js";
|
|
339
|
-
|
|
340
|
-
// Read — cached by the SW for offline access
|
|
341
|
-
const { response, fromCache } = await fetchWithCache("/api/todos");
|
|
342
|
-
const data = await response.json();
|
|
343
|
-
|
|
344
|
-
// Mutation — passes through, auto-invalidates cache tags
|
|
345
|
-
await fetchWithCache("/api/todos", {
|
|
346
|
-
method: "POST",
|
|
347
|
-
body: JSON.stringify({ title: "New task" }),
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
// POST used as a read (search, GraphQL) — override with type: "read"
|
|
351
|
-
await fetchWithCache("/api/search", {
|
|
352
|
-
method: "POST",
|
|
353
|
-
type: "read",
|
|
354
|
-
body: JSON.stringify({ query: "hello" }),
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
// With cache tags + stale-while-revalidate
|
|
358
|
-
const { response: staleRes, fromCache } = await fetchWithCache("/api/data", {
|
|
359
|
-
tags: ["data"],
|
|
360
|
-
staleWhileRevalidate: true,
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
// Auth + disable offline queue
|
|
364
|
-
const { response: userRes } = await fetchWithCache("/api/me", {
|
|
365
|
-
auth: true,
|
|
366
|
-
queueOffline: false,
|
|
367
|
-
});
|
|
368
|
-
```
|
|
369
|
-
|
|
370
|
-
### Options
|
|
371
|
-
|
|
372
|
-
All `RequestInit` fields are supported (`method`, `body`, `headers`, `credentials`, `signal`, etc.), plus:
|
|
373
|
-
|
|
374
|
-
| Option | Type | Default | Description |
|
|
375
|
-
|--------|------|---------|-------------|
|
|
376
|
-
| `tags` | `string[]` | auto-generated | Cache invalidation tags for this request |
|
|
377
|
-
| `staleWhileRevalidate` | `boolean` | `false` | Return cached immediately, refresh in background |
|
|
378
|
-
| `auth` | `boolean` | `false` | Attach auth token (uses `getAuth()`) |
|
|
379
|
-
| `queueOffline` | `boolean` | `true` | When offline, queue writes to IndexedDB for later replay |
|
|
380
|
-
| `invalidate` | `'auto' \| string[] \| false` | `'auto'` | Auto-invalidate cache tags after a successful mutation |
|
|
381
|
-
| `type` | `'read' \| 'mutation'` | auto-detected | Override read/mutation detection |
|
|
382
|
-
| `strategy` | `'cache-first' \| 'network-first' \| 'stale-while-revalidate' \| 'cache-only' \| 'network-only'` | — | Override caching strategy per-request (highest priority, overrides config strategies and default) |
|
|
383
|
-
|
|
384
|
-
### Behavior
|
|
385
|
-
|
|
386
|
-
- **Read vs mutation**: GET/HEAD → read (cached). POST/PUT/DELETE/PATCH → mutation (pass through). Override with `type: 'read'` or `type: 'mutation'`.
|
|
387
|
-
- **Offline reads**: returns cached response if available, throws if not cached.
|
|
388
|
-
- **Offline writes**: queues to IndexedDB (when `mutationQueue` enabled). Replays on `online` event. Disable per-request with `queueOffline: false`.
|
|
389
|
-
- **Dedup**: in-flight GETs to the same URL return a single promise (cloned response).
|
|
390
|
-
- **Auto-tags**: when `tagInvalidation` is enabled, tags are derived from the URL for read requests.
|
|
391
|
-
- **Auto-invalidate**: after a successful mutation, matching cache tags are invalidated so the SW re-fetches fresh data.
|
|
392
|
-
- **Auth**: when `auth: true`, attaches auth headers via `withAuthHeaders()` (supports bearer, cookie, and custom). Dispatches `sw-auth-unauthorized` on 401 and clears auth.
|
|
393
|
-
|
|
394
|
-
### Return value
|
|
395
|
-
|
|
396
|
-
```
|
|
397
|
-
{ response: Response, fromCache: boolean }
|
|
398
|
-
```
|
|
399
|
-
|
|
400
|
-
- `response`: the fetch Response (from cache or network)
|
|
401
|
-
- `fromCache`: `true` when the response was served from cache
|
|
402
|
-
|
|
403
|
-
In TypeScript, use the generic variant for typed responses:
|
|
404
|
-
|
|
405
|
-
```ts
|
|
406
|
-
const { response } = await fetchWithCache<Todo[]>("/api/todos");
|
|
407
|
-
const data: Todo[] = await response.json();
|
|
408
|
-
// data is typed as Todo[]
|
|
409
|
-
```
|
|
410
|
-
|
|
411
|
-
---
|
|
412
|
-
|
|
413
|
-
## Auth API
|
|
414
|
-
|
|
415
|
-
The auth module is generated when `features.auth.enabled` is `true`. It manages authentication state with a **memory-only token** (never persisted to disk) and optional IndexedDB caching for offline user display.
|
|
416
|
-
|
|
417
|
-
There is no separate auth fetch wrapper — `fetchWithCache` handles all auth types
|
|
418
|
-
natively. Just pass `{ auth: true }`:
|
|
419
|
-
|
|
420
|
-
```js
|
|
421
|
-
import { fetchWithCache } from "./swoff/fetch-wrapper.js";
|
|
422
|
-
|
|
423
|
-
// Attaches auth headers, bypasses SW cache for auth URLs, handles 401
|
|
424
|
-
const { response } = await fetchWithCache("/api/me", { auth: true });
|
|
425
|
-
const user = await response.json();
|
|
426
|
-
|
|
427
|
-
// Mutations too
|
|
428
|
-
await fetchWithCache("/api/todos", {
|
|
429
|
-
method: "POST",
|
|
430
|
-
auth: true,
|
|
431
|
-
body: JSON.stringify({ title: "New" }),
|
|
432
|
-
});
|
|
433
|
-
```
|
|
434
|
-
|
|
435
|
-
**What `auth: true` does:**
|
|
436
|
-
|
|
437
|
-
| Step | What happens |
|
|
438
|
-
|------|-------------|
|
|
439
|
-
| 1 | Calls `getAuth()` to retrieve the stored token/user |
|
|
440
|
-
| 2 | Calls `withAuthHeaders(headers, auth)` — injects Bearer token, cookie, or custom header based on `auth.type` in config |
|
|
441
|
-
| 3 | Marks auth endpoints (`/login`, `/logout`, `/register`, `refreshPath`, `userEndpoint`) as `"mutation"` strategy so the SW never caches them |
|
|
442
|
-
| 4 | For `auth.type: "cookie"`, sets `credentials: "include"` |
|
|
443
|
-
| 5 | On 401 response: calls `clearAuth()`, dispatches `sw-auth-unauthorized` event |
|
|
444
|
-
|
|
445
|
-
**Returns:** `{ response: Response, fromCache: boolean }` — same as any `fetchWithCache` call
|
|
446
|
-
|
|
447
|
-
### Auth functions
|
|
448
|
-
|
|
449
|
-
| Function | Arguments | Returns | Description |
|
|
450
|
-
|----------|-----------|---------|-------------|
|
|
451
|
-
| `setAuth(authData)` | `{ token?, user?, expiresAt? }` | `Promise<void>` | Store auth in memory, persist user to IndexedDB |
|
|
452
|
-
| `getAuth()` | — | `Promise<AuthData \| null>` | Retrieve auth from memory (or IndexedDB fallback after page refresh) |
|
|
453
|
-
| `clearAuth()` | — | `Promise<void>` | Clear memory + IndexedDB. Call on logout/401 |
|
|
454
|
-
| `isAuthValid(auth)` | `AuthData \| null` | `boolean` | Check existence + expiry (`expiresAt`). Returns `true` if no `expiresAt` set |
|
|
455
|
-
| `createAuthFromResponse(response)` | server login response | `AuthData` | **Edit this** to match your backend's login response shape |
|
|
456
|
-
| `ensureValidAuth()` | — | `Promise<AuthData \| null>` | Check expiry, refresh token via `refreshPath` if needed |
|
|
457
|
-
| `fetchCurrentUser()` | — | `Promise<Record<string, unknown> \| null>` | Fetch from `userEndpoint` and cache in IndexedDB |
|
|
458
|
-
| `getCachedUser()` | — | `Promise<Record<string, unknown> \| null>` | Load user from IndexedDB (available offline) |
|
|
459
|
-
| `cacheUser(user)` | user object | `Promise<void>` | Manually persist user data |
|
|
460
|
-
| `clearCachedUser()` | — | `Promise<void>` | Remove user from cache |
|
|
461
|
-
| `getAuthState()` | — | `Promise<{ authenticated, user, online }>` | Detect which of 4 states the app is in |
|
|
462
|
-
|
|
463
|
-
### Auth types
|
|
464
|
-
|
|
465
|
-
| `auth.type` | How auth headers are set | Notes |
|
|
466
|
-
|-------------|------------------------|-------|
|
|
467
|
-
| `"bearer"` | `Authorization: Bearer <token>` | Token in memory only. Re-login required after page refresh. Use `refreshPath` for token refresh. |
|
|
468
|
-
| `"cookie"` | No explicit header. `credentials: "include"` is set for all requests. | HttpOnly cookie handled by the server. |
|
|
469
|
-
| `"custom"` | **Edit the `withAuthHeaders` function** in `auth/store.ts` | Full control over header injection. |
|
|
470
|
-
|
|
471
|
-
---
|
|
472
|
-
|
|
473
|
-
## Mutation Queue
|
|
474
|
-
|
|
475
|
-
When the user is offline, write operations (POST/PUT/DELETE) are stored in IndexedDB. They replay automatically when the connection returns.
|
|
476
|
-
|
|
477
|
-
```js
|
|
478
|
-
import { queueMutation, getPendingCount } from "./swoff/mutation-queue.js";
|
|
479
|
-
|
|
480
|
-
// Offline write — stored and replayed when online
|
|
481
|
-
await queueMutation({
|
|
482
|
-
method: "POST",
|
|
483
|
-
url: "/api/todos",
|
|
484
|
-
body: { title: "Buy milk" },
|
|
485
|
-
tags: ["todos"],
|
|
486
|
-
});
|
|
487
|
-
|
|
488
|
-
// Show sync badge
|
|
489
|
-
const count = await getPendingCount();
|
|
10
|
+
npx @swoff/cli init # create swoff.config.json
|
|
11
|
+
npx @swoff/cli generate # generate service worker + client files
|
|
490
12
|
```
|
|
491
13
|
|
|
492
|
-
|
|
493
|
-
|----------|-------------|
|
|
494
|
-
| `queueMutation(mutation)` | Store a write for later sync |
|
|
495
|
-
| `processMutationQueue()` | Replay all queued writes. Runs automatically on `online` event |
|
|
496
|
-
| `flushMutations()` | Same as `processMutationQueue`. Call after re-login (queued mutations may have stale auth) |
|
|
497
|
-
| `getPendingCount()` | Number of mutations waiting to sync |
|
|
498
|
-
|
|
499
|
-
Generated when `features.mutationQueue` is `true`.
|
|
500
|
-
|
|
501
|
-
---
|
|
502
|
-
|
|
503
|
-
## Cache Invalidation
|
|
504
|
-
|
|
505
|
-
Tag-based invalidation keeps the service worker cache fresh after mutations.
|
|
14
|
+
Then import the single entry point:
|
|
506
15
|
|
|
507
16
|
```js
|
|
508
|
-
import {
|
|
509
|
-
|
|
510
|
-
// Tag reads automatically
|
|
511
|
-
const data = await fetchWithCache("/api/todos", {
|
|
512
|
-
tags: generateTags("/api/todos"),
|
|
513
|
-
});
|
|
514
|
-
|
|
515
|
-
// Invalidate after writing
|
|
516
|
-
await invalidateUrl("/api/todos/42");
|
|
517
|
-
```
|
|
518
|
-
|
|
519
|
-
When the SW receives an invalidation, it removes matching cache entries and attempts to background-refetch them. If the refetch fails (network error), the old cached entry is served with a stale-while-revalidate fallback.
|
|
520
|
-
|
|
521
|
-
| Function | Description |
|
|
522
|
-
|----------|-------------|
|
|
523
|
-
| `generateTags(url)` | Extract tags from URL path. e.g. `/api/todos/42` → `["todos", "todo:42"]` |
|
|
524
|
-
| `invalidateUrl(url)` | Extract tags from URL and invalidate all matching cache entries |
|
|
525
|
-
| `invalidateByTag(tag)` | Invalidate a single tag. Dispatches `cache-invalidated` event |
|
|
526
|
-
| `invalidateByTags(tags)` | Invalidate multiple tags |
|
|
527
|
-
|
|
528
|
-
Generated when `features.tagInvalidation` is `true`.
|
|
529
|
-
|
|
530
|
-
---
|
|
531
|
-
|
|
532
|
-
## PWA
|
|
533
|
-
|
|
534
|
-
Install prompt handling and manifest generation.
|
|
535
|
-
|
|
536
|
-
```js
|
|
537
|
-
import { isInstallable, promptInstall } from "./swoff/pwa/install.js";
|
|
538
|
-
|
|
539
|
-
// Show install button conditionally
|
|
540
|
-
if (isInstallable()) {
|
|
541
|
-
const { outcome } = await promptInstall();
|
|
542
|
-
}
|
|
17
|
+
import { initServiceWorker } from "swoff/client-injector.js";
|
|
18
|
+
initServiceWorker();
|
|
543
19
|
```
|
|
544
20
|
|
|
545
|
-
|
|
546
|
-
|----------|-------------|
|
|
547
|
-
| `setupPwaInstall()` | Listen for `beforeinstallprompt`/`appinstalled` events. Called automatically by `client-injector.ts` |
|
|
548
|
-
| `isInstallable()` | Check if install prompt is available |
|
|
549
|
-
| `promptInstall()` | Show the native install prompt. Returns `{ outcome }` |
|
|
21
|
+
## Features
|
|
550
22
|
|
|
551
|
-
|
|
23
|
+
| Category | Feature |
|
|
24
|
+
| ---------------------- | --------------------------------------------------- |
|
|
25
|
+
| Caching | 6 strategies |
|
|
26
|
+
| Mutations | useState-style tracking, offline queue, concurrency |
|
|
27
|
+
| Prefetching | Cache warming on hover/navigation |
|
|
28
|
+
| Dependent queries | enabled option, nullable URL |
|
|
29
|
+
| Query cancellation | AbortController integration |
|
|
30
|
+
| Real-time invalidation | SSE / WebSocket push events |
|
|
31
|
+
| Tag invalidation | URL-derived cache tags |
|
|
32
|
+
| Auth | memory-only tokens, bearer/cookie/custom |
|
|
33
|
+
| GraphQL | body-hash caching, auto-tags |
|
|
34
|
+
| PWA | install prompt, manifest, SW update hooks |
|
|
35
|
+
| Push notifications | VAPID subscription management |
|
|
36
|
+
| Background Sync | process mutations after tab close |
|
|
37
|
+
| Cross-tab sync | broadcast invalidation across tabs |
|
|
552
38
|
|
|
553
|
-
|
|
39
|
+
## CLI Commands
|
|
554
40
|
|
|
555
|
-
|
|
41
|
+
| Command | Description |
|
|
42
|
+
| ---------------- | ---------------------------------------------- |
|
|
43
|
+
| `init` | Create `swoff.config.json` with auto-detection |
|
|
44
|
+
| `generate` | Generate SW + all supporting files |
|
|
45
|
+
| `add <feature>` | Enable a feature and regenerate |
|
|
46
|
+
| `validate` | Validate `swoff.config.json` |
|
|
47
|
+
| `info [feature]` | Show summary or per-feature details |
|
|
48
|
+
| `clean` | Remove all generated files and config |
|
|
49
|
+
| `help [command]` | Show help for a specific command |
|
|
556
50
|
|
|
557
|
-
|
|
51
|
+
## Configuration
|
|
558
52
|
|
|
559
|
-
|
|
560
|
-
|
|
53
|
+
Create `swoff.config.json` with `swoff init`, then tweak as needed.
|
|
54
|
+
Full schema reference in [docs/CONFIG.md](./docs/CONFIG.md).
|
|
561
55
|
|
|
562
|
-
|
|
563
|
-
const sub = await subscribeToPush("YOUR_VAPID_PUBLIC_KEY");
|
|
564
|
-
if (sub) {
|
|
565
|
-
await fetch("/api/push/subscribe", {
|
|
566
|
-
method: "POST",
|
|
567
|
-
body: JSON.stringify(sub.toJSON()),
|
|
568
|
-
});
|
|
569
|
-
}
|
|
56
|
+
## API Reference
|
|
570
57
|
|
|
571
|
-
|
|
572
|
-
await unsubscribeFromPush();
|
|
573
|
-
```
|
|
58
|
+
Read the [API reference](./docs/API.md) for detailed information on each API.
|
|
574
59
|
|
|
575
|
-
|
|
576
|
-
|----------|-------------|
|
|
577
|
-
| `subscribeToPush(vapidPublicKey)` | Request permission and subscribe. Returns `PushSubscription` or `null` if denied |
|
|
578
|
-
| `unsubscribeFromPush()` | Unsubscribe and clear stored subscription from IndexedDB |
|
|
579
|
-
| `isSubscribed()` | Check if currently subscribed |
|
|
580
|
-
| `getPushSubscription()` | Get current `PushSubscription` object, or `null` |
|
|
581
|
-
| `requestNotificationPermission()` | Request notification permission only. Returns `boolean` |
|
|
60
|
+
## CLI Reference
|
|
582
61
|
|
|
583
|
-
|
|
62
|
+
Read the [CLI reference](./docs/CLI.md) for detailed information on each command.
|
|
584
63
|
|
|
585
|
-
|
|
64
|
+
## Architecture
|
|
586
65
|
|
|
587
|
-
|
|
66
|
+
Read the [architecture](./docs/ARCHITECTURE.md) for detailed information on the project's architecture.
|
|
588
67
|
|
|
589
|
-
|
|
68
|
+
## Comparison With Other Popular Tools
|
|
590
69
|
|
|
591
|
-
|
|
592
|
-
|------|---------|-------------|
|
|
593
|
-
| `useAuth()` | `{ authenticated, user, online }` | Auth + connectivity state. Listens to online/offline/auth changes |
|
|
594
|
-
| `useCachedFetch(url, options?)` | `{ data, error, loading, refetch }` | Fetches data (auto-parsed JSON), auto-refetches on tag invalidation events |
|
|
595
|
-
| `useMutationQueue()` | `{ pending, lastSync }` | Queue status (`pending` count) and last sync result (`lastSync.succeeded`, `lastSync.failed`) |
|
|
596
|
-
| `useSWUpdate()` | `{ updateStatus, currentVersion, availableVersion, forceUpdate, error, acceptUpdate, dismissUpdate }` | SW update management. `updateStatus` is one of `"idle"`, `"available"`, `"downloading"`, `"ready"` |
|
|
597
|
-
| `useSWProgress()` | `{ status, progress }` | Download progress during SW update. `progress` is `{ percent, downloaded, total }` |
|
|
598
|
-
| `usePushSubscription(vapidPublicKey)` | `{ subscribed, subscription, permission, loading, subscribe, unsubscribe }` | Push subscription state. `subscribe()`/`unsubscribe()` toggle notifications at runtime |
|
|
599
|
-
| `useNetworkStatus()` | `boolean` | Reactive online/offline state |
|
|
600
|
-
| `useBackgroundSync()` | `{ supported, registered, lastSync, triggerSync }` | Background sync support detection and manual sync trigger |
|
|
601
|
-
| `useCacheInvalidation()` | `{ invalidateByTag, invalidateByTags, invalidateUrl }` | Stable callback wrappers around cache invalidation functions |
|
|
70
|
+
Read the [comparison](./docs/COMPARISON.md) for a comprehensive comparison with popular tools.
|
|
602
71
|
|
|
603
|
-
|
|
604
|
-
import { useCachedFetch } from "./swoff/hooks/useCachedFetch.tsx";
|
|
72
|
+
## Ecosystem
|
|
605
73
|
|
|
606
|
-
|
|
607
|
-
const { data, loading } = useCachedFetch("/api/todos");
|
|
608
|
-
if (loading) return <Spinner />;
|
|
609
|
-
return <TodoList data={data} />;
|
|
610
|
-
}
|
|
611
|
-
```
|
|
74
|
+
Read the [ecosystem](./docs/ECOSYSTEM.md) to see how swoff integrates with any web framework you can work with.
|
|
612
75
|
|
|
613
76
|
---
|
|
614
77
|
|
|
615
|
-
##
|
|
616
|
-
|
|
617
|
-
After `swoff generate`, your `package.json` build script is updated to:
|
|
618
|
-
|
|
619
|
-
```json
|
|
620
|
-
"build": "vite build && node swoff/sw/generator.js"
|
|
621
|
-
```
|
|
622
|
-
|
|
623
|
-
The generator (`sw/generator.js`) runs after every build:
|
|
624
|
-
|
|
625
|
-
1. Reads `swoff.config.json` for output dir, version, and strategy config
|
|
626
|
-
2. Reads `sw/template.js` — the service worker source
|
|
627
|
-
3. Collects all built assets from the output directory
|
|
628
|
-
4. Replaces placeholders (`[[CACHE_NAME]]`, `[[ASSETS_LIST]]`, `[[AUTO_SKIP_WAITING]]`) with actual values
|
|
629
|
-
5. Writes the final versioned SW file (e.g. `dist/sw-v1.2.3.js`) and `version.json`
|
|
78
|
+
## Requirements
|
|
630
79
|
|
|
631
|
-
|
|
80
|
+
- **Node.js >= 18**
|
|
81
|
+
- A build tool with a `package.json` build script
|
|
82
|
+
- [Secure Context](https://developer.mozilla.org/en-US/docs/Web/Security/Defenses/Secure_Contexts) (required for service workers)
|
|
632
83
|
|
|
633
84
|
---
|
|
634
85
|
|
|
635
|
-
## Requirements
|
|
636
|
-
|
|
637
|
-
- Node.js >= 18
|
|
638
|
-
- A build tool with a `package.json` build script (Vite, Webpack, etc.)
|
|
639
|
-
- HTTPS or localhost (required for service workers)
|
|
640
|
-
|
|
641
86
|
## License
|
|
642
87
|
|
|
643
88
|
MIT
|
|
89
|
+
|
|
90
|
+
Source: [github.com/iamsuudi/swoff](https://github.com/iamsuudi/swoff)
|