Astro 集成 Service Worker 教程
通过此方案,你可以实现:
1、自动缓存所有静态资源(本地 + 指定外部域)
2、版本化缓存管理(更新即清理)
3、用户友好的更新提示
4、跨域资源支持
5、生产级缓存策略
第 1 步
在public
目录下新建sw.js
:
// 定义版本标识符和缓存名称
const SW_VERSION = 'v1'; // 更新版本号以触发缓存更新
const CACHE_NAME = `${SW_VERSION}-static-cache`;
// 需要预缓存的本地资源模式(支持通配符)
const LOCAL_ASSETS = [
'/assets/**',
'/*.js',
'/*.css',
'/*.woff2'
];
// 需要缓存的外部域名路径(完整前缀匹配)
const EXTERNAL_ASSETS = [
'https://Your-CDN-Domain/'
];
// 匹配通配符路径
function matchWildcard(pattern, path) {
const regexPattern = pattern
.replace(/\*\*/g, '.*') // ** 匹配任意层级目录
.replace(/\*/g, '[^/]*') // * 匹配单层目录/文件
.replace(/\//g, '\\/');
return new RegExp(`^${regexPattern}$`).test(path);
}
// 检查是否匹配任意通配符模式
function shouldCache(url) {
const path = new URL(url).pathname;
return LOCAL_ASSETS.some(pattern => matchWildcard(pattern, path));
}
// ====================== 安装阶段 ======================
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => {
// 预缓存固定资源
const staticAssets = LOCAL_ASSETS.filter(path => !path.includes('*'));
return cache.addAll(staticAssets);
})
.then(() => self.skipWaiting())
);
});
// ====================== 激活阶段 ======================
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then(existingCaches => {
return Promise.all(
existingCaches.map(cache => {
if (cache !== CACHE_NAME) return caches.delete(cache);
})
);
}).then(() => self.clients.claim())
);
});
// ====================== 请求拦截 ======================
self.addEventListener('fetch', (event) => {
const url = event.request.url;
// 处理本地资源(缓存优先)
if (url.startsWith(self.location.origin)) {
// 检查是否匹配通配符规则
if (shouldCache(url)) {
event.respondWith(
caches.match(event.request)
.then(cached => cached || fetchAndCache(event.request))
);
}
}
// 处理目标外部资源(网络优先,失败回退缓存)
else if (EXTERNAL_ASSETS.some(domain => url.startsWith(domain))) {
event.respondWith(
fetchWithFallback(event.request)
);
}
});
// ====================== 策略函数 ======================
async function fetchAndCache(request) {
try {
const response = await fetch(request);
if (response.ok) {
const cache = await caches.open(CACHE_NAME);
cache.put(request, response.clone());
}
return response;
} catch (err) {
const cached = await caches.match(request);
if (cached) return cached;
throw err;
}
}
async function fetchWithFallback(request) {
try {
const response = await fetch(request);
if (response.ok) {
const cache = await caches.open(CACHE_NAME);
cache.put(request, response.clone());
}
return response;
} catch (err) {
return caches.match(request);
}
}
第 2 步
在src/components
目录下新建SWController.astro
:
<!-- 使用现代 Toast 提示代替原生 alert -->
<script is:inline>
if ('serviceWorker' in navigator) {
const showUpdateToast = () => {
const toast = document.createElement('div');
toast.style = 'position:fixed; bottom:20px; right:20px; padding:16px; background:#333; color:white; border-radius:8px;';
toast.innerHTML = `
新版本可用!<button
style="margin-left:12px; padding:4px 8px; background:#666; border:none; color:white;"
onclick="window.location.reload()"
>立即刷新</button>
`;
document.body.appendChild(toast);
};
// 注册并监听更新
navigator.serviceWorker.register('/sw.js').then(reg => {
reg.addEventListener('updatefound', () => {
const newWorker = reg.installing;
newWorker.addEventListener('statechange', () => {
if (newWorker.state === 'activated' && !navigator.serviceWorker.controller) {
showUpdateToast(); // 首次安装不提示
} else if (newWorker.state === 'activated') {
showUpdateToast(); // 检测到更新时提示
}
});
});
});
// 检查服务器端是否有更新(每 2 小时)
setInterval(() => navigator.serviceWorker.ready.then(reg => reg.update()), 7200_000);
}
</script>
第 3 步
在布局或页面中(比如Footer.astro
)引入组件:
---
import SWController from '../components/SWController.astro';
---
<body>
<!-- 页面内容 -->
<slot />
<SWController />
</body>