عامل الخدمة في جافاسكريبت

عامل الخدمة في جافاسكريبت

تشرح هذه المقالة مفهوم عامل الخدمة في جافاسكريبت۔

سنشرح خطوة بخطوة من أساسيات عامل الخدمة حتى التحكم العملي في التخزين المؤقت۔

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 &amp; 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) في الوقت الفعلي ويطبق استراتيجية التخزين المؤقت أولاً على الملفات الثابتة مثل أوراق الأنماط وجافاسكريبت.۔

تدفق التحديث

تدفق التحديث للعامل الخدمي كالتالي:۔

  1. يتم اكتشاف ملف sw.js جديد.
  2. يتم تشغيل حدث install.
  3. ينتظر حتى يصبح عامل الخدمة السابق خاملاً.
  4. يتم تشغيل حدث activate.
  5. يتم التبديل إلى عامل الخدمة الجديد. ٦. يتم إطلاق حدث controllerchange.

اكتشاف التحديث

بعد تثبيت عامل خدمة جديد، يظل القديم مستخدمًا حتى الزيارة التالية۔ لتطبيق التحديثات، من الشائع استخدام كود يكتشف التحديثات ويعيد تحميل الصفحة.۔

1navigator.serviceWorker.addEventListener('controllerchange', () => {
2    window.location.reload();
3});
  • يتم إطلاق حدث controllerchange عندما يتغير متحكم العامل الخدمي، أي العامل الخدمي الذي يتحكم في الصفحة الحالية.۔
  • تستمر الصفحات المفتوحة بالفعل في استخدام العامل الخدمي الحالي، ولا يؤثر العامل الخدمي المثبت حديثًا على تلك الصفحات فورًا.۔ لذلك، يتم استخدام تقنية حيث يُستخدم حدث controllerchange لاكتشاف أن متحكمًا جديدًا أصبح نشطًا، ثم يتم إعادة تحميل الصفحة لتطبيق التحديث فورًا.۔

تحذيرات وأفضل الممارسات

عند استخدام عامل الخدمة، ضع النقاط التالية في اعتبارك:۔

  • يتطلب HTTPS بسبب قيود الأمان، لا يعمل عبر http:// إلا على localhost۔

  • أسماء ملفات مشفرة (Hashed) يمكن أن يتضمن اسم التخزين المؤقت اسم الملف، عنوان URL، ومعلومات الإصدار۔

  • التواصل مع العملاء استخدم postMessage للتواصل بين عامل الخدمة وجافاسكريبت الصفحة۔

الملخص

عامل الخدمة هو تقنية أساسية لدعم العمل دون اتصال وتحسين أداء تطبيقات الويب۔ من خلال فهم التدفق الأساسي للتثبيت والتفعيل ومعالجة الجلب، وتطبيق استراتيجيات تخزين مؤقت مناسبة، يمكنك بناء تطبيقات ويب عالية الجودة۔

يمكنك متابعة المقالة أعلاه باستخدام Visual Studio Code على قناتنا على YouTube.۔ يرجى التحقق من القناة على YouTube أيضًا.۔

YouTube Video