`Service Worker` en JavaScript
Este artículo explica el concepto de Service Worker
en JavaScript.
Explicaremos paso a paso desde los conceptos básicos de Service Worker
hasta el control de caché práctico.
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
en JavaScript
Service Worker
es una funcionalidad de JavaScript que se sitúa entre el navegador y la red, permitiendo el almacenamiento en caché de solicitudes y soporte sin conexión. Es una tecnología fundamental de las PWAs (Aplicaciones Web Progresivas) y brinda una experiencia similar a una aplicación nativa a las aplicaciones web.
¿Qué es un Service Worker
?
Service Worker
es un archivo JavaScript que se ejecuta en el hilo de fondo del navegador. Se ejecuta en un hilo separado de la página, no puede acceder a la interfaz de usuario, pero puede interceptar solicitudes de red, gestionar la caché y manejar notificaciones push.
Las características clave de un Service Worker
incluyen lo siguiente:.
- Solo funciona a través de HTTPS, excepto en localhost.
- Utiliza una API asíncrona basada en Promesas.
- Es orientado a eventos, utilizando eventos como
install
,activate
,fetch
ypush
.
Registrando un Service Worker
Primero, escribamos el código para registrar un Service Worker
en el navegador.
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}
Explicación
- Utiliza
navigator.serviceWorker.register()
para registrar/sw.js
(el archivo delService Worker
). - Puedes usar
then
para manejar el éxito ycatch
para manejar errores durante el registro. registration.scope
representa el rango de rutas (alcance) afectadas por elService Worker
.- Por defecto, el alcance es el directorio donde se encuentra el archivo registrado (en este caso,
/sw.js
) y sus subdirectorios.
Alcance del Service Worker
Si deseas limitar el alcance, puedes especificar el scope
usando el segundo argumento de 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});
Explicación
- Con esta configuración, solo las páginas bajo
/app/
serán controladas por elService Worker
.
Creando el archivo Service Worker
A continuación, crea un archivo llamado sw.js
e implementa los eventos básicos.
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];
Este código define una lista de recursos que se deben almacenar en caché.
Funciones y mecanismos de cada evento
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')
se activa cuando elService Worker
se registra por primera vez. En esta etapa, los archivos necesarios se almacenan previamente en caché.
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});
- En el evento
activate
, las cachés antiguas se eliminan para optimizar el almacenamiento. Solo se mantiene la caché de la nueva versión.
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});
Todas las solicitudes HTTP son interceptadas: si existe una versión en caché, se devuelve; de lo contrario, se obtiene de la red. Cuando no hay conexión, se devuelve una página alternativa (por ejemplo, offline.html
).
Confirmando la operación
Vamos a comprobar realmente cómo funciona el 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});
- Aquí, comprobamos el registro del
Service Worker
y el comportamiento al obtener recursos haciendo clic en el botón de prueba.
Ejemplos de estrategias de caché
Las siguientes son estrategias comunes de almacenamiento en caché:.
Primero la caché
Aquí tienes un ejemplo de implementación para la estrategia 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});
- Este código implementa una estrategia de cache primero, donde el recurso solicitado se devuelve desde la caché si está disponible; si no, se obtiene de la red.
Primero la red
Aquí tienes un ejemplo de implementación para la estrategia 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});
- Este código implementa una estrategia de red primero, donde el recurso solicitado se obtiene primero de la red, y si eso falla, se recupera desde la caché.
Solo cachear estilos y JavaScript, acceder a las APIs en tiempo real
Aquí tienes un ejemplo de implementación donde los estilos y JavaScript se almacenan en caché mientras que las APIs se acceden en tiempo real:.
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});
- Este código siempre accede a las solicitudes de API en tiempo real y aplica una estrategia de cache primero a archivos estáticos como hojas de estilo y JavaScript.
Flujo de actualización
El flujo de actualización de un Service Worker es el siguiente:.
- Se detecta un nuevo
sw.js
. - Se dispara el evento
install
. - Espera hasta que el
Service Worker
anterior quede inactivo. - Se dispara el evento
activate
. - Se cambia al nuevo Service Worker.
- Se dispara el evento
controllerchange
.
Detección de actualizaciones
Una vez que un Service Worker
está instalado, el anterior se sigue utilizando hasta la próxima visita. Para aplicar actualizaciones, es común utilizar código que detecta actualizaciones y recarga la página.
1navigator.serviceWorker.addEventListener('controllerchange', () => {
2 window.location.reload();
3});
- El evento
controllerchange
se dispara cuando el controlador del Service Worker, es decir, el Service Worker que controla la página actual, cambia. - Las páginas que ya están abiertas continúan utilizando el Service Worker actual, y el nuevo Service Worker instalado no tiene efecto inmediato en esas páginas. Por lo tanto, se utiliza una técnica en la que el evento
controllerchange
se utiliza para detectar que un nuevo controlador se ha activado, y luego la página se recarga para aplicar la actualización de inmediato.
Precauciones y buenas prácticas
Al usar Service Worker
, ten en cuenta lo siguiente:.
-
Se requiere HTTPS Por restricciones de seguridad, no funciona sobre
http://
excepto enlocalhost
. -
Nombres de archivos con hash El nombre de la caché puede incluir el nombre del archivo, la URL y la información de la versión.
-
Comunicación con clientes Utiliza
postMessage
para comunicarte entre elService Worker
y el JavaScript de la página.
Resumen
Service Worker
es una tecnología esencial para el soporte sin conexión y la mejora de rendimiento en las aplicaciones web. Al comprender el flujo básico de instalación, activación y manejo de 'fetch', e implementar estrategias de caché adecuadas, puedes construir aplicaciones web de mayor calidad.
Puedes seguir el artículo anterior utilizando Visual Studio Code en nuestro canal de YouTube. Por favor, también revisa nuestro canal de YouTube.