عامل الخدمة في جافاسكريبت
تشرح هذه المقالة مفهوم عامل الخدمة في جافاسكريبت۔
سنشرح خطوة بخطوة من أساسيات عامل الخدمة حتى التحكم العملي في التخزين المؤقت۔
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>
عامل الخدمة في جافاسكريبت
عامل الخدمة هو ميزة في جافاسكريبت تعمل بين المتصفح والشبكة، مما يسمح بتخزين الطلبات مؤقتًا ودعم الاستخدام دون اتصال۔ إنها تقنية أساسية في تطبيقات الويب التقدمية (PWA) وتمنح تطبيقات الويب تجربة مماثلة للتطبيقات الأصلية۔
ما هو عامل الخدمة؟
عامل الخدمة هو ملف جافاسكريبت يعمل في خلفية المتصفح۔ يعمل في خيط منفصل عن الصفحة ولا يمكنه الوصول إلى واجهة المستخدم، لكنه يستطيع اعتراض طلبات الشبكة، إدارة التخزين المؤقت، والتعامل مع إشعارات الدفع۔
تتضمن الميزات الرئيسية لعامل الخدمة ما يلي:۔
- يعمل فقط عبر HTTPS، باستثناء البيئة المحلية (localhost)۔
- يستخدم واجهة برمجة تطبيقات غير متزامنة مبنية على الوعود (Promises)۔
- يعتمد على الأحداث، ويستخدم أحداث مثل
install
،activate
،fetch
، وpush
.
تسجيل عامل الخدمة
أولاً، لنكتب الكود لتسجيل عامل الخدمة في المتصفح۔
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
(ملف عامل الخدمة)۔ - يمكنك استخدام
then
لمعالجة النجاح وcatch
لمعالجة الأخطاء أثناء التسجيل۔ registration.scope
تمثل نطاق المسار (النطاق) الذي يتأثر بواسطةService Worker
۔- افتراضياً، النطاق هو المجلد الذي يوجد فيه الملف المسجل (في هذه الحالة
/sw.js
) والمجلدات الفرعية التابعة له۔
نطاق Service Worker
إذا كنت تريد تقييد النطاق، يمكنك تحديد 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
۔
إنشاء ملف عامل الخدمة
بعد ذلك، أنشئ ملفًا باسم 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')
عند تسجيل عامل الخدمة لأول مرة۔ في هذه المرحلة، يتم تخزين الملفات الضرورية مؤقتًا مسبقًا۔
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
وسلوك جلب الموارد من خلال النقر على زر الاختبار۔
أمثلة لاستراتيجيات التخزين المؤقت
الاستراتيجيات الشائعة للتخزين المؤقت تشمل:۔
التخزين المؤقت أولاً
فيما يلي مثال على تنفيذ استراتيجية التخزين المؤقت أولاً:۔
1self.addEventListener('fetch', event => {
2 event.respondWith(
3 caches.match(event.request).then(response => {
4 return response || fetch(event.request);
5 })
6 );
7});
- ينفذ هذا الكود استراتيجية التخزين المؤقت أولاً، حيث يتم إرجاع المورد المطلوب من التخزين المؤقت إذا كان متاحًا؛ وإذا لم يكن كذلك، يتم جلبه من الشبكة.۔
الشبكة أولاً
فيما يلي مثال على تنفيذ استراتيجية الشبكة أولاً:۔
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});
- ينفذ هذا الكود استراتيجية الشبكة أولاً، حيث يتم جلب المورد المطلوب من الشبكة أولاً، وإذا فشل ذلك، يتم استرجاعه من التخزين المؤقت.۔
خزن ملفات الأنماط وجافاسكريبت فقط مؤقتًا، واصل الوصول إلى واجهات البرامج في الوقت الفعلي
فيما يلي مثال على تنفيذ حيث يتم تخزين الأنماط وجافاسكريبت مؤقتًا بينما يتم الوصول إلى الواجهات البرمجية في الوقت الفعلي:۔
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) في الوقت الفعلي ويطبق استراتيجية التخزين المؤقت أولاً على الملفات الثابتة مثل أوراق الأنماط وجافاسكريبت.۔
تدفق التحديث
تدفق التحديث للعامل الخدمي كالتالي:۔
- يتم اكتشاف ملف
sw.js
جديد. - يتم تشغيل حدث
install
. - ينتظر حتى يصبح عامل الخدمة السابق خاملاً.
- يتم تشغيل حدث
activate
. - يتم التبديل إلى عامل الخدمة الجديد.
٦. يتم إطلاق حدث
controllerchange
.
اكتشاف التحديث
بعد تثبيت عامل خدمة جديد، يظل القديم مستخدمًا حتى الزيارة التالية۔ لتطبيق التحديثات، من الشائع استخدام كود يكتشف التحديثات ويعيد تحميل الصفحة.۔
1navigator.serviceWorker.addEventListener('controllerchange', () => {
2 window.location.reload();
3});
- يتم إطلاق حدث
controllerchange
عندما يتغير متحكم العامل الخدمي، أي العامل الخدمي الذي يتحكم في الصفحة الحالية.۔ - تستمر الصفحات المفتوحة بالفعل في استخدام العامل الخدمي الحالي، ولا يؤثر العامل الخدمي المثبت حديثًا على تلك الصفحات فورًا.۔ لذلك، يتم استخدام تقنية حيث يُستخدم حدث
controllerchange
لاكتشاف أن متحكمًا جديدًا أصبح نشطًا، ثم يتم إعادة تحميل الصفحة لتطبيق التحديث فورًا.۔
تحذيرات وأفضل الممارسات
عند استخدام عامل الخدمة، ضع النقاط التالية في اعتبارك:۔
-
يتطلب HTTPS بسبب قيود الأمان، لا يعمل عبر
http://
إلا علىlocalhost
۔ -
أسماء ملفات مشفرة (Hashed) يمكن أن يتضمن اسم التخزين المؤقت اسم الملف، عنوان URL، ومعلومات الإصدار۔
-
التواصل مع العملاء استخدم
postMessage
للتواصل بين عامل الخدمة وجافاسكريبت الصفحة۔
الملخص
عامل الخدمة هو تقنية أساسية لدعم العمل دون اتصال وتحسين أداء تطبيقات الويب۔ من خلال فهم التدفق الأساسي للتثبيت والتفعيل ومعالجة الجلب، وتطبيق استراتيجيات تخزين مؤقت مناسبة، يمكنك بناء تطبيقات ويب عالية الجودة۔
يمكنك متابعة المقالة أعلاه باستخدام Visual Studio Code على قناتنا على YouTube.۔ يرجى التحقق من القناة على YouTube أيضًا.۔