1// Service Worker Ð´Ð»Ñ Ð¾Ð¿ÑимизиÑованного кеÑиÑÐ¾Ð²Ð°Ð½Ð¸Ñ ÑеÑÑÑÑов 2// 3// ÐеÑÑÐ¸Ñ ÐºÐµÑа: пÑи пÑавке ÐЮÐÐÐÐ Ñайла из CRITICAL_RESOURCES или кÑиÑиÑной 4// логики SW поднÑÑÑ SW_VERSION на 1. ÐÑо ÑÑиггеÑÐ¸Ñ install â activate, 5// activate ÑноÑÐ¸Ñ Ð²Ñе каÑи Ñ Ð´ÑÑгим именем, install заново пÑе-кеÑиÑÑÐµÑ 6// CRITICAL_RESOURCES Ñвежими. Ðдна ÑоÑка пÑавки вмеÑÑо ÑÑÑÑ Ð·Ð°Ñ Ð°ÑдкоженнÑÑ . 7// 8// ÐÐÐÐÐÐÐÐ: ÑÐ°Ð¹Ð»Ñ Ñапки ÑайÑа (/css/header.css, /js/header-include.js) 9// намеÑенно ÐСÐÐЮЧÐÐЫ из CRITICAL_RESOURCES и обÑабаÑÑваÑÑÑÑ ÑÑÑаÑегией 10// network-only (Ñм. HEADER_PATTERNS ниже). СеÑÐ²ÐµÑ Ð¾ÑдаÑÑ Ð¸Ñ Ñ Cache-Control: 11// no-cache, must-revalidate. РанÑÑе они пÑе-кеÑиÑовалиÑÑ Ð·Ð´ÐµÑÑ ÐºÐ°Ðº bare-URL 12// без ?v=..., замоÑаживалиÑÑ Ð² STATIC_CACHE до бампа веÑÑии и пÑи лÑбом 13// fallback (caches.match) ломали вÑÑÑÑкÑ. ТепеÑÑ Ð¿Ñавка Ñапки болÑÑе ÐÐ 14// ÑÑебÑÐµÑ Ð±Ð°Ð¼Ð¿ SW_VERSION â она ÑеÑÑÑ Ð½Ð°Ð¿ÑÑмÑÑ Ñ ÑеÑвеÑа ÑеÑез 304. 15const SW_VERSION = 'v5'; 16const CACHE_NAME = 'fittin-' + SW_VERSION; 17const STATIC_CACHE = 'fittin-static-' + SW_VERSION; 18const DYNAMIC_CACHE = 'fittin-dynamic-' + SW_VERSION; 19 20// ÐÑиÑиÑеÑки важнÑе ÑеÑÑÑÑÑ Ð´Ð»Ñ Ð¾Ñлайн ÑабоÑÑ 21const CRITICAL_RESOURCES = [ 22 '/', 23 '/main.html',
24 '/AIPhotoEditor.html', 25 '/css/fonts.css', 26 '/css/hero-block.css', 27 '/css/main.css', 28 '/css/ai-photo-editor.css', 29 '/js/site-config.js', 30 '/fonts/Gilroy-Regular.woff', 31 '/fonts/Gilroy-SemiBold.woff', 32 '/fonts/Gilroy-Bold.woff', 33 '/images/LogoFittin.webp', 34 '/images/Background-1-block.webp', 35 '/images/Novii.webp' 36]; 37 38// Шапка ÑайÑа â вÑегда из ÑеÑи, мимо лÑбого кеÑа SW. 39// ÐÑÑи ÑопоÑÑавлÑÑÑÑÑ Ñ pathname (без query string). 40const HEADER_PATTERNS = [ 41 /^\/css\/header\.css$/, 42 /^\/js\/header-include\.js$/ 43]; 44 45// РеÑÑÑÑÑ, коÑоÑÑе кеÑиÑÑÑÑÑÑ Ð´Ð¾Ð»Ð³Ð¾ (ÑÑаÑиÑеÑкие) 46const LONG_CACHE_PATTERNS = [ 47 /\.(woff|woff2|ttf|eot|otf)$/, // ШÑиÑÑÑ 48 /\.(jpg|jpeg|png|gif|webp|svg|ico)$/, // ÐзобÑÐ°Ð¶ÐµÐ½Ð¸Ñ 49 /\.(mp4|webm|avi|mov)$/ // Ðидео 50]; 51 52// РеÑÑÑÑÑ, коÑоÑÑе кеÑиÑÑÑÑÑÑ ÑÑедне (обновлÑемÑе) 53const MEDIUM_CACHE_PATTERNS = [ 54 /\.(css|js)$/, // CSS и JS 55 /\.(pdf|doc|docx)$/ // ÐокÑменÑÑ 56]; 57 58// ÐакÑималÑнÑй ÑÐ°Ð·Ð¼ÐµÑ Ð´Ð¸Ð½Ð°Ð¼Ð¸ÑеÑкого кеÑа 59const MAX_DYNAMIC_CACHE_SIZE = 50; 60 61// ФÑнкÑÐ¸Ñ Ð´Ð»Ñ Ð¾ÑиÑÑки ÑÑаÑого кеÑа 62const cleanupCache = async (cacheName, maxSize) => { 63 const cache = await caches.open(cacheName); 64 const keys = await cache.keys(); 65 if (keys.length > maxSize) { 66 const keysToDelete = keys.slice(0, keys.length - maxSize); 67 await Promise.all(keysToDelete.map(key => cache.delete(key))); 68 } 69}; 70 71// УÑÑановка Service Worker 72self.addEventListener('install', event => { 73 event.waitUntil( 74 caches.open(STATIC_CACHE) 75 .then(cache => { 76 return cache.addAll(CRITICAL_RESOURCES); 77 }) 78 ); 79 self.skipWaiting(); 80}); 81 82// ÐкÑиваÑÐ¸Ñ Service Worker 83self.addEventListener('activate', event => { 84 event.waitUntil( 85 Promise.all([ 86 // УдалÑем ÑÑаÑÑе кеÑи 87 caches.keys().then(cacheNames => { 88 return Promise.all( 89 cacheNames.map(cacheName => { 90 if (cacheName !== STATIC_CACHE && cacheName !== DYNAMIC_CACHE) { 91 return caches.delete(cacheName); 92 } 93 }) 94 ); 95 }), 96 // ÐÑиÑаем динамиÑеÑкий ÐºÐµÑ ÐµÑли он ÑлиÑком болÑÑой 97 cleanupCache(DYNAMIC_CACHE, MAX_DYNAMIC_CACHE_SIZE) 98 ]) 99 ); 100 self.clients.claim(); 101}); 102 103// ÐпÑеделÑем ÑÑÑаÑÐµÐ³Ð¸Ñ ÐºÐµÑиÑÐ¾Ð²Ð°Ð½Ð¸Ñ Ð´Ð»Ñ URL. 104// ÐÐÐÐÐ: ÑабоÑаем Ñ pathname (без query string). СÑаÑÑй код ÑеÑÑиÑовал regex 105// пÑоÑив полного url вида "https://fittin.ru/css/header.css?v=3.3" â паÑÑеÑн 106// /\.(css|js)$/ не маÑÑил (URL заканÑиваеÑÑÑ Ð½Ð° "?v=3.3"), CSS/JS пÑоваливалиÑÑ 107// в default-веÑÐºÑ cache-first и пÑи пеÑвом запÑоÑе кеÑиÑовалиÑÑ Ð½Ð°Ð²Ñегда. 108const getCacheStrategy = (url) => { 109 let pathname = url; 110 try { 111 pathname = new URL(url).pathname; 112 } catch (_e) { 113 // url мог пÑийÑи без origin â оÑÑавим как еÑÑÑ, паÑÑеÑÐ½Ñ ÑабоÑаÑÑ Ð¸ Ñак 114 } 115 116 // Шапка ÑайÑа â никогда не кеÑиÑÑеÑÑÑ Ð² SW. СеÑÐ²ÐµÑ Ñам оÑдаÑÑ 304 ÑеÑез ETag. 117 if (HEADER_PATTERNS.some(pattern => pattern.test(pathname))) { 118 return 'network-only'; 119 } 120 121 // API запÑоÑÑ - вÑегда ÑеÑÑ 122 if (pathname.includes('/api/') || pathname.includes('/news/') || pathname.includes('/sync/')) { 123 return 'network-only'; 124 } 125 126 // СÑаÑиÑеÑкие ÑеÑÑÑÑÑ Ñ Ð´Ð¾Ð»Ð³Ð¸Ð¼ кеÑиÑованием 127 if (LONG_CACHE_PATTERNS.some(pattern => pattern.test(pathname))) { 128 return 'cache-first'; 129 } 130 131 // CSS/JS - ÑеÑÑ Ñ fallback на ÐºÐµÑ 132 if (MEDIUM_CACHE_PATTERNS.some(pattern => pattern.test(pathname))) { 133 return 'network-first'; 134 } 135 136 // HTML и ЧÐУ без ÑаÑÑиÑÐµÐ½Ð¸Ñ (напÑ. /all_cases, /o-nas): инаÑе ÑÑабаÑÑвал Ð±Ñ cache-first ниже â 137 // веÑнÑй ÑÑаÑÑй докÑÐ¼ÐµÐ½Ñ Ð¿Ð¾Ñле пеÑвого визиÑа. РаÑÑиÑÐµÐ½Ð¸Ñ ÑеÑÑÑÑов Ñже оÑÑилÑÑÑÐ¾Ð²Ð°Ð½Ñ Ð²ÑÑе. 138 if (pathname.endsWith('.html') || pathname.endsWith('/')) { 139 return 'network-first'; 140 }
141 const lastSeg = pathname.split('/').filter(Boolean).pop() || ''; 142 if (!lastSeg.includes('.')) { 143 return 'network-first'; 144 } 145 146 return 'cache-first'; 147}; 148 149// ÐбÑабоÑка запÑоÑов 150self.addEventListener('fetch', event => { 151 // ÐÑÑиÑÑем ÑолÑко GET запÑоÑÑ 152 if (event.request.method !== 'GET') { 153 return; 154 } 155 156 const strategy = getCacheStrategy(event.request.url); 157 158 if (strategy === 'network-only') { 159 // Шапка ÑайÑа и API: ÐРпеÑÐµÑ Ð²Ð°ÑÑваем запÑÐ¾Ñ â оÑдаÑм его бÑаÑзеÑÑ ÐºÐ°Ðº еÑÑÑ. 160 // РанÑÑе ÑÑÑ Ð±Ñло event.respondWith(fetch(event.request)) без .catch(): пÑи лÑбом 161 // ÑеджекÑе ÑеÑи fetch падал Ñ "Failed to fetch" (sw.js:160), respondWith полÑÑал 162 // оÑклонÑннÑй пÑÐ¾Ð¼Ð¸Ñ â бÑаÑÐ·ÐµÑ Ð¾Ñдавал network error на /js/header-include.js, и 163 // Ñапка не ÑендеÑилаÑÑ (а ÑиÑÑка кÑÑа не помогала â SW пеÑÐµÑ Ð²Ð°ÑÑвал, но не кеÑиÑовал). 164 // Ðез respondWith() бÑаÑÐ·ÐµÑ Ð´ÐµÐ»Ð°ÐµÑ Ñвой ÑÑаÑнÑй fetch (HTTP-кеÑ, 304, ÑедиÑекÑÑ) â 165 // он надÑжнее и не воÑпÑÐ¾Ð¸Ð·Ð²Ð¾Ð´Ð¸Ñ ÑÑÐ¾Ñ ÑеджекÑ. Та же ÑÐµÐ»Ñ Â«Ð¼Ð¸Ð¼Ð¾ SW-кеÑа», без оÑказа. 166 return; 167 } 168 169 if (strategy === 'cache-first') { 170 // СнаÑала кеÑ, поÑом ÑеÑÑ 171 event.respondWith( 172 caches.match(event.request) 173 .then(response => { 174 if (response) { 175 return response; 176 } 177 178 return fetch(event.request) 179 .then(fetchResponse => { 180 if (fetchResponse && fetchResponse.status === 200) { 181 const responseToCache = fetchResponse.clone(); 182 caches.open(STATIC_CACHE) 183 .then(cache => cache.put(event.request, responseToCache)); 184 } 185 return fetchResponse; 186 }); 187 }) 188 ); 189 } else if (strategy === 'network-first') { 190 // СнаÑала ÑеÑÑ, поÑом ÐºÐµÑ 191 event.respondWith( 192 fetch(event.request) 193 .then(response => { 194 if (response && response.status === 200) { 195 const responseToCache = response.clone(); 196 caches.open(DYNAMIC_CACHE) 197 .then(cache => { 198 cache.put(event.request, responseToCache); 199 // ÐÑиÑаем ÐºÐµÑ Ð¿Ñи Ð½ÐµÐ¾Ð±Ñ Ð¾Ð´Ð¸Ð¼Ð¾ÑÑи 200 cleanupCache(DYNAMIC_CACHE, MAX_DYNAMIC_CACHE_SIZE); 201 }); 202 } 203 return response; 204 }) 205 .catch(() => { 206 // Fallback на ÐºÐµÑ Ð¿Ñи оÑибке ÑеÑи 207 return caches.match(event.request); 208 }) 209 ); 210 } 211});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.