Loading...
Loading...
Compare original and translation side by side
beforeinstallpromptbeforeinstallprompt┌─────────────────────────────────────────┐
│ Your App (React/Next.js) │
├─────────────────────────────────────────┤
│ Service Worker (sw.js) │
│ ┌─────────────┐ ┌─────────────────┐ │
│ │ Cache │ │ Network Fetch │ │
│ │ Storage │ │ Handling │ │
│ └─────────────┘ └─────────────────┘ │
├─────────────────────────────────────────┤
│ manifest.json │
│ (App identity, icons, display mode) │
└─────────────────────────────────────────┘┌─────────────────────────────────────────┐
│ 你的应用(React/Next.js) │
├─────────────────────────────────────────┤
│ Service Worker (sw.js) │
│ ┌─────────────┐ ┌─────────────────┐ │
│ │ 缓存(Cache) │ │ 网络请求处理 │ │
│ │ 存储(Storage)│ │ │ │
│ └─────────────┘ └─────────────────┘ │
├─────────────────────────────────────────┤
│ manifest.json │
│ (应用标识、图标、显示模式) │
└─────────────────────────────────────────┘{
"name": "Junkie Buds 4 Life",
"short_name": "JB4L",
"description": "Recovery support app",
"start_url": "/",
"scope": "/",
"display": "standalone",
"orientation": "portrait-primary",
"background_color": "#1a1410",
"theme_color": "#1a1410",
"icons": [
{
"src": "/icons/icon-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/icon-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/icon-maskable-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
],
"shortcuts": [
{
"name": "Find Meetings",
"short_name": "Meetings",
"url": "/meetings?source=shortcut",
"icons": [{ "src": "/icons/meetings-96.png", "sizes": "96x96" }]
}
]
}{
"name": "Junkie Buds 4 Life",
"short_name": "JB4L",
"description": "康复支持应用",
"start_url": "/",
"scope": "/",
"display": "standalone",
"orientation": "portrait-primary",
"background_color": "#1a1410",
"theme_color": "#1a1410",
"icons": [
{
"src": "/icons/icon-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/icon-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/icon-maskable-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
],
"shortcuts": [
{
"name": "查找会议",
"short_name": "会议",
"url": "/meetings?source=shortcut",
"icons": [{ "src": "/icons/meetings-96.png", "sizes": "96x96" }]
}
]
}| Mode | Description |
|---|---|
| No browser UI, full screen |
| App-like, no URL bar (recommended) |
| Some browser controls |
| Normal browser tab |
| 模式 | 描述 |
|---|---|
| 无浏览器UI,全屏显示 |
| 类应用样式,无地址栏(推荐) |
| 保留部分浏览器控件 |
| 普通浏览器标签页 |
<head>
<link rel="manifest" href="/manifest.json" />
<meta name="theme-color" content="#1a1410" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<link rel="apple-touch-icon" href="/icons/apple-touch-icon.png" />
</head><head>
<link rel="manifest" href="/manifest.json" />
<meta name="theme-color" content="#1a1410" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<link rel="apple-touch-icon" href="/icons/apple-touch-icon.png" />
</head>// lib/pwa.ts
export async function registerServiceWorker() {
if ('serviceWorker' in navigator) {
try {
const registration = await navigator.serviceWorker.register('/sw.js', {
scope: '/',
});
return registration;
} catch (error) {
console.error('SW registration failed:', error);
}
}
}
// Call on app mount
useEffect(() => {
registerServiceWorker();
}, []);// lib/pwa.ts
export async function registerServiceWorker() {
if ('serviceWorker' in navigator) {
try {
const registration = await navigator.serviceWorker.register('/sw.js', {
scope: '/',
});
return registration;
} catch (error) {
console.error('SW注册失败:', error);
}
}
}
// 在应用挂载时调用
useEffect(() => {
registerServiceWorker();
}, []);// public/sw.js
const CACHE_NAME = 'myapp-v1';
const STATIC_ASSETS = ['/', '/offline', '/manifest.json'];
// Install: Cache static assets
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => cache.addAll(STATIC_ASSETS))
);
self.skipWaiting();
});
// Activate: Clean old caches
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((keys) =>
Promise.all(keys.filter((k) => k !== CACHE_NAME).map((k) => caches.delete(k)))
)
);
self.clients.claim();
});
// Fetch: Handle requests (see references for strategies)
self.addEventListener('fetch', (event) => {
event.respondWith(handleFetch(event.request));
});See:for caching strategy implementationsreferences/service-worker-patterns.md
// public/sw.js
const CACHE_NAME = 'myapp-v1';
const STATIC_ASSETS = ['/', '/offline', '/manifest.json'];
// 安装:缓存静态资源
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => cache.addAll(STATIC_ASSETS))
);
self.skipWaiting();
});
// 激活:清理旧缓存
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((keys) =>
Promise.all(keys.filter((k) => k !== CACHE_NAME).map((k) => caches.delete(k)))
)
);
self.clients.claim();
});
// 网络请求:处理请求(缓存策略实现请参考参考文献)
self.addEventListener('fetch', (event) => {
event.respondWith(handleFetch(event.request));
});参考:中的缓存策略实现references/service-worker-patterns.md
| Strategy | Best For | Tradeoff |
|---|---|---|
| Cache-First | Static assets, fonts, images | Stale until cache updated |
| Network-First | API data, user content | Slower, needs connectivity |
| Stale-While-Revalidate | Balance freshness/speed | Background updates |
| Network-Only | Auth, real-time data | No offline support |
| Cache-Only | Versioned assets | Never updates |
See:for full implementationsreferences/service-worker-patterns.md
| 策略 | 适用场景 | 权衡点 |
|---|---|---|
| Cache-First | 静态资源、字体、图片 | 缓存未更新前内容可能过时 |
| Network-First | API数据、用户内容 | 速度较慢,需要网络连接 |
| Stale-While-Revalidate | 平衡新鲜度与速度 | 在后台更新内容 |
| Network-Only | 认证请求、实时数据 | 无离线支持 |
| Cache-Only | 版本化资源 | 永不自动更新 |
参考:中的完整实现references/service-worker-patterns.md
beforeinstallprompt// Basic pattern
const [deferredPrompt, setDeferredPrompt] = useState(null);
useEffect(() => {
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault();
setDeferredPrompt(e);
});
}, []);
const handleInstall = async () => {
if (deferredPrompt) {
deferredPrompt.prompt();
const { outcome } = await deferredPrompt.userChoice;
// outcome: 'accepted' or 'dismissed'
}
};See:for fullreferences/install-prompt.mdhook and componentusePWAInstall
beforeinstallprompt// 基础实现
const [deferredPrompt, setDeferredPrompt] = useState(null);
useEffect(() => {
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault();
setDeferredPrompt(e);
});
}, []);
const handleInstall = async () => {
if (deferredPrompt) {
deferredPrompt.prompt();
const { outcome } = await deferredPrompt.userChoice;
// outcome值为'accepted'或'dismissed'
}
};参考:中的完整references/install-prompt.md钩子和组件实现usePWAInstall
useOnlineStatusSee:for implementationsreferences/offline-handling.md
useOnlineStatus参考:中的实现方案references/offline-handling.md
// In Service Worker
self.addEventListener('sync', (event) => {
if (event.tag === 'sync-data') {
event.waitUntil(syncPendingData());
}
});
// In App - trigger sync
const registration = await navigator.serviceWorker.ready;
await registration.sync.register('sync-data');See:for full IndexedDB integrationreferences/background-sync.md
// 在Service Worker中
self.addEventListener('sync', (event) => {
if (event.tag === 'sync-data') {
event.waitUntil(syncPendingData());
}
});
// 在应用中 - 触发同步
const registration = await navigator.serviceWorker.ready;
await registration.sync.register('sync-data');参考:中的IndexedDB集成完整实现references/background-sync.md
// Basic pattern
registration.addEventListener('updatefound', () => {
const newWorker = registration.installing;
newWorker?.addEventListener('statechange', () => {
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
// New version available - show update prompt
}
});
});See:forreferences/update-flow.mdhook and update strategiesusePWAUpdate
// 基础实现
registration.addEventListener('updatefound', () => {
const newWorker = registration.installing;
newWorker?.addEventListener('statechange', () => {
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
// 新版本可用 - 显示更新提示
}
});
});参考:中的references/update-flow.md钩子和更新策略usePWAUpdate
output: 'export'See:for detailed configurationsreferences/nextjs-integration.md
output: 'export'参考:中的详细配置references/nextjs-integration.md
| Task | Solution |
|---|---|
| Check if installed | |
| Force SW update | |
| Clear all caches | |
| Check online | |
| Get SW registration | |
| Skip waiting | |
| Take control | |
| 任务 | 解决方案 |
|---|---|
| 检查应用是否已安装 | |
| 强制Service Worker更新 | |
| 清除所有缓存 | |
| 检查网络状态 | |
| 获取Service Worker注册实例 | |
| 跳过等待直接激活 | 在Service Worker中调用 |
| 立即控制所有客户端 | 在Service Worker中调用 |
/references/service-worker-patterns.mdinstall-prompt.mdusePWAInstalloffline-handling.mdbackground-sync.mdupdate-flow.mdnextjs-integration.md/references/service-worker-patterns.mdinstall-prompt.mdusePWAInstalloffline-handling.mdbackground-sync.mdupdate-flow.mdnextjs-integration.md