`Service Worker` в JavaScript

`Service Worker` в JavaScript

В этой статье объясняется концепция Service Worker в JavaScript.

Мы пошагово объясним основы Service Worker и практическое управление кэшем.

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>

Service Worker в JavaScript

Service Worker — это возможность JavaScript, которая находится между браузером и сетью, обеспечивая кэширование запросов и поддержку офлайн-режима. Это ключевая технология PWA (Progressive Web Apps), которая предоставляет веб-приложениям опыт, схожий с нативными приложениями.

Что такое Service Worker?

Service Worker — это JavaScript-файл, который работает в фоновом потоке браузера. Он работает в отдельном потоке от страницы, не имеет доступа к пользовательскому интерфейсу, но может перехватывать сетевые запросы, управлять кэшированием и обрабатывать push-уведомления.

Ключевые особенности 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 представляет диапазон путей (область действия), на которые влияет 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.

Создание файла 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 и поведение при загрузке ресурсов, нажимая на тестовую кнопку.

Примеры стратегий кэширования

Ниже приведены распространённые стратегии кэширования:.

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});
  • Этот код реализует стратегию cache-first, при которой запрашиваемый ресурс возвращается из кэша, если он доступен; если нет — запрашивается из сети.

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});
  • Этот код реализует стратегию network-first, при которой запрашиваемый ресурс сначала загружается из сети, а если это не удаётся — извлекается из кэша.

Кэшируйте только стили и JavaScript, а к API обращайтесь в реальном времени

Вот пример реализации, где кэшируются стили и 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-запросы в реальном времени и применяет стратегию cache-first к статическим файлам, таким как таблицы стилей и JavaScript.

Процесс обновления

Процесс обновления Service Worker выглядит следующим образом:.

  1. Обнаруживается новый sw.js.
  2. Запускается событие install.
  3. Ожидает, пока предыдущий Service Worker не завершит работу.
  4. Запускается событие activate.
  5. Переключается на новый Service Worker.
  6. Происходит событие controllerchange.

Обнаружение обновлений

После установки Service Worker старая версия будет использоваться до следующего визита. Для применения обновлений обычно используется код, который обнаруживает обновления и перезагружает страницу.

1navigator.serviceWorker.addEventListener('controllerchange', () => {
2    window.location.reload();
3});
  • Событие controllerchange происходит, когда контроллер Service Worker, то есть Service Worker, управляющий текущей страницей, изменяется.
  • Уже открытые страницы продолжают использовать текущий Service Worker, и только что установленный Service Worker не вступает в силу для этих страниц сразу. Поэтому применяется техника, при которой событие controllerchange используется для обнаружения того, что новый контроллер стал активным, после чего страница перезагружается для немедленного применения обновления.

Предупреждения и лучшие практики

При использовании Service Worker помните о следующих моментах:.

  • Требуется HTTPS Из-за ограничений безопасности не работает через http://, кроме случая с localhost.

  • Имена файлов с хешем Имя кэша может включать имя файла, URL и информацию о версии.

  • Взаимодействие с клиентом Используйте postMessage для обмена сообщениями между Service Worker и JavaScript страницы.

Резюме

Service Worker — это неотъемлемая технология для поддержки офлайн-режима и повышения производительности веб-приложений. Понимая базовый процесс установки, активации и обработки запросов с применением правильных стратегий кэширования, вы сможете создавать более качественные веб-приложения.

Вы можете следовать этой статье, используя Visual Studio Code на нашем YouTube-канале. Пожалуйста, также посмотрите наш YouTube-канал.

YouTube Video