283 lines
7.8 KiB
JavaScript
283 lines
7.8 KiB
JavaScript
|
|
// Service Worker for performance optimization
|
|||
|
|
const CACHE_NAME = 'n8n-demo-v1';
|
|||
|
|
const STATIC_CACHE = 'static-v1';
|
|||
|
|
const DYNAMIC_CACHE = 'dynamic-v1';
|
|||
|
|
|
|||
|
|
// 需要缓存的静态资源
|
|||
|
|
const STATIC_FILES = [
|
|||
|
|
'/',
|
|||
|
|
'/index.html',
|
|||
|
|
'/pages/overview.html',
|
|||
|
|
'/pages/exhibition.html',
|
|||
|
|
'/pages/marketing.html',
|
|||
|
|
'/pages/operation.html',
|
|||
|
|
'/pages/budget.html',
|
|||
|
|
'/pages/risk.html',
|
|||
|
|
'/js/nav-component.js',
|
|||
|
|
'/js/back-to-top.js',
|
|||
|
|
'/js/mobile-optimize.js',
|
|||
|
|
'/js/performance-optimizer.js',
|
|||
|
|
'/js/page-loader.js',
|
|||
|
|
'/css/styles.css',
|
|||
|
|
'/css/animations.css'
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
// 缓存策略配置
|
|||
|
|
const CACHE_STRATEGIES = {
|
|||
|
|
// 静态资源:缓存优先
|
|||
|
|
static: {
|
|||
|
|
pattern: /\.(js|css|woff|woff2|ttf|eot)$/,
|
|||
|
|
strategy: 'cacheFirst',
|
|||
|
|
maxAge: 30 * 24 * 60 * 60 * 1000 // 30天
|
|||
|
|
},
|
|||
|
|
// HTML页面:网络优先,失败时使用缓存
|
|||
|
|
html: {
|
|||
|
|
pattern: /\.html$/,
|
|||
|
|
strategy: 'networkFirst',
|
|||
|
|
maxAge: 24 * 60 * 60 * 1000 // 1天
|
|||
|
|
},
|
|||
|
|
// 图片:缓存优先
|
|||
|
|
images: {
|
|||
|
|
pattern: /\.(png|jpg|jpeg|gif|svg|webp)$/,
|
|||
|
|
strategy: 'cacheFirst',
|
|||
|
|
maxAge: 7 * 24 * 60 * 60 * 1000 // 7天
|
|||
|
|
},
|
|||
|
|
// 外部资源:网络优先
|
|||
|
|
external: {
|
|||
|
|
pattern: /^https:\/\/(fonts\.googleapis\.com|cdnjs\.cloudflare\.com|cdn\.jsdelivr\.net)/,
|
|||
|
|
strategy: 'networkFirst',
|
|||
|
|
maxAge: 7 * 24 * 60 * 60 * 1000 // 7天
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 安装事件 - 缓存静态资源
|
|||
|
|
self.addEventListener('install', event => {
|
|||
|
|
console.log('SW: Install event');
|
|||
|
|
|
|||
|
|
event.waitUntil(
|
|||
|
|
caches.open(STATIC_CACHE)
|
|||
|
|
.then(cache => {
|
|||
|
|
console.log('SW: Caching static files');
|
|||
|
|
return cache.addAll(STATIC_FILES);
|
|||
|
|
})
|
|||
|
|
.then(() => {
|
|||
|
|
console.log('SW: Static files cached');
|
|||
|
|
return self.skipWaiting();
|
|||
|
|
})
|
|||
|
|
.catch(err => {
|
|||
|
|
console.error('SW: Failed to cache static files', err);
|
|||
|
|
})
|
|||
|
|
);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 激活事件 - 清理旧缓存
|
|||
|
|
self.addEventListener('activate', event => {
|
|||
|
|
console.log('SW: Activate event');
|
|||
|
|
|
|||
|
|
event.waitUntil(
|
|||
|
|
caches.keys()
|
|||
|
|
.then(cacheNames => {
|
|||
|
|
return Promise.all(
|
|||
|
|
cacheNames.map(cacheName => {
|
|||
|
|
// 删除旧版本的缓存
|
|||
|
|
if (cacheName !== STATIC_CACHE && cacheName !== DYNAMIC_CACHE) {
|
|||
|
|
console.log('SW: Deleting old cache:', cacheName);
|
|||
|
|
return caches.delete(cacheName);
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
);
|
|||
|
|
})
|
|||
|
|
.then(() => {
|
|||
|
|
console.log('SW: Old caches cleaned');
|
|||
|
|
return self.clients.claim();
|
|||
|
|
})
|
|||
|
|
);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 获取事件 - 实现缓存策略
|
|||
|
|
self.addEventListener('fetch', event => {
|
|||
|
|
// 只处理 GET 请求
|
|||
|
|
if (event.request.method !== 'GET') {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const url = new URL(event.request.url);
|
|||
|
|
|
|||
|
|
// 跳过 chrome-extension 等协议
|
|||
|
|
if (!url.protocol.startsWith('http')) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
event.respondWith(handleFetch(event.request));
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 处理请求的核心函数
|
|||
|
|
async function handleFetch(request) {
|
|||
|
|
const url = new URL(request.url);
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
// 确定缓存策略
|
|||
|
|
const strategy = getCacheStrategy(request);
|
|||
|
|
|
|||
|
|
switch (strategy.strategy) {
|
|||
|
|
case 'cacheFirst':
|
|||
|
|
return await cacheFirst(request, strategy);
|
|||
|
|
case 'networkFirst':
|
|||
|
|
return await networkFirst(request, strategy);
|
|||
|
|
case 'networkOnly':
|
|||
|
|
return await fetch(request);
|
|||
|
|
default:
|
|||
|
|
return await networkFirst(request, strategy);
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('SW: Fetch failed:', error);
|
|||
|
|
|
|||
|
|
// 返回离线页面或缓存的响应
|
|||
|
|
return await getOfflineResponse(request);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取缓存策略
|
|||
|
|
function getCacheStrategy(request) {
|
|||
|
|
const url = request.url;
|
|||
|
|
|
|||
|
|
for (const [key, config] of Object.entries(CACHE_STRATEGIES)) {
|
|||
|
|
if (config.pattern.test(url)) {
|
|||
|
|
return config;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 默认策略
|
|||
|
|
return { strategy: 'networkFirst', maxAge: 24 * 60 * 60 * 1000 };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 缓存优先策略
|
|||
|
|
async function cacheFirst(request, strategy) {
|
|||
|
|
const cacheName = isStaticResource(request) ? STATIC_CACHE : DYNAMIC_CACHE;
|
|||
|
|
const cache = await caches.open(cacheName);
|
|||
|
|
const cachedResponse = await cache.match(request);
|
|||
|
|
|
|||
|
|
if (cachedResponse) {
|
|||
|
|
// 检查缓存是否过期
|
|||
|
|
const cachedDate = new Date(cachedResponse.headers.get('date') || Date.now());
|
|||
|
|
const now = new Date();
|
|||
|
|
|
|||
|
|
if (now - cachedDate < strategy.maxAge) {
|
|||
|
|
return cachedResponse;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const networkResponse = await fetch(request);
|
|||
|
|
|
|||
|
|
if (networkResponse.ok) {
|
|||
|
|
// 缓存新响应
|
|||
|
|
await cache.put(request, networkResponse.clone());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return networkResponse;
|
|||
|
|
} catch (error) {
|
|||
|
|
// 网络失败,返回缓存(即使过期)
|
|||
|
|
if (cachedResponse) {
|
|||
|
|
return cachedResponse;
|
|||
|
|
}
|
|||
|
|
throw error;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 网络优先策略
|
|||
|
|
async function networkFirst(request, strategy) {
|
|||
|
|
const cacheName = isStaticResource(request) ? STATIC_CACHE : DYNAMIC_CACHE;
|
|||
|
|
const cache = await caches.open(cacheName);
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const networkResponse = await fetch(request);
|
|||
|
|
|
|||
|
|
if (networkResponse.ok) {
|
|||
|
|
// 缓存响应
|
|||
|
|
await cache.put(request, networkResponse.clone());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return networkResponse;
|
|||
|
|
} catch (error) {
|
|||
|
|
// 网络失败,尝试从缓存获取
|
|||
|
|
const cachedResponse = await cache.match(request);
|
|||
|
|
|
|||
|
|
if (cachedResponse) {
|
|||
|
|
return cachedResponse;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
throw error;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 判断是否为静态资源
|
|||
|
|
function isStaticResource(request) {
|
|||
|
|
const url = new URL(request.url);
|
|||
|
|
return STATIC_FILES.some(file => url.pathname.endsWith(file));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取离线响应
|
|||
|
|
async function getOfflineResponse(request) {
|
|||
|
|
const url = new URL(request.url);
|
|||
|
|
|
|||
|
|
// 如果是 HTML 请求,返回主页
|
|||
|
|
if (request.headers.get('accept')?.includes('text/html')) {
|
|||
|
|
const cache = await caches.open(STATIC_CACHE);
|
|||
|
|
return await cache.match('/index.html') || new Response('Offline', { status: 503 });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 其他资源返回 503
|
|||
|
|
return new Response('Offline', { status: 503 });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 监听消息事件
|
|||
|
|
self.addEventListener('message', event => {
|
|||
|
|
if (event.data && event.data.type === 'SKIP_WAITING') {
|
|||
|
|
self.skipWaiting();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (event.data && event.data.type === 'GET_VERSION') {
|
|||
|
|
event.ports[0].postMessage({ version: CACHE_NAME });
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 后台同步(如果支持)
|
|||
|
|
self.addEventListener('sync', event => {
|
|||
|
|
if (event.tag === 'background-sync') {
|
|||
|
|
event.waitUntil(doBackgroundSync());
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
async function doBackgroundSync() {
|
|||
|
|
// 这里可以执行后台同步任务
|
|||
|
|
console.log('SW: Background sync');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 推送通知(如果需要)
|
|||
|
|
self.addEventListener('push', event => {
|
|||
|
|
if (event.data) {
|
|||
|
|
const data = event.data.json();
|
|||
|
|
const options = {
|
|||
|
|
body: data.body,
|
|||
|
|
icon: data.icon || '/favicon.ico',
|
|||
|
|
badge: '/badge.png',
|
|||
|
|
vibrate: [200, 100, 200],
|
|||
|
|
data: data.data,
|
|||
|
|
actions: data.actions
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
event.waitUntil(
|
|||
|
|
self.registration.showNotification(data.title, options)
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 通知点击事件
|
|||
|
|
self.addEventListener('notificationclick', event => {
|
|||
|
|
event.notification.close();
|
|||
|
|
|
|||
|
|
event.waitUntil(
|
|||
|
|
clients.openWindow(event.notification.data?.url || '/')
|
|||
|
|
);
|
|||
|
|
});
|