سرویس ورکر یک قابلیت از مرورگرهای مدرن میباشد که امکان اجرای برخی از اسکریپتهای جاوااسکریپت را خارج از process اصلی صفحه وب میدهد.
کدهای جاوااسکریپت خط به خط و در قالب یک thread اجرا میشوند و امکان داشتن دو thread همزمان را نداریم. service worker این امکان را میدهد که برخی از اسکریپتهای جاوااسکریپت را در خارج از thread اصلی صفحه وب و در پس زمینه اجرا کنیم. service worker قابلیت دسترسی به DOM را ندارد اما امکان مواردی مانند کش کردن فایلها، اسکریپتها، استایلها و ... را میدهد، همیچنین برای پیاده سازی PWA و نوتیفیکیشنها به service worker نیازمند هستیم.
قابلیت service worker در مرورگرهای مدرن پشتیبانی میشود ولی خللی در اجرای سایر اسکریپتهای صفحات وب، در مرورگرهای قدیمیتر که service worker را پشتیبانی نمیکنند، ایجاد نمیکند. برای استفاده از service worker، پس از چک کردن پشتیبانی مرورگر، باید فایل service worker را ریجیستر کرد:
if("serviceWorker" in navigator){
navigator.serviceWorker.register("/sw.js", {
scope: "/",
}).then(register=>{
console.log(register)
});
}
در این مثال فایل sw.js را که در مسیر اصلی وبسایت است را ریجیستر کردیم. پس از ریجیستر کردن service worker، وضعیت آن به صورت یکی از موارد زیر خواهد بود.
درحال نصب یا installing:زمانی که service worker در حال نصب sw.js است.
درحال انتظار یا waiting: پس از نصب service worker، فعال نمیشود و درحال انتظار برای فعال شدن از روشهایی مانند ریلود صفحه یا متد skipWaiting میماند.
فعال یا active زمانی که سروریس ورکر فعال شده باشد.
if("serviceWorker" in navigator){
navigator.serviceWorker.register("/sw.js", {
scope: "/",
}).then(registeration=>{
if(registeration.installing){
console.log("sw.js is installing ...");
}else if(registeration.waiting){
console.log("sw.js is wating for activation ...");
}else if(registeration.active){
console.log("sw.js is active!");
}
});
}
با هربار تغییر در فایل sw.js این چرخه برای نسخه جدید این فایل تکرار میشود. برای مشاهده نسخه فایل sw.js و وضعیت service worker خود میتوانید از dev tools مرورگر کمک بگیرید:
دقت کنید فایل sw.js را اگر در مسیر دیگری غیر از root مانند js قرار دهیم، فقط برای درخواستهایی که در زیر مسیر js باشد، فعال خواهد شد.
برای استفاده از service worker باید https فعال باشد، اما برای localhost استثنائا با http نیز اجرا خواهد شد.
در صورتی که از قبل service worker ریجیستر شده باشد، متد register تا زمانی که فایل اسکریپت sw.js تغییری نداشته باشد، باعث ریجیستر شدن مجدد فایل اسکریپت نمیشود.
برای چک کردن فعال بودن serviceWorker میتوان مانند زیر عمل کرد:
if("serviceWorker" in navigator){
if(navigator.serviceWorker.controller){
console.log("service worker is actived!")
}
}
پراپرتی controller یک پراپرتی read-only است که یک رفرنس از service worker ریجیسترشده فعال را برمیگرداند.
یک تابع دریافت میکند و موقعی که service worker مجددا ریجیستر و فعال (active) شد، آنرا اجرا میکند.
navigator.serviceWorker.addEventListener("controllerchange", () => {
console.log("The controller of current browsing context has changed.");
});
به کمک پراپرتی ready که یک پراپرتی read-only است، میتوان اجرای کدها را تا فعال شدن service worker به تعویق انداخت:
navigator.serviceWorker.ready.then((registration) => {
console.log(`A service worker is active: ${registration.active}`);
});
این متد یک url دریافت میکند و در عوض یک پرامیس که حاوی service worker ریجسترشده فعال مطابق با url را برمیگرداند:
navigator.serviceWorker.getRegistration("/").then((registration) => {
if (registration) {
console.log(registration)
}
});
متد getRegistrations یک پرامیس حاوی آرایهای از تمام service worker های ریجیستر شده برمیگرداند.
navigator.serviceWorker.getRegistrations().then((registrations) => {
for(let registration of registrations){
console.log(registration)
}
});
متد unregister برای unregister کردن service worker ریجیستر شده بکار میرود. این متد یک پرامیس حاوی مقدار بولین نتیجه unregister کردن service worker را برمیگرداند.
navigator.serviceWorker.getRegistration("/").then((registration) => {
if (registration) {
registration.unregister().then((boolean) => {
// if boolean = true, unregister is successful
});
}
});
با فراخوانی متد update، مرورگر فایل اسکریپت service weorker را فتچ میکند و درصورتی که تغییری در این فایل رخ داده باشد، دوباره آنرا install میکند.
navigator.serviceWorker.getRegistration("/").then((registration) => {
if (registration) {
registration.update();
}
});
عملیات update و unregister را باید براساس لاجیک نرم افزار خود استفاده کنید. مثلا موقعی که کاربر روی دکمه update کلیک کرد.
همان طور که مشاهده کردید، با کمک متد register توانستیم فایل اسکریپت مربوط به service worker را به مرورگر معرفی کنیم. اکنون به سراغ ایجاد اسکریپت مورد نیاز خود میرویم.
navigator.serviceWorker.register("/sw.js", { scope: "/", });
در فایل sw.js دسترسی به آبجکت window و یا document دسترسی وجود ندارد، برای به آبجکت ServiceWorkerGlobalScope، میتوان از کلمه کلیدی self استفاده کرد. این آبجکت برخی از پراپرتیهای مورد نیاز مانند caches، locaction و ... را در اختیار برنامه نویس قرار میدهد.
//sw.js
console.log({slef});
اکنون به سراغ معرفی برخی event های مهم و ضروری میرویم.
یک تابع دریافت میکند و آنرا در زمان install شدن فایل service worker اجرا میکند. در صورتی که مشکلی در اجرای ایونت install رخ دهد، در دفعات بعد مجددا اجرا میشود ولی در صورتی که با موفقیت اجرا شود، دیگر اجرا نخواهد شد. کارهایی مثل کش کردن فایلها و asset ها را میتوان درون این تابع انجام داد.
//sw.js
self.addEventListener('install',function(event){
console.log('install');
});
یک تابع دریافت میکند و آنرا در زمان active شدن service worker اجرا میکند.
//ws.js
self.addEventListener('activate',function(event){
console.log('activated');
});
پس از install و active شدن اسکریپت service worker، صفحه وب را تحت کنترل خود درخواهد آورد. در هنگام ارسال درخواست بین سرور و کلاینت کاربر، service worker بصورت یک پراکسی عمل خواهد کرد. منظور از ارسال درخواست، تمام ریکوئستهای صفحه تحت کنترل، شامل فایل html، استایلهای css و اسکریپتهای js و ... میباشد. به عبارت دیگر درخواست ها از صفحه وب به service worker ارسال میشود. service worker تصمیم میگیرد که پاسخ درخواست را از سرور یا کش دریافت کند و به کلاینت کاربر برگرداند.
یک تابع دریافت میکند و آنرا در زمان اجرای هر ریکوئست وبسایت، اجرا میکند. به کمک این تابع شما میتوانید مشخص کنید که پاسخ درخواست از طریق کش یا سرور خوانده شود.
//ws.js
self.addEventListener('fetch',function(event){
console.log('fetch:',event.request);
});
راه ارتباطی service worker با صفحه تحت کنترلش ارسال message میباشد.
در نمونه زیر، یک تابع دریافت میکند و آنرا موقعی که کلاینت یک message به service worker ارسال کند، اجرا خواهد کرد.
//sw.js
self.addEventListener('message',function(ev){
console.log(ev.data);
});
در مثال زیر موقعی که serviceWorker آماده استفاده شد، یک message ارسال میکنیم، که نتیجه آن اجرا شدن event بالا میشود.
navigator.serviceWorker.ready.then( registration => {
registration.active.postMessage("hi!");
});
همچنین قابلیت ارسال message از service worker به صفحه تحت کنترلش فراهم است.
موقعی که service worker جدید ریجیستر شود، صفحه وب برای فعال شدن آن تا ریلود صفحه در حالت waiting منتظر میماند. برای اینکه نیاز به ریلود صفحه برای اکتیو شدن sw.js جدید نباشد و مرورگر مجبور شود sw.js جدید را فعال کند، متد skipWaiting را صدا میزنیم. این متد در تمام script قابل صدا زدن میباشد اما چون فقط روی sw.js جدید تاثیر میگذارد، معمولا در ایونت install صدا زده میشود:
self.addEventListener('install', function(event) {
self.skipWaiting();
});
موقعی که از service worker جدید بوسیله skipWaiting فعال یا active شود، صفحه وب برای استفاده از نسخه جدید تا ریلود صفحه منتظر میماند. برای اینکه صفحه وب بدون نیاز به ریلود، نسخه جدید service worker را جایگزین نسخه قبلی کند، پس از اکتیو شدن service worker، از متد client.claim استفاده میکنیم.
self.addEventListener('install', function(event) {
self.skipWaiting();
});
self.addEventListener("activate", (event) => {
event.waitUntil(clients.claim());
});
در هنگام install و activate سرویس ورکر، کارهایی مانند کش کردن فایلها، fetch و ... انجام میشود. این کارها به صورت async انجام میشوند. در حقیقت waitUntil یک promise دریافت میکند و تا موقعی که عملیات promise درحال انجام است، service worker را در حالت installing نگه میدارد. درصورتی که پرامیس پاس داده شده، ریجکت شود جلوی کامل شدن عملیات install یا activate را میگیرد.
self.addEventListener('install', function(event) {
self.skipWaiting();
const preCache = async () => {
const cache = await caches.open("static-v1");
return cache.addAll(["/", "/about/", "/static/styles.css"]);
};
event.waitUntil(preCache());
});