`Service Worker` ב-JavaScript
מאמר זה מסביר את המושג Service Worker
ב-JavaScript.
נסביר שלב אחר שלב מהבסיס של Service Worker
ועד לשליטה מעשית בחיוץ (Cache).
YouTube Video
offline.html
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <title>Offline</title>
6</head>
7<body>
8 <h1>You are offline</h1>
9 <p>This is the offline fallback page.</p>
10</body>
11</html>
style.css
1body {
2 font-family: sans-serif;
3 background-color: #f0f0f0;
4 padding: 20px;
5}
6h1 {
7 color: #333;
8}
javascript-service-worker.html
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <title>JavaScript & HTML</title>
6 <style>
7 * {
8 box-sizing: border-box;
9 }
10
11 body {
12 margin: 0;
13 padding: 1em;
14 padding-bottom: 10em;
15 font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
16 background-color: #f7f9fc;
17 color: #333;
18 line-height: 1.6;
19 }
20
21 .container {
22 max-width: 800px;
23 margin: 0 auto;
24 padding: 1em;
25 background-color: #ffffff;
26 border: 1px solid #ccc;
27 border-radius: 10px;
28 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
29 }
30
31 .container-flex {
32 display: flex;
33 flex-wrap: wrap;
34 gap: 2em;
35 max-width: 1000px;
36 margin: 0 auto;
37 padding: 1em;
38 background-color: #ffffff;
39 border: 1px solid #ccc;
40 border-radius: 10px;
41 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
42 }
43
44 .left-column, .right-column {
45 flex: 1 1 200px;
46 min-width: 200px;
47 }
48
49 h1, h2 {
50 font-size: 1.2rem;
51 color: #007bff;
52 margin-top: 0.5em;
53 margin-bottom: 0.5em;
54 border-left: 5px solid #007bff;
55 padding-left: 0.6em;
56 background-color: #e9f2ff;
57 }
58
59 button {
60 display: block;
61 margin: 1em auto;
62 padding: 0.75em 1.5em;
63 font-size: 1rem;
64 background-color: #007bff;
65 color: white;
66 border: none;
67 border-radius: 6px;
68 cursor: pointer;
69 transition: background-color 0.3s ease;
70 }
71
72 button:hover {
73 background-color: #0056b3;
74 }
75
76 #output {
77 margin-top: 1em;
78 background-color: #1e1e1e;
79 color: #0f0;
80 padding: 1em;
81 border-radius: 8px;
82 min-height: 200px;
83 font-family: Consolas, monospace;
84 font-size: 0.95rem;
85 overflow-y: auto;
86 white-space: pre-wrap;
87 }
88
89 .highlight {
90 outline: 3px solid #ffc107; /* yellow border */
91 background-color: #fff8e1; /* soft yellow background */
92 transition: background-color 0.3s ease, outline 0.3s ease;
93 }
94
95 .active {
96 background-color: #28a745; /* green background */
97 color: #fff;
98 box-shadow: 0 0 10px rgba(40, 167, 69, 0.5);
99 transition: background-color 0.3s ease, box-shadow 0.3s ease;
100 }
101 </style>
102</head>
103<body>
104 <div class="container">
105 <h1>JavaScript Console</h1>
106 <button id="executeBtn">Execute</button>
107 <div id="output"></div>
108 </div>
109
110 <div class="container">
111 <h2>HTML Sample</h2>
112 <button id="fetchBtn">Fetch Test</button>
113 </div>
114
115 <script>
116 // Override console.log to display messages in the #output element
117 (function () {
118 // Override console.log
119 const originalLog = console.log;
120 console.log = function (...args) {
121 originalLog.apply(console, args);
122 const message = document.createElement('div');
123 message.textContent = args.map(String).join(' ');
124 output.appendChild(message);
125 };
126
127 // Override console.error
128 const originalError = console.error;
129 console.error = function (...args) {
130 originalError.apply(console, args);
131 const message = document.createElement('div');
132 message.textContent = args.map(String).join(' ');
133 message.style.color = 'red'; // Color error messages red
134 output.appendChild(message);
135 };
136 })();
137
138 document.getElementById('executeBtn').addEventListener('click', () => {
139 // Prevent multiple loads
140 if (document.getElementById('externalScript')) return;
141
142 const script = document.createElement('script');
143 script.src = 'javascript-service-worker.js';
144 script.id = 'externalScript';
145 //script.onload = () => console.log('javascript-service-worker.js loaded and executed.');
146 //script.onerror = () => console.log('Failed to load javascript-service-worker.js.');
147 document.body.appendChild(script);
148 });
149 </script>
150</body>
151</html>
Service Worker
ב-JavaScript
Service Worker
היא תכונה של JavaScript העומדת בין הדפדפן לרשת ומאפשרת חיוץ בקשות ותמיכה בעבודה לא מקוונת (Offline). זו טכנולוגיה מרכזית של אפליקציות אינטרנט מתקדמות (PWAs) המעניקה חוויית שימוש הדומה לאפליקציות מקוריות.
מהו Service Worker
?
Service Worker
הוא קובץ JavaScript שפועל ב-thread רקע של הדפדפן. הוא פועל ב-thread נפרד מהעמוד, לא יכול לגשת ל-UI, אבל מסוגל ליירט בקשות רשת, לנהל חיוץ ולטפל בהתראות דחיפה (Push Notifications).
התכונות המרכזיות של Service Worker
כוללות את הבאות:.
- הוא עובד רק ב-HTTPS, למעט בסביבת Localhost.
- הוא עושה שימוש ב-API אסינכרוני מבוסס Promise.
- הוא מבוסס אירועים, ומשתמש באירועים כמו
install
,activate
,fetch
ו-push
.
רישום Service Worker
ראשית, נכתוב את הקוד לרישום Service Worker
בדפדפן.
1if ('serviceWorker' in navigator) {
2 window.addEventListener('load', () => {
3 navigator.serviceWorker.register('/sw.js')
4 .then(registration => {
5 console.log(
6 'Service Worker registered with scope:',
7 registration.scope
8 );
9 })
10 .catch(error => {
11 console.error('Service Worker registration failed:', error);
12 });
13 });
14}
הסבר
- השתמשו ב־
navigator.serviceWorker.register()
כדי לרשום את /sw.js (קובץService Worker
). - ניתן להשתמש ב־
then
לטיפול בהצלחה ו־catch
לטיפול בשגיאות בעת הרישום. registration.scope
מייצג את טווח הנתיבים (scope) שמושפע על ידי ה-Service Worker
.- כברירת מחדל, ה-scope הוא התיקייה שבה נמצא הקובץ הרשום (במקרה זה,
/sw.js
) ותתי התיקיות שלה.
ה-scope של Service Worker
אם ברצונך להגביל את ה-scope, תוכל לציין את ה-scope
באמצעות הארגומנט השני של register
.
1navigator.serviceWorker.register('/sw.js', { scope: '/app/' })
2.then(registration => {
3 console.log(
4 'Service Worker registered with scope:',
5 registration.scope
6 );
7});
הסבר
- בעזרת הגדרה זו, רק דפים תחת
/app/
יהיו בשליטה של ה-Service Worker
.
יצירת קובץ ה־Service Worker
לאחר מכן, צרו קובץ בשם sw.js ויישמו בו את האירועים הבסיסיים.
1// sw.js
2const CACHE_NAME = 'my-cache-v1';
3const urlsToCache = [
4 '/',
5 '/index.html',
6 '/styles.css',
7 '/script.js',
8 '/offline.html'
9];
קוד זה מגדיר רשימה של משאבים שיש לאחסן במטמון.
התפקידים והמנגנונים של כל אירוע
install
1// Install event (initial caching)
2self.addEventListener('install', event => {
3 console.log('[ServiceWorker] Install');
4 event.waitUntil(
5 caches.open(CACHE_NAME).then(cache => {
6 console.log('[ServiceWorker] Caching app shell');
7 return cache.addAll(urlsToCache);
8 })
9 );
10});
self.addEventListener('install')
מופעל כאשרService Worker
נרשם לראשונה. בשלב זה, קבצים נדרשים נשמרים מראש בחיוץ.
activate
1// Activation event (delete old caches)
2self.addEventListener('activate', event => {
3 console.log('[ServiceWorker] Activate');
4 event.waitUntil(
5 caches.keys().then(keyList => {
6 return Promise.all(keyList.map(key => {
7 if (key !== CACHE_NAME) {
8 console.log('[ServiceWorker] Removing old cache:', key);
9 return caches.delete(key);
10 }
11 }));
12 })
13 );
14 return self.clients.claim();
15});
- במהלך אירוע
activate
, חיווצים ישנים נמחקים כדי לייעל את השימוש במקום האחסון. רק החיוץ של הגרסה החדשה נשמר.
fetch
1// Fetch event (cache-first strategy)
2self.addEventListener('fetch', event => {
3 console.log('[ServiceWorker] Fetch', event.request.url);
4 event.respondWith(
5 caches.match(event.request).then(response => {
6 return response || fetch(event.request).catch(() => caches.match('/offline.html'));
7 })
8 );
9});
כל הבקשות ב-HTTP ניירטות — אם קיימת גרסה בחיוץ היא מוחזרת, ואם לא — הבקשה מבוצעת ברשת. בעת עבודה לא מקוונת, תוחזר עמוד חלופי (למשל, offline.html).
מאשר את הפעולה
בוא נבדוק בפועל כיצד ה-Service Worker
פועל.
1document.getElementById('fetchBtn').addEventListener('click', () => {
2 fetch('/style.css')
3 .then(response => response.text())
4 .then(data => {
5 console.log('Fetched data:', data);
6 })
7 .catch(error => {
8 console.error('Fetch failed:', error);
9 });
10});
- כאן אנו בודקים את רישום ה-
Service Worker
ואת אופן הבאת המשאבים על ידי לחיצה על לחצן בדיקה.
דוגמאות לאסטרטגיות חיוץ (Caching)
הבאות הן אסטרטגיות חיוץ נפוצות:.
חיוץ תחילה (Cache First)
להלן דוגמה ליישום אסטרטגיית Cache First:.
1self.addEventListener('fetch', event => {
2 event.respondWith(
3 caches.match(event.request).then(response => {
4 return response || fetch(event.request);
5 })
6 );
7});
- הקוד הזה מממש אסטרטגיית 'קודם המטמון', כלומר המשאב המבוקש יוחזר מהמטמון אם הוא זמין; אם לא, הוא יובא מהרשת.
רשת תחילה (Network First)
להלן דוגמה ליישום אסטרטגיית Network First:.
1self.addEventListener('fetch', event => {
2 event.respondWith(
3 fetch(event.request)
4 .then(response => {
5 return caches.open(CACHE_NAME).then(cache => {
6 cache.put(event.request, response.clone());
7 return response;
8 });
9 })
10 .catch(() => caches.match(event.request))
11 );
12});
- הקוד הזה מממש אסטרטגיית 'קודם הרשת', כלומר המשאב המבוקש יובא קודם כל מהרשת, ואם זה נכשל, יישלף מהמטמון.
חיוץ סגנונות (CSS) ו-JavaScript בלבד, וגישה ל-API בזמן אמת
להלן דוגמה ליישום שבו CSS ו-JavaScript נשמרים בחיוץ, בעוד הגישה ל-API מתבצעת בזמן אמת:.
1self.addEventListener('fetch', event => {
2 if (event.request.url.includes('/api/')) {
3 // Fetch API responses in real-time without caching
4 return;
5 }
6
7 // Use cache-first strategy for static files
8 event.respondWith(
9 caches.match(event.request).then(response => {
10 return response || fetch(event.request);
11 })
12 );
13});
- הקוד הזה תמיד פונה ל-API בזמן אמת ומיישם אסטרטגיית 'קודם המטמון' עבור קבצים סטטיים כמו גיליונות עיצוב ו-JavaScript.
מהלך עדכון
מהלך העדכון של Service Worker הוא כך:.
sw.js
חדש מזוהה.- אירוע
install
מופעל. - ממתין עד ש־
Service Worker
הקודם נהיה לא פעיל (idle). - אירוע
activate
מופעל. - מתבצעת החלפה ל־Service Worker החדש.
- אירוע
controllerchange
מתרחש.
זיהוי עדכון
לאחר התקנת Service Worker
, הגרסה הישנה ממשיכה להיות בשימוש עד לביקור הבא. כדי להחיל עדכונים, נהוג להשתמש בקוד שמזהה עדכונים ומרענן את הדף.
1navigator.serviceWorker.addEventListener('controllerchange', () => {
2 window.location.reload();
3});
- אירוע
controllerchange
מתרחש כאשר הקונטרולר של ה-Service Worker, כלומר ה-Service Worker ששולט בעמוד הנוכחי, משתנה. - דפים שכבר פתוחים ממשיכים להשתמש ב-Service Worker הנוכחי, וה-Service Worker החדש שהותקן לא נכנס לתוקף מיידית בדפים אלו. לכן, משתמשים בטכניקה שבה אירוע
controllerchange
משמש לזיהוי ש-controller חדש הופעל, ואז הדף נטען מחדש כדי להחיל את העדכון מיד.
אזהרות והמלצות לעבודה נכונה
בעת עבודה עם Service Worker
, חשוב לזכור את הנקודות הבאות:.
-
נדרש HTTPS בגלל מגבלות אבטחה, זה לא יעבוד על
http://
אלא רק ב־localhost
. -
שמות קבצים מגובבים (Hashed) שם החיוץ יכול להכיל את שם הקובץ, כתובת (URL) ומידע על גרסה.
-
תקשורת עם לקוחות (Clients) יש להשתמש ב־
postMessage
לצורך תקשורת בין ה־Service Worker
ל־JavaScript של העמוד.
סיכום
Service Worker
היא טכנולוגיה חיונית לתמיכה בעבודה לא מקוונת ולשיפור ביצועים באפליקציות אינטרנט. באמצעות הבנה של תהליך ההתקנה, ההפעלה והטיפול בבקשות fetch, ויישום אסטרטגיות חיוץ מתאימות, תוכלו לבנות אפליקציות אינטרנט איכותיות יותר.
תוכלו לעקוב אחר המאמר שלמעלה באמצעות Visual Studio Code בערוץ היוטיוב שלנו. נא לבדוק גם את ערוץ היוטיוב.