Mundarija (28)
- Bu darsda
- 1. Nega bu kerak?
- 2. Xato anatomiyasi: skipWaiting va kech chunk
- 2.1 Eslab olamiz: yangi versiya kutadi
- 2.2 Ssenariy: nima bo'ldi
- 2.3 O'lchov: xato haqiqiy
- 3. Tuzatish: kutish va «Yangilash»
- 3.1 SW tomoni: uchta o'zgarish
- 3.2 Sahifa tomoni: kutayotgan SW'ni ko'rish
- 3.3 Jonli model: taklif komponenti
- 4. Workbox: o'zimiz yozganlarning tayyor nusxasi
- 4.1 Strategiyalar — tanish do'stlar
- 4.2 Precache: build bergan ro'yxat
- 5. vite-plugin-pwa
- 5.1 Ikki rejim
- 5.2 Runtime keshlash — sozlama bilan
- 6. Serwist
- 7. Biz sinab ko'rdik: uch SW, bitta ilova
- 8. Vazifalar qadami: SW yangilanishi (holat 28)
- 9. Ko'p uchraydigan xatolar
- 10. Mashqlar
- 1-mashq (oson): bo'shliqlarni to'ldiring
- 2-mashq (o'rta): qaysi keshlar o'chadi?
- 3-mashq (qiyin): «Keyinroq» tugmasi
- 4-mashq: Vazifalar qadami
- 11. Real ishda
- Xulosa
- Manbalar
PWA React loyihada: vite-plugin-pwa, Workbox, Serwist va «Yangilash» taklifi
Qisqacha: Vite loyihasida Service Worker'ni uch yo'l bilan yozish mumkin: o'zingiz (
public/sw.js), vite-plugin-pwa (Workbox ustida, SW'ni build o'zi yaratadi) yoki Serwist (Workbox'dan ajralib chiqqan fork). Next.js hujjati oflayn kesh uchun Serwist'ni variantlardan biri sifatida tilga oladi. Qaysi biri bo'lmasin, eng xavfli joy — yangilanish:skipWaitingyangi SW'ni darhol faollashtirsa, ochiq sahifa eski versiyaning kech chunk'ini topolmay yiqiladi. Kanon buni o'lchab topdi va tuzatdi: yangi versiya kutadi, foydalanuvchiga «Yangilash» taklif qilinadi. O'lchovda o'z SW — 2.3 KB, vite-plugin-pwa — 6.3, Serwist — 10.4 KB; ikkala kutubxonaning ro'yxatdan o'tkazgichi Trusted Types ostida ishlamadi — kanon o'zsw.jsini qoldirdi.
Bu darsda
- Deploydan keyin ochiq sahifa nega "Failed to fetch dynamically imported module" bilan yiqilishini ketma-ket tushuntirasiz.
- «Yangi versiya tayyor — Yangilash» oqimini (
waiting,postMessage,controllerchange) React'da yozasiz. - Workbox strategiyalari va precache'ni o'zingiz yozgan funksiyalar bilan solishtirasiz.
- vite-plugin-pwa va Serwist'ni sozlay olasiz va ularning narxini (KB, CSP) bilasiz.
- O'z SW yoki kutubxona — qarorni o'lchov bilan asoslaysiz.
Oldin bilishingiz kerak: Service Worker hayot sikli, Kesh strategiyalari va offline ishlash, PWA: o'rnatiladigan veb ilova, lazy va code splitting, useSyncExternalStore.
1. Nega bu kerak?
Sardor (stajyor) «Bahor» ilovasining yangi versiyasini deploy qildi. Hamma narsa yaxshi. Lekin oshxonada planshetda ilova kechadan beri ochiq turibdi. Oshpaz «Tartiblash» tugmasini bosadi — va ekranda xato qutisi: "Failed to fetch dynamically imported module".
Sahifani yangilasa — hammasi ishlaydi. Xato faqat deploydan oldin ochilgan va yopilmagan sahifada chiqadi. Dasturchi o'z kompyuterida uni hech qachon ko'rmaydi: u har safar sahifani yangilab ishlaydi.
vazifalar da aynan shu xato v10 dan beri bor edi. Uni hech kim sezmagan — kanon 21-qismda maxsus ssenariy bilan o'lchab topdi. Bu darsda shu xatoni ochamiz, tuzatamiz va keyin tayyor kutubxonalar (vite-plugin-pwa, Serwist) uni qanday hal qilishini ko'ramiz.
Hayotiy o'xshatish: restoran menyusi. Oshpaz yangi menyu chiqardi va eski menyudagi taomlarni oshxonadan olib tashladi. Lekin zalda bir mehmon hali eski menyuni ushlab o'tiribdi. U eski taomni buyurtma qilsa — oshxonada yo'q. To'g'ri yo'l: eski menyuni yig'ib, yangisini bermaguncha, eski taomlarni saqlab turish.
2. Xato anatomiyasi: skipWaiting va kech chunk
2.1 Eslab olamiz: yangi versiya kutadi
Service Worker hayot sikli darsidagi qoida: yangi SW o'rnatilgach kutadi (waiting). Eski SW boshqarayotgan sahifalarning hammasi yopilmaguncha u faollashmaydi. self.skipWaiting() bu kutishni bekor qiladi.
O'sha darsda ogohlantirgan edik: skipWaiting — "yarmi eski, yarmi yangi" xavfini o'zingiz qabul qilasiz. 13-qismda vazifalar ning SW'i bitta HTML va bir nechta fayldan iborat edi — xavf nazariy edi. React'da esa ilova o'nlab kech chunkka bo'lingan (lazy): TartiblashRejimi-CRVfLoM8.js, QrKod-…js. Ular faqat kerak bo'lganda so'raladi. Xavf endi haqiqiy.
2.2 Ssenariy: nima bo'ldi
v10 dagi sw.js install da shunday qilardi:
event.waitUntil(
caches
.open(KESH)
.then((kesh) => kesh.addAll(QOBIQ))
// Eski versiya yopilishini kutmaymiz: fayllar keshda tayyor
.then(() => self.skipWaiting()),
);activate da esa eski versiyaning keshi o'chiriladi. Ketma-ketlikni kuzating:
sequenceDiagram
participant P as Ochiq sahifa (A)
participant W as Service Worker
participant S as Server (B)
Note over S: deploy B — chunk nomlari yangi
P->>S: sw.js tekshiruvi
S-->>W: yangi sw.js (B)
W->>W: install B, skipWaiting
W->>W: activate B — A keshi o'chirildi
P->>W: Tartiblash-CRV…js (A versiyasi)
W->>S: tarmoqdan so'rash
S-->>W: 404 yoki index.html
W-->>P: HTML — modul emas
Note over P: Failed to fetch dynamically imported moduleUch shart birga keldi: (1) sahifa A kodi bilan ishlayapti va A nomli chunk'ni so'raydi; (2) serverda faqat B fayllari — hosting eski fayllarni saqlamaydi; (3) SW A keshini allaqachon o'chirgan. Natija — eski sahifa o'z faylini hech qayerdan topa olmaydi.
Server ba'zan 404 emas, index.html qaytaradi: SPA zaxirasi (SPA routing) "noma'lum manzil — ilova sahifasi" deydi, 200 bilan. Brauzer JS kutgan joyda HTML oladi.
2.3 O'lchov: xato haqiqiy
Kanon ssenariyni puppeteer bilan yozdi (olchov/sw-yangilanish.mjs): A versiyani ochadi, keyin "deploy B" qiladi — sw.js dagi VERSIYA o'zgaradi va Tartiblash chunk'i o'chiriladi. Eski sahifa yangilanishni topadi va Tartiblash'ni ochishga urinadi. v10 kodi bilan natija (28-sw-yangilanish-oldin.txt):
keshlar oldin: vazifalar-qobiq-v10-e65f0e6f
B: yangi SW kutmoqda: false; keshlar keyin: vazifalar-qobiq-v10-e65f0e6f-B
«Yangilash» taklifi ko'rindi: false
eski sahifada o'chirilgan chunk (TartiblashRejimi-CRVfLoM8.js) — Tartiblash ochildi: false
xatolar: Failed to load module script: Expected a JavaScript-or-Wasm module script but the server responded w ; Xato ushlandi (chegara): Failed to fetch dynamically imported module: http://127.0.0.1:5895/assets/TQatorma-qator: B kutmadi (false) — darhol faollashdi. Keshlar ro'yxatida faqat B qoldi — A o'chirildi. Tartiblash ochilmadi. Ikki xato: birinchisi — "modul skript kutildi, server esa (text/html) javob berdi" (qator o'lchovda kesilgan); ikkinchisi — error boundary ushlagan "dinamik import qilingan modulni olib bo'lmadi".
Tekshirib ko'ring: Nega bu xato dasturchining o'z kompyuterida deyarli hech qachon chiqmaydi?
Javob
Dasturchi npm run dev da ishlaydi — dev serverda SW yo'q yoki fayl nomlari xeshsiz. Production'ni tekshirganda esa sahifani yangilab ochadi: yangi HTML yangi chunk nomlarini so'raydi. Xato uchun sahifa deploydan oldin ochilgan va deploydan keyin kech chunk so'ragan bo'lishi kerak. Planshet, kun bo'yi ochiq turadigan admin panel — haqiqiy foydalanuvchida bu odatiy holat.
3. Tuzatish: kutish va «Yangilash»
3.1 SW tomoni: uchta o'zgarish
Kanon public/sw.js da uch joyni o'zgartirdi.
Birinchi — install da skipWaiting yo'q. Yangi versiya keshni tayyorlaydi va kutadi:
// 21/#28: skipWaiting YO'Q — yangi versiya "kutadi" (waiting), ochiq
// sahifa eski kod va eski kesh bilan ishlayveradi; foydalanuvchi
// «Yangilash» ni bossa — "yangila" xabari (pastda).
// Birinchi o'rnatishda kutish yo'q — boshqaradigan eski SW yo'q
self.addEventListener("install", (event) => {
event.waitUntil(caches.open(KESH).then((kesh) => kesh.addAll(QOBIQ)));
});Ikkinchi — skipWaiting faqat sahifa so'raganda. Sahifa "yangila" xabarini yuboradi, SW kutishni tugatadi:
// Sahifa «Yangilash» ni bosdi (sw-yangilanish.ts): kutish
// tugaydi, activate → clients.claim → sahifada controllerchange →
// qayta yuklash
self.addEventListener("message", (event) => {
if (event.data === "yangila") {
void self.skipWaiting();
}
});Uchinchi — xavfsizlik to'ri. Fayl serverda yo'q (404) yoki skript o'rniga HTML keldi — keshda shu nomli eski fayl bo'lsa, o'shani beramiz. caches.match hamma keshlarda qidiradi, faqat joriy versiyada emas. Kanon funksiyasi aynan, Node'da sinab ko'ramiz:
function yoqmi(sorov, javob) {
if (!javob.ok) {
return true;
}
const turi = javob.headers.get("Content-Type") ?? "";
return sorov.destination === "script" && turi.includes("text/html");
}
// Sinov: sorov — faqat kerakli maydon bilan obyekt
const skript = { destination: "script" };
const html = {
headers: { "Content-Type": "text/html; charset=utf-8" },
};
console.log(yoqmi(skript, new Response("", { status: 404 })));
console.log(yoqmi(skript, new Response("<!doctype html>", html)));
console.log(yoqmi(skript, new Response("export {}")));
console.log(yoqmi({ destination: "" }, new Response("<p>", html)));Konsolda:
true
true
false
falseTo'rt holat: 404 — "yo'q"; skript so'ralgan, HTML kelgan — "yo'q" (SPA zaxirasi); oddiy JS — "bor"; sahifa (navigatsiya) so'ralgan va HTML kelgan — normal, "bor". sorov.destination — so'rov nima uchunligi: "script", "style", "image", sahifa uchun "document".
fetch ishlovchisidagi ishlatilishi (kanon):
// 21/#28: fayl serverda yo'q (404) yoki SPA zaxirasi uning o'rniga
// index.html berdi (Vercel rewrites — 200, lekin HTML) — eski
// versiyaning fayli: keshda bo'lsa (har qanday versiya keshida)
// o'shani
if (tur === "fayl" && yoqmi(sorov, javob)) {
const eski = await caches.match(sorov);
if (eski !== undefined) {
return eski;
}
return javob;
}3.2 Sahifa tomoni: kutayotgan SW'ni ko'rish
Sahifa yangi SW kutayotganini qanday biladi? ServiceWorkerRegistration da uch xususiyat va bir hodisa bor:
| Nom | Ma'nosi |
|---|---|
reg.installing |
hozir o'rnatilayotgan SW |
reg.waiting |
o'rnatilgan, kutayotgan SW |
reg.active |
faol SW |
updatefound |
yangi SW o'rnatila boshladi |
Kanon sw-yangilanish.ts — kod aynan (fayl boshidagi izoh qisqartirildi, uzun izohlar telefon uchun qayta bo'lindi):
let kutayotgan: ServiceWorker | null = null;
let soralgan = false;
const tinglovchilar = new Set<() => void>();
function belgila(sw: ServiceWorker | null) {
kutayotgan = sw;
for (const t of tinglovchilar) {
t();
}
}
/** main.tsx: ro'yxatdan o'tgach — kuzatishni boshlash */
export function swniKuzat(reg: ServiceWorkerRegistration): void {
// controller bor — demak bu birinchi o'rnatish emas,
// haqiqiy yangilanish
if (
reg.waiting !== null &&
navigator.serviceWorker.controller !== null
) {
belgila(reg.waiting);
}
reg.addEventListener("updatefound", () => {
const yangi = reg.installing;
yangi?.addEventListener("statechange", () => {
if (
yangi.state === "installed" &&
navigator.serviceWorker.controller !== null
) {
belgila(yangi);
}
});
});
document.addEventListener("visibilitychange", () => {
if (document.visibilityState === "visible") {
reg.update().catch(() => {}); // oflayn — keyingi safar
}
});
navigator.serviceWorker.addEventListener("controllerchange", () => {
// Faqat foydalanuvchi so'ragan bo'lsa: boshqa tab bosgan
// bo'lishi ham mumkin — bu tab ham yangi SW ostida, lekin sahifa
// eski; uni ogohlantirmasdan qayta yuklamaymiz (yozilayotgan
// matn yo'qolmasin)
if (soralgan) {
location.reload();
} else {
belgila(null);
}
});
}
/** «Yangilash» bosildi */
export function yangila(): void {
soralgan = true;
kutayotgan?.postMessage("yangila");
}
/** Yangi versiya kutyaptimi (komponent uchun) */
export function useYangiVersiya(): boolean {
return useSyncExternalStore(
(t) => {
tinglovchilar.add(t);
return () => tinglovchilar.delete(t);
},
() => kutayotgan !== null,
);
}To'rt nozik joy:
controller !== null— birinchi o'rnatishda haminstalledholati bo'ladi. Lekin u paytda boshqaruvchi yo'q — "yangilanish" emas. Shart busiz bo'lsa, yangi foydalanuvchi birinchi kirishda «Yangi versiya tayyor» ni ko'rardi.visibilitychange→reg.update()— brauzer yangisw.jsni navigatsiyada o'zi tekshiradi. Kun bo'yi ochiq planshetda navigatsiya yo'q. Tab qayta ko'ringanda tekshiramiz.soralgan— ikki tab ochiq, birinchisida «Yangilash» bosildi. Ikkinchi tab hamcontrollerchangeoladi. Uni so'ramasdan qayta yuklasak — u yerda yozilayotgan matn yo'qoladi. Shuning uchun faqat o'zi so'ragan tab qayta yuklanadi.useSyncExternalStore— tashqi o'zgaruvchiga obuna (useSyncExternalStore). Komponentkutayotgano'zgarganda qayta chiziladi.
3.3 Jonli model: taklif komponenti
Natija oynasida Service Worker yo'q. Shuning uchun SW'ni oddiy obyekt bilan taqlid qilamiz — postMessage uni konsolga yozadi. Qolgani — kanon naqshi: tashqi o'zgaruvchi, tinglovchilar, useSyncExternalStore, role="status":
import { useSyncExternalStore } from "react";
let kutayotgan = null;
const tinglovchilar = new Set();
function belgila(sw) {
kutayotgan = sw;
for (const t of tinglovchilar) t();
}
function yangila() {
kutayotgan?.postMessage("yangila");
belgila(null); // haqiqiyda: controllerchange → location.reload()
}
function useYangiVersiya() {
return useSyncExternalStore(
(t) => {
tinglovchilar.add(t);
return () => tinglovchilar.delete(t);
},
() => kutayotgan !== null,
);
}
const soxtaSW = {
postMessage: (x) => console.log(`SW'ga xabar: ${x}`),
};
function YangilanishTaklifi() {
const bor = useYangiVersiya();
return (
<div role="status" hidden={!bor}>
{bor && (
<p>
Yangi versiya tayyor.{" "}
<button onClick={yangila}>Yangilash</button>
</p>
)}
</div>
);
}
export default function App() {
return (
<>
<button onClick={() => belgila(soxtaSW)}>
Deploy B (taqlid)
</button>
<YangilanishTaklifi />
</>
);
}Konsolda:
SW'ga xabar: yangilaEkranda:
Deploy B (taqlid)«Deploy B» dan keyin taklif paydo bo'ldi (oraliq nuqta), «Yangilash» dan keyin — yo'qoldi. Kanon YangilanishTaklifi.tsx xuddi shunday tuzilgan, matnlar t("yangilanish.matn") va t("yangilanish.tugma") bilan to'rt tilda.
role="status" — Malika uchun: taklif paydo bo'lganda ekran o'quvchi uni bir marta o'qiydi, fokus o'g'irlanmaydi. Modal oyna emas — foydalanuvchi ishini tugatib, keyin bosadi. Bosmasa — eski versiya hamma tablar yopilguncha ishlayveradi (TEXNIK-QARZ 70, ataylab).
4. Workbox: o'zimiz yozganlarning tayyor nusxasi
4.1 Strategiyalar — tanish do'stlar
Kesh strategiyalari darsida to'rt strategiyani qo'lda yozgan edik va oxirida Workbox'ni ko'rsatgan edik. Moslik jadvali:
| O'zimizniki | Workbox klassi | Qachon |
|---|---|---|
| cache-first | CacheFirst |
xeshli fayllar, rasmlar, shriftlar |
| network-first | NetworkFirst |
sahifa (HTML), tez o'zgaradigan JSON |
| stale-while-revalidate | StaleWhileRevalidate |
menyu, sozlamalar — biroz eski bo'lsa ham bo'ladi |
| network-only / cache-only | NetworkOnly / CacheOnly |
POST, API; oflayn sahifa |
vazifalar SW'i — network-first sahifa va qobiq uchun, API'ga tegmaydi (13-qism qarori: API keshdan berilsa server o'chganda eski ro'yxat "yangi" bo'lib ko'rinardi).
4.2 Precache: build bergan ro'yxat
Precache — o'rnatish paytida oldindan keshlanadigan fayllar ro'yxati. Bizda bu QOBIQ massivi: build'dan keyin skriptlar/qobiq.js dist/ dagi hamma fayl nomini sw.js ga yozadi. Holat 28 build'ida — sw.js: QOBIQ — 29 ta, VERSIYA — v11-7ac8df32.
Workbox'da xuddi shu g'oya self.__WB_MANIFEST — build vositasi uni [{ url, revision }, …] ro'yxatiga almashtiradi. revision — fayl mazmunining xeshi: fayl o'zgarmasa, qayta yuklanmaydi. Bizning VERSIYA esa butun qobiq uchun bitta — bitta fayl o'zgarsa, hammasi qayta yuklanadi. Kichik ilovada (29 fayl) farq sezilmaydi; yuzlab faylli ilovada Workbox usuli tejamliroq.
5. vite-plugin-pwa
5.1 Ikki rejim
vite-plugin-pwa — Vite plagini: build oxirida SW yaratadi, manifestni boshqaradi va ro'yxatdan o'tkazish kodini beradi. Ichida Workbox. Ikki rejim:
generateSW— SW'ni butunlay plagin yozadi. Siz faqat sozlama berasiz (globPatterns,runtimeCaching).injectManifest— SW'ni siz yozasiz (Workbox klasslari bilan), plagin faqatself.__WB_MANIFESTo'rniga ro'yxatni qo'yadi.
Kanon tajribasida (vite-plugin-pwa 2.0.0, workbox 7.4.1) vazifalar ning holat 26 nusxasi shunday sozlandi — vite.config.ts dagi plagin (kanon tajriba/pwa/sozla.mjs dan, aynan):
VitePWA({
strategies: "generateSW",
registerType: "prompt",
injectRegister: false,
manifest: false,
filename: "sw.js",
workbox: {
globPatterns: ["**/*.{js,css,html,svg,png,webmanifest}"],
globIgnores: ["404.html"],
navigateFallback: "index.html",
cleanupOutdatedCaches: true,
},
}),registerType: "prompt"— yangilanishda so'raydi (kanon tuzatishidagi oqim). Ikkinchi qiymat"autoUpdate"— ichidaskipWaitingva qayta yuklash: «Nega bu kerak?» dagi xavfga qaytadi.injectRegister: false— ro'yxatdan o'tkazishni o'zimiz chaqiramiz.manifest: false—vazifalardamanifest.webmanifestallaqachon bor (PWA).navigateFallback— har sahifa manziliindex.htmldan (SPA).cleanupOutdatedCaches— eski Workbox keshlarini o'chirish.
Ro'yxatdan o'tkazish (tajriba, main.tsx):
// tajriba: vite-plugin-pwa (workbox-window orqali)
import { registerSW } from "virtual:pwa-register";
registerSW({ immediate: true, onRegisterError: (e) => console.warn("SW xato:", String(e)) });virtual:pwa-register — plagin yaratadigan "virtual modul" (diskda fayl yo'q). React uchun uning virtual:pwa-register/react varianti bor — useRegisterSW hook'i. Hujjat bo'yicha u needRefresh (yangi versiya kutyapti) va updateServiceWorker() (yangilash) beradi — bizning useYangiVersiya va yangila ning aynan o'zi.
5.2 Runtime keshlash — sozlama bilan
generateSW rejimida strategiyalarni kod emas, sozlama bilan berasiz. Hujjat bo'yicha misol (kanonda ishlatilmagan — vazifalar API'ni keshlamaydi):
workbox: {
runtimeCaching: [
{
urlPattern: ({ request }) => request.destination === "image",
handler: "CacheFirst",
options: {
cacheName: "rasmlar",
expiration: { maxEntries: 50 },
},
},
],
},Sozlama qulay, lekin nima bo'layotganini ko'rish qiyinroq: SW kodi build'da yaratiladi. Muammo chiqsa, dist/sw.js ni ochib o'qishga to'g'ri keladi.
6. Serwist
Serwist — Workbox'dan ajralib chiqqan (fork) kutubxona. Uning README'sida shunday yozilgan: Workbox rivoji to'xtab qolgani uchun fork qilingan, oldingi ishning ko'pi Google jamoasiniki. G'oyalar va klasslar o'sha, paketlar zamonaviy bundlerlarga moslangan. Next.js hujjatidagi PWA qo'llanmasi (2026-10 holatiga) to'liq SW asosidagi oflayn kesh uchun "variantlardan biri" deb Serwist'ni ko'rsatadi (@serwist/next), yonida o'zining tajribaviy useOffline hook'i ham bor. Vite uchun — @serwist/vite. Kanon tajribasida (@serwist/vite 9.5.13) SW fayli shunday edi (aynan):
// tajriba: Serwist service worker
import { Serwist } from "serwist";
declare const self: any;
const s = new Serwist({
precacheEntries: self.__SW_MANIFEST,
skipWaiting: true,
clientsClaim: true,
precacheOptions: { navigateFallback: "index.html" },
});
s.addEventListeners();Bu injectManifest uslubi: SW'ni siz yozasiz, self.__SW_MANIFEST o'rniga build ro'yxat qo'yadi. Diqqat qiling: tajriba sozlamasida skipWaiting: true — aynan «Xato anatomiyasi» dagi tuzoq. Hujjatlardagi ko'p "tez boshlash" misollari shunday. Kech chunk'li ilovada skipWaiting: false va o'z taklif oqimingiz kerak.
Ro'yxatdan o'tkazish (tajriba):
// tajriba: Serwist (@serwist/window)
import { Serwist } from "@serwist/window";
void new Serwist(import.meta.env.BASE_URL + "sw.js", { scope: import.meta.env.BASE_URL }).register().catch((e) => console.warn("SW xato:", String(e)));7. Biz sinab ko'rdik: uch SW, bitta ilova
Kanon holat 26 ni uch nusxada yig'di: o'z sw.js, vite-plugin-pwa, Serwist. Har birini vazifalar CSP'si va Trusted Types ostida ochib, oflayn sinadi (28-pwa-taqqos.txt):
oz SW 2308 B (fayllar: sw.js) | birinchi JS 218.8 KB | qobiq 29 | SW faol true, boshqaradi true | CSP 0 | API keshda 0 | oflayn /:✓ /statistika:✓ /ru/sozlamalar:✓ | xato: yo'q
vpp SW 6290 B (fayllar: sw.js + workbox-2fbc6a65.js; import: l) | birinchi JS 219.2 KB | qobiq 29 | SW faol false, boshqaradi false | CSP require-trusted-types-for×1 | API keshda 0 | oflayn /:✗ /statistika:✗ /ru/sozlamalar:✗ | xato: This document requires 'TrustedScriptURL' assignment. The action has been blocked. ; SW xato: TypeError: Failed to execute 'register' on 'ServiceWorkerContainer': This document requires 'TrustedScriptURL'
serwist SW 10400 B (fayllar: sw.js; import: ...r) | birinchi JS 220.0 KB | qobiq 0 | SW faol false, boshqaradi false | CSP require-trusted-types-for×1 | API keshda 0 | oflayn /:✗ /statistika:✗ /ru/sozlamalar:✗ | xato: This document requires 'TrustedScriptURL' assignment. The action has been blocked. ; SW xato: TypeError: Failed to execute 'register' on 'ServiceWorkerContainer': This document requires 'TrustedScriptURL'
vpp-tt SW 6272 B (fayllar: sw.js + workbox-2fbc6a65.js; import: l) | birinchi JS 218.8 KB | qobiq 28 | SW faol true, boshqaradi true | CSP 0 | API keshda 0 | oflayn /:✓ /statistika:✓ /ru/sozlamalar:✓ | xato: yo'q
serwist-tt SW 10404 B (fayllar: sw.js; import: ...r) | birinchi JS 218.8 KB | qobiq 0 | SW faol true, boshqaradi true | CSP 0 | API keshda 0 | oflayn /:✓ /statistika:✓ /ru/sozlamalar:✓ | xato: yo'qSW hajmi (gzip) — uchala variant bir xil ishni qiladi:
- o'z sw.js2,3 KB
- vite-plugin-pwa (Workbox)sw.js + workbox-*.js6,3 KB
- Serwist10,4 KB
Manba: O'lchandi: kanon tajribasi, holat 26 nusxalari, vite-plugin-pwa 2.0.0 / workbox 7.4.1 / @serwist/vite 9.5.13, gzip -9, Chrome 155, natijalar/tajriba/28-pwa-taqqos.txt, 2026-10-17
Jadvaldan uchta xulosa:
- Hajm. O'z SW — 2.3 KB, Workbox'li — 6.3, Serwist — 10.4. SW alohida faylda va o'rnatishda bir marta yuklanadi — birinchi kadrni sekinlashtirmaydi. Lekin farq 3–4 baravar.
- Trusted Types.
vppvaserwistqatorlari: SW umuman ro'yxatdan o'tmadi, oflayn ishlamadi. Sabab —register()ga oddiy satr berilgan,vazifalarCSP'si esaTrustedScriptURLtalab qiladi. Ikkala kutubxonaning ro'yxatdan o'tkazgichi (workbox-window, @serwist/window) Trusted Types'ni bilmaydi.vpp-ttvaserwist-ttqatorlari — o'sha SW'lar bizning Trusted Types siyosatimiz orqali ro'yxatdan o'tkazilgan: ikkalasi ham ishladi, oflayn ham. - Natija bir xil. Ishlagan to'rt variantning hammasi oflayn ochildi va API'ni keshlamadi (
API keshda 0).
Ikki halol izoh. Birinchisi: Serwist qatorlarida qobiq 0 — bu "precache bo'sh" degani emas. O'lchov skripti Serwist manifestidagi yozuvlarni sanamagan (formatini tanimagan); serwist-tt qatorida oflayn sinov o'tgani precache ishlaganini ko'rsatadi. Ya'ni bu ustun Serwist uchun o'lchanmagan. Ikkinchisi: vpp-tt va serwist-tt nusxalarini yig'adigan skript tajriba papkasida saqlanmagan — natija xom faylda bor, lekin tajribani aynan takrorlab bo'lmaydi (texnik qarz sifatida yozilgan).
Kanon qarori: o'z sw.js qoladi. Sabablar: 3–4 baravar kichik; Trusted Types bilan ishlash uchun baribir o'z ro'yxatdan o'tkazgichimiz kerak; SW 130 qator, har qatori tushunarli va testlangan (sw.test.js). Kutubxona qachon to'g'ri tanlov? Yuzlab faylli ilovada (fayl-darajasidagi revision), ko'p runtime strategiyali ilovada (rasmlar, API, shriftlar — har biri o'z keshida) va Next.js'da (Serwist — hujjatdagi variantlardan biri).
Tekshirib ko'ring: Kutubxona tanlansa ham, «Xato anatomiyasi» dagi xavf o'z-o'zidan yo'qoladimi?
Javob
Yo'q. Kutubxona skipWaiting ni bitta sozlamaga aylantiradi (registerType: "autoUpdate", skipWaiting: true), lekin uni yoqish-o'chirishni siz hal qilasiz. Kech chunk'li ilovada "darhol yangilash" — o'sha xato. Workbox'da eski precache yozuvlarini cleanupOutdatedCaches o'chiradi — ochiq sahifa uchun eski fayl yana yo'qoladi. Xavfni tushunish kutubxonadan muhimroq.
8. Vazifalar qadami: SW yangilanishi (holat 28)
git switch -c fix/sw-yangilanishO'zgarishlar (kanon, hammasi yuqorida ko'rildi):
public/sw.js—installdaskipWaitingyo'q;message"yangila";yoqmivacaches.matchto'ri.src/sw-yangilanish.ts—swniKuzat,yangila,useYangiVersiya.src/komponentlar/YangilanishTaklifi.tsx—role="status",hidden, i18n matnlari.main.tsx— ro'yxatdan o'tgach kuzatish:
navigator.serviceWorker
.register(manzil)
// 21/#28: yangi versiya kutsa — «Yangilash» taklifi
.then(swniKuzat)
.catch((xato: unknown) => {
console.warn(`Service Worker o'rnatilmadi: ${xatoMatni(xato)}`);
});manzil — skriptManzili(SW_MANZILI): Trusted Types siyosatimizdan o'tgan TrustedScriptURL (18-qismdan beri). Kutubxonalar aynan shu joyda yiqilgan edi.
Qobiq.tsx—<YangilanishTaklifi />;i18n/*/umumiy.json—yangilanish.matn,yangilanish.tugma(uz: «Yangi versiya tayyor.», «Yangilash»).skriptlar/qobiq.js— VERSIYA prefiksiv10-→v11-.
Tekshiruvlar (haqiqiy, holat 28):
npm test— node:test 248 (avval 245):sw.test.js+3 ("yangila" xabari, 404 → keshdagi eski fayl, HTML o'rniga keshdagi skript), 1 ta o'zgardi (install endi kutadi).- Birinchi yuklash 224.1 → 224.4 KB (+0.3).
- e2e-21 — 69; "birinchi o'rnatish: faol, kutayotgan yo'q, sahifani boshqaradi, taklif yashirin", "install'da skipWaiting yo'q".
- Deploydan keyingi ssenariy — endi (
28-sw-yangilanish-keyin.txt):
keshlar oldin: vazifalar-qobiq-v11-7ac8df32
B: yangi SW kutmoqda: true; keshlar keyin: vazifalar-qobiq-v11-7ac8df32,vazifalar-qobiq-v11-7ac8df32-B
«Yangilash» taklifi ko'rindi: true
eski sahifada o'chirilgan chunk (TartiblashRejimi-BwpTeRIZ.js) — Tartiblash ochildi: true
xatolar: yo'q
«Yangilash» bosildi: sahifa qayta yuklandi, keshlar: vazifalar-qobiq-v11-7ac8df32-B, taklif yo'qoldi: true«Oldin» bilan solishtiring: B kutdi, ikkala kesh ham joyida, taklif chiqdi, o'chirilgan chunk keshdan ochildi, xato yo'q. «Yangilash» bosilgach — sahifa qayta yuklandi va faqat B keshi qoldi.
git add -A
git commitCommit (kanon): fix: SW yangilanishi — kutish va «Yangilash» (ochiq sahifa kech chunk'ni yo'qotmaydi).
9. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
| deploydan keyin "Failed to fetch dynamically imported module" | skipWaiting + eski kesh o'chirildi |
kutish + «Yangilash» + keshdan zaxira |
| yangi foydalanuvchi «Yangi versiya» ni ko'radi | controller tekshirilmagan |
navigator.serviceWorker.controller !== null |
| «Yangilash» bosildi, hech narsa bo'lmadi | controllerchange tinglanmagan |
qayta yuklash — controllerchange da |
| ikkinchi tab o'zi qayta yuklandi | soralgan belgisi yo'q |
faqat so'ragan tab qayta yuklanadi |
register — TrustedScriptURL xatosi |
satr berilgan, CSP'da Trusted Types | siyosatdan o'tgan manzil |
registerType: "autoUpdate" — o'sha eski xato |
ichida skipWaiting |
"prompt" |
10. Mashqlar
Mashqlar kurs/mashqlar/21/28-pwa/ da.
1-mashq (oson): bo'shliqlarni to'ldiring
- O'rnatilgan, lekin hali faollashmagan SW holati —
- Kutishni bekor qiladigan SW metodi —
- Sahifa boshqaruvchisi almashganini bildiradigan hodisa —
- vite-plugin-pwa'da SW'ni o'zingiz yozadigan rejim —
- Yangilanishni so'rab qiladigan
registerTypeqiymati —
Yechim
- waiting —
reg.waitingda turadi. - skipWaiting — kanonda faqat "yangila" xabarida.
- controllerchange —
navigator.serviceWorkerda; kanon shu yerda qayta yuklaydi. - injectManifest —
generateSWesa SW'ni o'zi yaratadi. - prompt —
"autoUpdate"darhol yangilaydi.
2-mashq (o'rta): qaysi keshlar o'chadi?
Funksiya ochiriladigan(nomlar, joriy) yozing: keshlar nomlari ro'yxatidan faqat vazifalar-qobiq- bilan boshlanadigan va joriy ga teng bo'lmaganlarni qaytaradi. Boshqa loyihalarning keshlariga (bir origin'da bo'lishi mumkin) tegilmaydi. Tekshiring:
const nomlar = [
"vazifalar-qobiq-v10-e65f0e6f",
"vazifalar-qobiq-v11-7ac8df32",
"portfolio-v3",
"workbox-precache-v2",
];
console.log(ochiriladigan(nomlar, "vazifalar-qobiq-v11-7ac8df32"));
// [ 'vazifalar-qobiq-v10-e65f0e6f' ]Ishora: kanon eskiKeshlarniOchir dagi filter shartini sof funksiyaga ajrating (filter).
Yechim
const KESH_OLDI = "vazifalar-qobiq-";
function ochiriladigan(nomlar, joriy) {
return nomlar.filter(
(nom) => nom.startsWith(KESH_OLDI) && nom !== joriy,
);
}
const nomlar = [
"vazifalar-qobiq-v10-e65f0e6f",
"vazifalar-qobiq-v11-7ac8df32",
"portfolio-v3",
"workbox-precache-v2",
];
console.log(ochiriladigan(nomlar, "vazifalar-qobiq-v11-7ac8df32"));
console.log(ochiriladigan(["portfolio-v3"], "x"));Konsolda:
[ 'vazifalar-qobiq-v10-e65f0e6f' ]
[]login.github.io kabi umumiy origin'da boshqa loyihalar ham kesh yaratadi. Prefikssiz nom !== joriy hammasini o'chirib yuborardi — qo'shni loyiha oflayn rejimini buzardi.
3-mashq (qiyin): «Keyinroq» tugmasi
«Jonli model» dagi taklifga «Keyinroq» tugmasini qo'shing:
- «Keyinroq» bosilsa, taklif yashirinadi, lekin kutayotgan SW unutilmaydi.
- Keyingi "deploy" bo'lsa (yangi soxta SW) — taklif yana chiqadi.
- Konsolga: «Keyinroq» bosilganda
keyinroq, «Yangilash» daSW'ga xabar: yangila.
Ishora: yashirish — alohida tashqi o'zgaruvchi yashirilgan (qaysi SW uchun yashirilgan). useSyncExternalStore ning getSnapshot i: kutayotgan !== null && kutayotgan !== yashirilgan.
Yechim
import { useSyncExternalStore } from "react";
let kutayotgan = null;
let yashirilgan = null;
const tinglovchilar = new Set();
const xabarla = () => tinglovchilar.forEach((t) => t());
let n = 0;
function deploy() {
n++;
kutayotgan = {
postMessage: (x) => console.log(`SW'ga xabar: ${x}`),
};
xabarla();
}
function keyinroq() {
yashirilgan = kutayotgan;
console.log("keyinroq");
xabarla();
}
function yangila() {
kutayotgan?.postMessage("yangila");
kutayotgan = null;
xabarla();
}
function useTaklif() {
return useSyncExternalStore(
(t) => {
tinglovchilar.add(t);
return () => tinglovchilar.delete(t);
},
() => kutayotgan !== null && kutayotgan !== yashirilgan,
);
}
export default function App() {
const bor = useTaklif();
return (
<>
<button onClick={deploy}>Deploy (taqlid)</button>
<div role="status" hidden={!bor}>
{bor && (
<p>
Yangi versiya tayyor.{" "}
<button onClick={yangila}>Yangilash</button>{" "}
<button onClick={keyinroq}>Keyinroq</button>
</p>
)}
</div>
</>
);
}Konsolda:
keyinroq
SW'ga xabar: yangilaEkranda:
Deploy (taqlid)Birinchi deploy → «Keyinroq» → taklif yo'q (oraliq nuqtada ekranda faqat «Deploy»). Ikkinchi deploy — yangi obyekt, u yashirilgan ga teng emas — taklif qaytdi. «Yangilash» → xabar va taklif yo'qoldi. Kalit — yashirishni "qaysi SW uchun" deb eslab qolish: shunda yangi versiya o'z-o'zidan qayta taklif qilinadi.
4-mashq: Vazifalar qadami
fix/sw-yangilanish branch'ida «Vazifalar qadami» dagi o'zgarishlarni qiling.
Yechim
Tekshiruv ro'yxati:
npm test— node 248, Vitest 65.npm run build && npm run preview, sahifani oching. Keyinsrc/da kichik o'zgarish qilib qayta build qiling (sahifani yangilamang). Tabni almashtirib qaytsangiz — «Yangi versiya tayyor. Yangilash».- DevTools → Application → Service Workers: ikkinchi SW "waiting to activate". «Yangilash» → u faol bo'ladi, sahifa qayta yuklanadi.
- Cache Storage: «Yangilash» dan oldin ikki
vazifalar-qobiq-v11-…keshi, keyin bittasi. - Ikki tab bilan: birida «Yangilash» — ikkinchisi o'zi qayta yuklanmaydi, taklif yo'qoladi.
11. Real ishda
- "Faqat menda ishlamayapti" murojaatlarining katta qismi — eski SW va kesh. «Yangilash» oqimi bunday murojaatlarni kamaytiradi.
- Next.js loyihalari to'liq oflayn kesh uchun ko'pincha Serwist ishlatadi (Next.js hujjati ham uni tilga oladi); Vite loyihalari — vite-plugin-pwa. Ularni o'qish uchun bu darsdagi tushunchalar yetarli: precache ro'yxati, strategiya,
registerType. - Intervyu savollari: "
skipWaitingqachon xavfli?", "SW yangilanishini foydalanuvchiga qanday ko'rsatasiz?", "precache va runtime cache farqi?".
Xulosa
- Kech chunk'li React ilovada
skipWaiting+ eski keshni o'chirish = deploydan keyin ochiq sahifa yiqiladi. Kanon buni o'lchab topdi. - Tuzatish: yangi SW kutadi; sahifa
reg.waitingni ko'radi va «Yangilash» taklif qiladi; bosilsa —postMessage("yangila")→skipWaiting→controllerchange→ qayta yuklash. - To'r: 404 yoki skript o'rniga HTML —
caches.matchdan eski fayl. - Workbox = kesh strategiyalari ning tayyor klasslari + fayl-darajasidagi precache. vite-plugin-pwa (
generateSW/injectManifest), Serwist — Workbox fork'i, Next.js'da keng tarqalgan. - O'lchov: o'z SW 2.3 KB, vite-plugin-pwa 6.3, Serwist 10.4; kutubxonalarning ro'yxatdan o'tkazgichi Trusted Types'da ishlamadi → o'z
sw.jsqoldi.
Keyingi dars: Offline-first ilova — internet yo'qligida yozuvlar rad etilmaydi: navbatga tushadi va qaytganda tartib bilan yuboriladi.
Manbalar
- MDN:
ServiceWorkerRegistration(waiting,updatefound,update()),ServiceWorkerGlobalScope.skipWaiting(),controllerchange,Request.destination - web.dev: "The service worker lifecycle" — updates
- vite-pwa-org.netlify.app: vite-plugin-pwa —
registerType,useRegisterSW,generateSW/injectManifest - developer.chrome.com/docs/workbox: strategiyalar, precaching
- serwist.pages.dev:
@serwist/vite,@serwist/next; github.com/serwist/serwist — README ("a fork of Workbox") - nextjs.org: "How to build a Progressive Web Application (PWA) with Next.js" — "Offline Support" bandi
- Kanon:
vazifalarholat 28,tekshiruv-28.txt,28-sw-yangilanish-oldin.txt,28-sw-yangilanish-keyin.txt,tajriba/28-pwa-taqqos.txt(2026-10-17)
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!