Service Worker چیست و چگونه یک سایت معمولی را به PWA تبدیل میکند؟
وبسایتهای مدرن دیگر فقط مجموعهای از صفحات HTML نیستند. با استفاده از تکنولوژیهایی مانند Service Worker، میتوان قابلیتهایی را به وبسایت اضافه کرد که تا سالها بیشتر در دنیای اپلیکیشنهای Native دیده میشدند.
امکان کارکردن در شرایط آفلاین، Cache کردن منابع، کنترل درخواستهای شبکه، ارسال Push Notification و نصب سایت روی دستگاه، بخشی از قابلیتهایی هستند که میتوانند یک وبسایت معمولی را به یک Progressive Web App یا PWA تبدیل کنند.
اما Service Worker دقیقاً چیست؟
کجا اجرا میشود؟
چه تفاوتی با JavaScript معمولی دارد؟
و مهمتر از همه، چگونه میتوان با آن یک سایت معمولی را به PWA تبدیل کرد؟
در این مقاله قدمبهقدم این مفاهیم را بررسی میکنیم.
Service Worker چیست؟
Service Worker یک اسکریپت JavaScript است که مرورگر آن را در یک محیط جدا از صفحه وب اجرا میکند.
برخلاف JavaScript معمولی که مستقیماً با صفحه و DOM کار میکند، Service Worker بیشتر نقش یک لایه واسط بین Application و Network را دارد.
میتوان معماری آن را به شکل زیر تصور کرد:
User
↓
Browser
↓
Service Worker
↓
Network / Cache
↓
Server
Service Worker میتواند درخواستهای Network را مشاهده و در صورت نیاز آنها را از Cache پاسخ دهد.
به همین دلیل یکی از پایههای اصلی معماری PWA محسوب میشود.
Service Worker کجا اجرا میشود؟
یکی از نکات مهم این است که Service Worker داخل صفحه اصلی اجرا نمیشود.
در واقع:
Main Thread
│
├── UI
├── DOM
└── Application JavaScript
Service Worker
│
├── Network
├── Cache
└── Background Tasks
Service Worker در یک Context جداگانه اجرا میشود.
به همین دلیل نمیتواند مستقیماً به DOM دسترسی داشته باشد.
مثلاً این کار در Service Worker امکانپذیر نیست:
document.querySelector('#app');
اما میتواند با APIهای مخصوص خودش با Cache و Requestهای شبکه کار کند.
چرا Service Worker مهم است؟
Service Worker چند قابلیت مهم را در اختیار Web Application قرار میدهد:
Cache کردن منابع
پشتیبانی از Offline
کنترل Requestهای شبکه
پاسخ دادن از Cache
Background Processing در برخی سناریوها
Push Notification
کمک به ساخت PWA
در نتیجه میتوان تجربهای ساخت که حتی در شرایطی که اینترنت ضعیف است یا موقتاً وجود ندارد، بخشهایی از Application همچنان قابل استفاده باشند.
Service Worker چه تفاوتی با JavaScript معمولی دارد؟
JavaScript معمولی معمولاً به Lifecycle همان صفحه وابسته است.
مثلاً:
Open Page
↓
JavaScript Runs
↓
Close Page
↓
JavaScript Stops
اما Service Worker مستقلتر از یک Page است.
مثلاً:
Browser
↓
Register Service Worker
↓
Install
↓
Activate
↓
Handle Requests
حتی ممکن است Service Worker زمانی که صفحه خاصی باز نیست نیز برای برخی رویدادهای پشتیبانیشده فعال شود.
البته Service Worker یک Process دائمی نیست و مرورگر میتواند آن را متوقف و در صورت نیاز دوباره راهاندازی کند.
Service Worker چگونه ثبت میشود؟
ابتدا باید Service Worker را در Application ثبت کنیم.
مثلاً در JavaScript اصلی:
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js');
}
اینجا مرورگر بررسی میکند که آیا Service Worker را پشتیبانی میکند یا خیر.
سپس فایل:
/sw.js
به عنوان Service Worker ثبت میشود.
Service Worker Lifecycle
Service Worker یک Lifecycle مشخص دارد.
مراحل اصلی آن عبارتاند از:
Register
↓
Install
↓
Waiting
↓
Activate
↓
Control
درک این Lifecycle برای Debug کردن PWA بسیار مهم است.
مرحله اول: Register
در مرحله اول، Application از مرورگر میخواهد Service Worker را ثبت کند:
navigator.serviceWorker.register('/sw.js');
اگر ثبت موفق باشد، مرورگر Service Worker را برای Scope مشخصشده مدیریت میکند.
مرحله دوم: Install
پس از Register، Service Worker وارد مرحله Install میشود.
در این مرحله معمولاً منابع اولیه Application را Cache میکنیم.
مثلاً:
const CACHE_NAME = 'app-v1';
const ASSETS = [
'/',
'/index.html',
'/styles.css',
'/app.js'
];
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => cache.addAll(ASSETS))
);
});
در این مثال، هنگام Install فایلهای مشخصشده وارد Cache میشوند.
مرحله سوم: Activate
پس از Install، Service Worker میتواند وارد مرحله Activate شود.
این مرحله معمولاً برای پاک کردن Cacheهای قدیمی کاربرد دارد.
مثلاً:
self.addEventListener('activate', event => {
const validCaches = ['app-v2'];
event.waitUntil(
caches.keys().then(keys =>
Promise.all(
keys
.filter(key => !validCaches.includes(key))
.map(key => caches.delete(key))
)
)
);
});
در اینجا میتوان Cacheهای قدیمی را حذف کرد.
مرحله چهارم: کنترل Requestها
یکی از قدرتمندترین قابلیتهای Service Worker، دریافت Event مربوط به Requestهای شبکه است.
مثلاً:
self.addEventListener('fetch', event => {
console.log(event.request.url);
});
هر زمان صفحه تحت کنترل Service Worker یک Request ایجاد کند، این Event میتواند اجرا شود.
اینجاست که میتوانیم تصمیم بگیریم:
Request
↓
Cache؟
┌─┴─┐
Yes No
↓ ↓
Cache Network
سادهترین استراتژی: Cache First
یکی از معروفترین استراتژیهای Caching، Cache First است.
منطق آن:
Request
↓
Cache
↓
Found?
┌─┴─┐
Yes No
↓ ↓
Return Network
↓
Cache
یک نمونه ساده:
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(cachedResponse => {
return cachedResponse || fetch(event.request);
})
);
});
اگر Response در Cache باشد، همان نسخه برگردانده میشود.
در غیر این صورت، Request به Network ارسال میشود.
Network First چیست؟
در برخی سناریوها بهتر است ابتدا Network را امتحان کنیم.
مثلاً برای دادههایی که مرتب تغییر میکنند:
Request
↓
Network
↓
Success?
┌─┴─┐
Yes No
↓ ↓
Save Cache
↓
Response
نمونه ساده:
self.addEventListener('fetch', event => {
event.respondWith(
fetch(event.request)
.catch(() => caches.match(event.request))
);
});
در این حالت ابتدا نسخه جدید از Server دریافت میشود.
اگر Network در دسترس نباشد، Cache میتواند به عنوان Fallback عمل کند.
چرا یک Strategy برای همه چیز مناسب نیست؟
یکی از اشتباهات رایج این است که برای تمام Requestها یک Strategy یکسان تعریف کنیم.
اما نوع داده اهمیت زیادی دارد.
مثلاً:
نوع محتواStrategy مناسبفونتCache FirstCSSCache FirstJavaScript VersionedCache FirstتصاویرCache FirstAPIهای پویاNetwork Firstدادههای حساسسیاست دقیقترصفحات HTMLبسته به معماری
بنابراین Service Worker باید بر اساس نوع Request طراحی شود.
Offline Mode چگونه کار میکند؟
یکی از جذابترین قابلیتهای PWA، امکان استفاده از بخشهایی از Application بدون اینترنت است.
فرض کنید کاربر قبلاً سایت را باز کرده است.
Service Worker فایلهای اصلی را Cache کرده:
index.html
app.js
styles.css
logo.svg
حالا اینترنت قطع میشود.
کاربر دوباره سایت را باز میکند:
Browser
↓
Service Worker
↓
Cache
↓
Cached Application
در این حالت میتوانیم Application Shell را از Cache نمایش دهیم.
Application Shell چیست؟
Application Shell به بخشهای اصلی رابط کاربری گفته میشود که برای نمایش اولیه Application لازم هستند.
مثلاً:
Header
Navigation
Layout
CSS
JavaScript
Icons
این بخشها را میتوان Cache کرد تا Application حتی بدون Network نیز بتواند ساختار اصلی خود را نمایش دهد.
آیا PWA یعنی سایت کاملاً Offline است؟
خیر.
این یک تصور اشتباه رایج است.
PWA لزوماً به این معنی نیست که تمام قابلیتهای سایت بدون اینترنت کار میکنند.
برای مثال یک فروشگاه آنلاین ممکن است بتواند:
Home Page ✓
Product Cache ✓
UI ✓
را Offline نمایش دهد.
اما:
پرداخت
ثبت سفارش
اطلاعات لحظهای
به Network نیاز داشته باشند.
بنابراین باید مشخص کنیم کدام بخش Application واقعاً قابلیت Offline دارد.
Service Worker و PWA چه رابطهای دارند؟
PWA یک تکنولوژی واحد نیست.
در واقع PWA مجموعهای از قابلیتها و استانداردهای Web است که تجربهای شبیه Application ایجاد میکند.
Service Worker یکی از اجزای مهم این معماری است.
در کنار آن معمولاً مواردی مانند:
Web App Manifest
HTTPS
Responsive Design
Offline Strategy
Installability
نیز اهمیت دارند.
Web App Manifest چیست؟
برای اینکه یک Web App بتواند تجربه نصبشدن روی دستگاه را ارائه دهد، معمولاً از فایل Manifest استفاده میشود.
مثلاً:
{
"name": "My Application",
"short_name": "My App",
"start_url": "/",
"display": "standalone",
"icons": [
{
"src": "/icon-192.png",
"sizes": "192x192",
"type": "image/png"
}
]
}
این فایل اطلاعاتی درباره Application در اختیار مرورگر قرار میدهد.
مثلاً:
نام Application
آیکون
صفحه شروع
نحوه نمایش
Theme
رنگ پسزمینه
HTTPS چرا مهم است؟
Service Worker به دلایل امنیتی باید در محیط امن اجرا شود.
در Production معمولاً باید سایت از:
HTTPS
استفاده کند.
یک استثنای مهم برای توسعه:
localhost
است که مرورگرها معمولاً آن را به عنوان محیط امن توسعه در نظر میگیرند.
Scope در Service Worker چیست؟
Service Worker فقط میتواند صفحات داخل Scope خودش را کنترل کند.
اگر فایل اینجا باشد:
/scripts/sw.js
به صورت پیشفرض Scope آن محدودتر از حالتی است که Service Worker در Root قرار داشته باشد.
برای مثال:
/sw.js
معمولاً میتواند Scope گستردهتری داشته باشد.
این موضوع هنگام طراحی ساختار پروژه اهمیت دارد.
Cache API چیست؟
Service Worker معمولاً برای مدیریت Cache از Cache API استفاده میکند.
مثلاً:
const cache = await caches.open('app-v1');
await cache.put('/data.json', response);
و برای دریافت:
const cached = await caches.match('/data.json');
Cache API با HTTP Cache مرورگر یکی نیست.
این دو سیستم میتوانند در کنار یکدیگر وجود داشته باشند.
Service Worker و HTTP Cache
این دو مفهوم گاهی با یکدیگر اشتباه گرفته میشوند.
HTTP Cache
توسط Browser و بر اساس Headerهایی مانند:
Cache-Control
ETag
Expires
مدیریت میشود.
Service Worker Cache
توسط Application و Service Worker با استفاده از Cache API کنترل میشود.
یعنی:
HTTP Cache
+
Service Worker Cache
میتوانند هر دو در معماری یک سایت وجود داشته باشند.
Push Notification
Service Worker میتواند در معماری Push Notification نیز نقش داشته باشد.
در یک سناریوی کلی:
Server
↓
Push Service
↓
Browser
↓
Service Worker
↓
Notification
Service Worker میتواند Event مربوط به Push را دریافت کرده و Notification را نمایش دهد.
البته این قابلیت نیازمند Permission کاربر و پیادهسازی سمت Server نیز هست.
Background Sync چیست؟
در برخی سناریوها Service Worker میتواند برای مدیریت عملیاتهایی که باید بعداً انجام شوند نیز مورد استفاده قرار گیرد.
مثلاً تصور کنید کاربر در شرایط Offline فرمی را ثبت میکند.
Application میتواند اطلاعات را موقتاً نگه دارد و پس از بازگشت Network، عملیات را تکمیل کند.
ایده کلی:
User Action
↓
Offline
↓
Store Request
↓
Network Returns
↓
Sync
↓
Server
پشتیبانی دقیق این قابلیتها به مرورگر و API مورد استفاده بستگی دارد، بنابراین باید هنگام پیادهسازی Compatibility را بررسی کرد.
Service Worker چه محدودیتهایی دارد؟
Service Worker بسیار قدرتمند است، اما نباید تصور کنیم جایگزین Backend یا یک Application Native است.
از جمله محدودیتها:
دسترسی مستقیم به DOM ندارد
Lifecycle آن توسط Browser مدیریت میشود
همیشه در حال اجرا نیست
باید سیاست Cache دقیقی داشته باشد
مدیریت Versionها اهمیت زیادی دارد
Debug کردن Cacheهای قدیمی میتواند دشوار باشد
بنابراین طراحی Service Worker باید بخشی از معماری Application باشد.
مشکل معروف Cacheهای قدیمی
فرض کنید نسخه اول Application را منتشر کردهایم:
app-v1
بعد نسخه جدید:
app-v2
را منتشر میکنیم.
اگر Service Worker همچنان Cache قدیمی را ارائه کند، ممکن است کاربر نسخه قبلی Application را ببیند.
به همین دلیل معمولاً هنگام تغییر نسخه باید:
Install New Worker
↓
Activate
↓
Delete Old Cache
↓
Use New Assets
را به شکل صحیح مدیریت کرد.
آیا ساخت PWA فقط با نوشتن یک Service Worker انجام میشود؟
خیر.
برای یک PWA حرفهای باید چند بخش را در نظر گرفت:
Responsive UI
+
HTTPS
+
Web App Manifest
+
Service Worker
+
Caching Strategy
+
Offline Strategy
+
Installability
Service Worker بخش مهمی از PWA است، اما تمام PWA نیست.
چگونه یک سایت معمولی را به PWA تبدیل کنیم؟
اگر یک سایت معمولی داشته باشیم، مسیر کلی میتواند این باشد:
مرحله اول: بررسی HTTPS
سایت باید روی HTTPS اجرا شود.
مرحله دوم: ایجاد Manifest
اطلاعات Application را در Manifest تعریف میکنیم.
مرحله سوم: ایجاد Service Worker
فایل Service Worker را ایجاد و Register میکنیم.
مرحله چهارم: Cache کردن منابع ضروری
منابع اصلی Application را Cache میکنیم.
مرحله پنجم: تعریف Fetch Strategy
مشخص میکنیم هر نوع Request چگونه مدیریت شود.
مرحله ششم: Offline Fallback
برای شرایط Offline یک تجربه مناسب طراحی میکنیم.
مرحله هفتم: مدیریت Versioning
Cacheهای قدیمی را مدیریت و در صورت نیاز حذف میکنیم.
مرحله هشتم: تست
Application را در شرایط مختلف بررسی میکنیم:
Online
Offline
Slow Network
Cache Hit
Cache Miss
New Version
Old Version
یک ساختار ساده برای PWA
یک پروژه ساده میتواند ساختاری شبیه این داشته باشد:
project/
│
├── index.html
├── app.js
├── styles.css
├── manifest.json
├── sw.js
│
└── icons/
├── icon-192.png
└── icon-512.png
و جریان کلی:
Browser
↓
index.html
↓
app.js
↓
Register sw.js
↓
Service Worker
↓
Cache / Network
Service Worker را با Cache اشتباه نگیریم
Service Worker خودش Cache نیست.
این نکته بسیار مهم است.
Service Worker یک Programmable Network Proxy در سمت Browser است.
یعنی میتواند روی نحوه دریافت و پاسخدهی Requestها منطق اعمال کند.
مثلاً:
Request
↓
Service Worker
↓
┌──────────────┐
│ Cache First │
│ Network First│
│ Stale While │
│ Revalidate │
└──────────────┘
پس قدرت اصلی Service Worker در این است که منطق مورد نیاز Application را روی فرآیند Request/Response اعمال میکند.
Service Worker و Performance
Service Worker میتواند Performance را به شکل قابل توجهی بهبود دهد؛ بهخصوص برای منابعی که قبلاً دریافت شدهاند.
مثلاً:
First Visit
↓
Network
↓
Cache
Next Visit
↓
Service Worker
↓
Cache
↓
Fast Response
اما استفاده نادرست از Service Worker میتواند نتیجه معکوس داشته باشد.
برای مثال اگر Cache Strategy اشتباه باشد، ممکن است کاربر نسخه قدیمی محتوا را دریافت کند.
بنابراین:
Caching سریعتر بودن نیست؛ Caching درست، سریعتر بودن است.
آیا Service Worker برای هر سایتی لازم است؟
خیر.
اگر سایت بسیار سادهای دارید که:
محتوای کاملاً استاتیک دارد
Offline بودن اهمیتی ندارد
Notification لازم ندارد
Installability هدف پروژه نیست
ممکن است Service Worker ارزش پیچیدگی اضافه را نداشته باشد.
اما برای Applicationهایی مانند:
فروشگاههای آنلاین
داشبوردها
ابزارهای تحت وب
سیستمهای مدیریت
اپلیکیشنهای SaaS
سرویسهای محتوایی
Web Appهای تعاملی
میتواند بسیار ارزشمند باشد.
جمعبندی
Service Worker یکی از مهمترین تکنولوژیهای Web برای ساخت تجربههای مدرن و Offline-capable است.
این تکنولوژی به ما اجازه میدهد بین Application و Network یک لایه قابل برنامهریزی داشته باشیم.
با استفاده از آن میتوانیم:
منابع را Cache کنیم
درخواستها را مدیریت کنیم
تجربه Offline بسازیم
Cache Strategy تعریف کنیم
برخی قابلیتهای Background را پیاده کنیم
Push Notification داشته باشیم
و بخشی از مسیر ساخت PWA را فراهم کنیم
اما یک PWA حرفهای فقط با Service Worker ساخته نمیشود.
معماری کامل معمولاً ترکیبی از:
Service Worker
+
Cache API
+
Web App Manifest
+
HTTPS
+
Responsive UI
+
Offline Strategy
است.
در نهایت Service Worker را میتوان اینگونه خلاصه کرد:
Service Worker یک لایه قابل برنامهریزی بین Web Application و Network است که به مرورگر اجازه میدهد نحوه Cache و مدیریت برخی درخواستها را کنترل کند.
و همین قابلیت، پایه بسیاری از تجربههایی است که یک وبسایت معمولی را به یک Progressive Web App سریع، قابل نصب و تا حدی مستقل از اتصال دائمی به اینترنت تبدیل میکنند.
