En esta lección se presenta una PWA básica.
Puedes probar el ejemplo en https://gilpgpwa.github.io/.
También puedes probar el ejemplo en https://pwab.rf.gd/.
Prueba e instala, de preferencia con Chrome, el sitio https://pwab.rf.gd/ y en https://gilpgpwa.github.io/.
Descarga el archivo /src/pwab.zip y descompáctalo.
Crea tu proyecto en GitHub pages:
Crea una cuenta de email con el nombre de tu sitio, por ejemplo, miapp@google.com
Crea una cuenta de GitHub usando el email anterior y selecciona el nombre de usuario unsando la parte inicial del correo electrónico, por ejemplo miapp.
Crea un repositorio nuevo. En la página principal de GitHub cliquea 📘 New.
En la página Create a new repository introduce los siguientes datos:
Proporciona el nombre de tu repositorio debajo de donde dice Repository name *. Debes usar el nombre de tu cuenta seguido por .github.io; por ejemplo miapp.github.io
Mantén la selección Public.
Verifica la casilla Add a README file. En este archivo se muestra información sobre tu proyecto.
Cliquea License: None. y selecciona la licencia que consideres más adecuada para tu proyecto.
Cliquea Create repository.
Entra al repositorio y selecciona ⚙ Settings, luego selecciona 📁 Pages y en la sección Branches selecciona la carpeta donde se ubicará la carpeta. De preferencia selecciona / (root) para que coloques la página en la raíz del proyecto.
Importa el proyecto en GitHub:
En la página principal de tu proyecto en GitHub, en la pestaña < > Code, cliquea < > Code y en la sección Branches y copia la dirección que está en HTTPS, debajo de Clone.
En Visual Studio Code, usa el botón de la izquierda para Source Control.
Cliquea el botón Clone Repository.
Pega la url que copiaste anteriormente hasta arriba, donde dice algo como Provide repository URL y presiona la teclea Intro.
Selecciona la carpeta donde se guardará la carpeta del proyecto.
Abre la carpeta del proyecto importado.
Añade el contenido de la carpeta descompactada que contiene el código del
ejemplo, excepto el archivo .htaccess
.
Edita los archivos que desees.
Crea los íconos del proyecto con https://www.photopea.com/. Este paso se hizo com Microsoft Edge (versión Chromium), pero no funcionó con Google Chrome.
Crea los íconos enmascarables con https://maskable.app/ a partir del archivo «icono2048.png».
Coloca el archivo favicon.ico
en la raíz del proyecto.
Coloca los otros íconos en la carpeta img
y asegúrate de que estén
declarados en el archivo site.webmanifest
.
El archivo sw.js
tiene una lista de los archivos que se instalan.
El archivo instruccionesListadoSw.txt
te indica como generarla usando
Visual Studio Code.
Cada vez que modifiques los archivos, debes modificar el valor
de VERSION en el archivo sw.js
para poder ver los cambios
en el navegador.
Si tu proyecto no usa backend, haz clic derecho en
index.html
, selecciona Open with Live Server y se abre el
navegador para que puedas probar localmente el ejemplo.
Si tu proyecto usa PHP, haz clic derecho en
index.html
, selecciona PHP Server: serve project y se abre
el navegador para que puedas probar localmente el ejemplo.
Haz al menos una captura de la ventana de tu aplicación con orientación vertical y otra con orientación horizontal, todas ellas de 320 px como mínimo y 3,840 px como máximo.
Coloca las capturas en la carpeta img
y asegúrate de que estén
declaradas en el archivo site.webmanifest
.
Cuando desarrolles, es incómodo modificar la versión cada que realizas cambios; en ves de ello desinstala la app:
Abre las herramientas de depuración haciendo clic derecho en la página y selecciona Inspeccionar (o Inspect si aparece en inglés).
En la Pestaña Aplicación (o Application en inglés) selecciona Almacenamoento (o Storage en inglés). Cliquea Borrar datos del sitio.
Recarga la app, de preferencia haciendo clic derecho en el ícono de volver a cargar la página y seleccionando vaciar caché y volver a cargar de manera forzada (o algo parecido). Si no aparece un menú emergente, simplemente cliquea volver a cargar la página . Revisa que no aparezca ningún error ni en la pestañas Consola, ni en Red.
Tanbién puedes usar la combinación de teclas Ctrl+Mayúsculas+r para forzar que se actualice temporalmente el navegador en caso de que no se vean los cambios.
En la Pestaña Aplicación (o Application en inglés) selecciona Archivo de manifiesto (o Manifest file en inglés). Esta herramienta analiza la estructura del archivo de manifiesto y te indica si hay un error. InfinityFree bloquea el análisis de las imágenes.
En la Pestaña Aplicación (o Application en inglés) selecciona Almacenamiento en caché (o Cache storage en inglés). Aquí puedes revisar si el caché de la aplicación se llenó correctamente. En caso de que esté vacío, es que hubo algún error durante la carga y la app se ejecuta más lenta.
Cuando usas GitHub pages, antes de subir los archivos, no
debes modificar el valor de VERSION en el archivo sw.js
.
Para subir el código a GitHub, en la sección de SOURCE CONTROL, en Message introduce un mensaje sobre los cambios que hiciste, por ejemplo index.html corregido, selecciona v y luego Commit & Push.
Si usas GitHub pages:
Entra a la página de tu repositorio y abajo a la derecha, selecciona el enlace github-pages.
Se muestran los despliegues realizados. Recarga la página hasta que apareca el mensaje de tu último push con una palomita dentro de un círculo verde.
A partir de este momento, espera al menos 11 minutos para modificar el
valor de VERSION en el archivo sw.js
y volver a subir
el proyecto.
Si no usas GitHub pages:
Sube el proyecto al hosting que elijas sin incluir el archivo
.htaccess
. En algunos casos puedes usar
filezilla
(https://filezilla-project.org/)
En algunos host como InfinityFree, tienes que configurar el certificado SSL.
En algunos host, como InfinityFree, debes subir el
archivo .htaccess
cuando el certificado SSL se ha creado e
instalado. Sirve para forzar el uso de https, para eliminar el
control de cache, pues ahora lo lleva el service worker y para asignar el
mime type correcto para el archivo de manifest.
Abre un navegador y prueba el proyecto en tu hosting.
En el hosting InfinityFree, la primera ves que corres la página, puede marcar un mensaje de error, pero al recargar funciona correctamente. Puedes evitar este problema usando un dominio propio.
Instala y usa tu PWA en Windows. Aunque en este video se recomienda usar Edge, al momento de actualizar el contenido, la opción más recomendada es Chrome para que te muestre las descripciones y las capturas de pantalla.
Instala y usa tu PWA en Android. Al momento de actualizar las notas, tal vez no te aparezca el botón para instalar y tengas que seleccionar la acción de instalar que aparece en el menú de extensión de Chrome.
Haz clic en los triángulos para expandir las carpetas
1 | <!DOCTYPE html> |
2 | <html lang="es"> |
3 | |
4 | <head> |
5 | |
6 | <meta charset="UTF-8"> |
7 | |
8 | <title>PWA Básica</title> |
9 | |
10 | <!-- Resumen para los motores de búsqueda. --> |
11 | <meta name="description" content="Ejemplo de PWA"> |
12 | |
13 | <script src="js/registraServiceWorker.js"></script> |
14 | |
15 | <meta name="viewport" content="width=device-width"> |
16 | |
17 | <!-- Color de la barra de navegación de Chrome en dispositivos móviles. --> |
18 | <meta name="theme-color" content="#cbc693"> |
19 | |
20 | <!-- Ícono para la página web, que normalmente se pone en la raíz del sitio. |
21 | Puede ser diferente para cada página. --> |
22 | <link rel="icon" sizes="32x32" href="favicon.ico"> |
23 | |
24 | <!-- Configuración de la PWA para Chrome, Edge y Safari. |
25 | Debe ponerse en todas las páginas. --> |
26 | <link rel="manifest" href="site.webmanifest"> |
27 | |
28 | <link rel="stylesheet" href="css/estilos.css"> |
29 | |
30 | </head> |
31 | |
32 | <body> |
33 | |
34 | <h1>PWA Básica</h1> |
35 | |
36 | <p>Hola mundo.</p> |
37 | |
38 | </body> |
39 | |
40 | </html> |
1 | html { |
2 | color-scheme: light dark; |
3 | font-family: sans-serif; |
4 | } |
1 | "use strict" // usa JavaScript en modo estricto. |
2 | |
3 | const nombreDeServiceWorker = "sw.js" |
4 | |
5 | try { |
6 | navigator.serviceWorker.register(nombreDeServiceWorker) |
7 | .then(registro => { |
8 | console.log(nombreDeServiceWorker, "registrado.") |
9 | console.log(registro) |
10 | }) |
11 | .catch(error => console.log(error)) |
12 | } catch (error) { |
13 | console.log(error) |
14 | } |
Este archivo sirve para configurar los instaladores de la aplicación.
short_name
Nombre corto. Normalmente se despliega en dispositivos móviles. Máximo 20 caracteres.
name
Nombre largo. Normalmente se despliega en computadoras de escritorio. Máximo 30 caracteres.
id
Identificador del archivo de instalación. Normalmente es la ruta del archivo inicial de la app.
start_url
Ruta del archivo inicial de la app.
display
Forma de mostrar la app. El término standalone
significa que no
se muestra la barra de navegación del navegador web.
theme_color
Color de la barra de estado (en dispositivos móviles) o de título (en computadoras de escritorio) de la app.
background_color
Color de fondo de la pantalla desplah en dispositivos móviles.
description
Describe el propósito de la aplicación. Aparece en el cuadro de diálogo que muestra el navegador al instalar la app.
screenshots
Listado de máximo 8 capturas de pantalla. Aparecen en el cuadro de diálogo
que muestra el navegador al instalar la app. Debes incluir al menos una con
"form_factor": "wide"
y otra con
"form_factor": "narrow"
.
El ancho y la altura deben ser de 320 px como mínimo y 3,840 px como máximo.
La dimensión máxima no puede ser más de 2.3 veces mayor que la dimensión mínima.
Todas las capturas de pantalla con el valor del mismo factor de forma deben tener relaciones de aspecto idénticas.
Solo se admiten los formatos de imagen JPEG y PNG.
Solo se mostrarán ocho capturas de pantalla. Si se agregan más, el usuario-agente simplemente los ignora.
https://web.dev/patterns/web-apps/richer-install-ui?hl=es-419
icons
Listado de íconos en distintas resoluciones para los instaladores de la app. Se selecciona el que se vea mejor según las característocas del dispositivo.
src
Url de la imagen dentro de la app.
sizes
Dimensiones en pixeles de la imagen, anchoxalto.
type
Tipo mime de la imagen.
purpose
Forma en que se usa la imagen.
maskable
La imagen puede recortarse de forma segura para tomar distintas formas, como círculos, gotas, cuadrados con esquinas redondeadas, etc. Normalmente se usa para dispositivos móviles.
any
No se puede asegurar nada sobre la imagen. Normalmente se usa para dispositivos de escritorio.
Normalmente debe proporcionarse un juego de íconos con purpose
any
y otro juego de íconos con purpose
maskable
.
form_factor
Orientación de una screenshot.
wide
La screenshot tiene una orientación horizontal. Normalmente la creenshot se usa para dispositivos de escritorio.
narrow
La screenshot tiene una orientación vertical. Normalmente la creenshot se usa para dispositivos móviles.
Debes incluir al menos una screenshot con
"form_factor": "wide"
y otra con
"form_factor": "narrow"
.
label
Descripción de una screenshot. Aparece en el cuadro de diálogo que muestra el navegador al instalar la app.
1 | { |
2 | "short_name": "PWA", |
3 | "name": "Ejemplo de PWA", |
4 | "id": "/index.html", |
5 | "start_url": "/index.html", |
6 | "display": "standalone", |
7 | "theme_color": "#cbc693", |
8 | "background_color": "#ffffff", |
9 | "description": "Ejemplos básico de PWA.", |
10 | "screenshots": [ |
11 | { |
12 | "src": "/img/screenshot_horizontal.png", |
13 | "sizes": "1507x777", |
14 | "type": "image/png", |
15 | "form_factor": "wide", |
16 | "label": "PWA Básica" |
17 | }, |
18 | { |
19 | "src": "/img/screenshot_vertical.png", |
20 | "sizes": "591x980", |
21 | "type": "image/png", |
22 | "form_factor": "narrow", |
23 | "label": "PWA Básica (2)" |
24 | } |
25 | ], |
26 | "icons": [ |
27 | { |
28 | "src": "/img/maskable_icon_x48.png", |
29 | "sizes": "48x48", |
30 | "type": "image/png", |
31 | "purpose": "any" |
32 | }, |
33 | { |
34 | "src": "/img/maskable_icon_x72.png", |
35 | "sizes": "72x72", |
36 | "type": "image/png", |
37 | "purpose": "any" |
38 | }, |
39 | { |
40 | "src": "/img/maskable_icon_x96.png", |
41 | "sizes": "96x96", |
42 | "type": "image/png", |
43 | "purpose": "any" |
44 | }, |
45 | { |
46 | "src": "/img/maskable_icon_x128.png", |
47 | "sizes": "128x128", |
48 | "type": "image/png", |
49 | "purpose": "any" |
50 | }, |
51 | { |
52 | "src": "/img/maskable_icon_x192.png", |
53 | "sizes": "192x192", |
54 | "type": "image/png", |
55 | "purpose": "any" |
56 | }, |
57 | { |
58 | "src": "/img/maskable_icon_x384.png", |
59 | "sizes": "384x384", |
60 | "type": "image/png", |
61 | "purpose": "any" |
62 | }, |
63 | { |
64 | "src": "/img/maskable_icon_x512.png", |
65 | "sizes": "512x512", |
66 | "type": "image/png", |
67 | "purpose": "any" |
68 | }, |
69 | { |
70 | "src": "/img/maskable_icon.png", |
71 | "sizes": "2730x2730", |
72 | "type": "image/png", |
73 | "purpose": "any" |
74 | }, |
75 | { |
76 | "src": "/img/icono2048.png", |
77 | "sizes": "2048x2048", |
78 | "type": "image/png", |
79 | "purpose": "any" |
80 | }, |
81 | { |
82 | "src": "/img/maskable_icon_x48.png", |
83 | "sizes": "48x48", |
84 | "type": "image/png", |
85 | "purpose": "maskable" |
86 | }, |
87 | { |
88 | "src": "/img/maskable_icon_x72.png", |
89 | "sizes": "72x72", |
90 | "type": "image/png", |
91 | "purpose": "maskable" |
92 | }, |
93 | { |
94 | "src": "/img/maskable_icon_x96.png", |
95 | "sizes": "96x96", |
96 | "type": "image/png", |
97 | "purpose": "maskable" |
98 | }, |
99 | { |
100 | "src": "/img/maskable_icon_x128.png", |
101 | "sizes": "128x128", |
102 | "type": "image/png", |
103 | "purpose": "maskable" |
104 | }, |
105 | { |
106 | "src": "/img/maskable_icon_x192.png", |
107 | "sizes": "192x192", |
108 | "type": "image/png", |
109 | "purpose": "maskable" |
110 | }, |
111 | { |
112 | "src": "/img/maskable_icon_x384.png", |
113 | "sizes": "384x384", |
114 | "type": "image/png", |
115 | "purpose": "maskable" |
116 | }, |
117 | { |
118 | "src": "/img/maskable_icon_x512.png", |
119 | "sizes": "512x512", |
120 | "type": "image/png", |
121 | "purpose": "maskable" |
122 | }, |
123 | { |
124 | "src": "/img/maskable_icon.png", |
125 | "sizes": "2730x2730", |
126 | "type": "image/png", |
127 | "purpose": "maskable" |
128 | } |
129 | ] |
130 | } |
1 | Generar el listado de archivos del sw.js desde Visual Studio Code. |
2 | 1. Abrir una terminal desde el menú |
3 | Terminal > New Terminal |
4 | |
5 | 2. Desde la terminal introducir la orden: |
6 | Get-ChildItem -path . -Recurse | Select Directory,Name | Out-File archivos.txt |
7 | |
8 | 3. Abrir el archivo generado, que se llama |
9 | archivos.txt |
10 | y sobre este, realizar los pasos que siguen: |
11 | |
12 | 4. Quita del archivo archivos.txt: |
13 | * el encabezado, |
14 | * todas las carpetas, |
15 | * el archivo .vscode/settings.json, |
16 | * el archivo .htaccess, |
17 | * el archivo archivos.txt, |
18 | * este archivo (instruccionesListadoSw.txt), |
19 | * el archivo jsconfig.json, |
20 | * el archivo sw.js, |
21 | * el archivo de la base de datos, que termina en ".bd" y |
22 | está en la carpeta srv, |
23 | * todos los archivos de php y |
24 | * las líneas en blanco del final |
25 | |
26 | 5. Cambia los \ por / desde Visual Studio Code con las siguientes |
27 | combinaciones de teclas: |
28 | |
29 | Ctrl+H En el diálogo que aparece introduce lo siguiente: |
30 | Find:\ |
31 | Replace:/ |
32 | |
33 | Clic en el icono Reemplaza todo o Replace All y luego teclea ESC |
34 | |
35 | 6. Coloca las comillas y coma del final de cada línea desde Visual |
36 | Studio Code con las siguientes combinaciones de teclas: |
37 | |
38 | Ctrl+H En el diálogo que aparece, selecciona el botón |
39 | ".*" |
40 | e introduce lo siguiente: |
41 | Find:\s*$ |
42 | Replace:", |
43 | |
44 | Clic en el icono Reemplaza todo o Replace All y luego teclea ESC |
45 | |
46 | 7. Marca la carpeta inicial, presiona la combinación de teclas: |
47 | |
48 | Shift+Ctrl+L |
49 | |
50 | borra la selección, teclea " y luego ESC |
51 | |
52 | 8. Cambia las secuencias de espacios por / con las siguientes |
53 | combinaciones de teclas: |
54 | |
55 | Ctrl+H En el diálogo que aparece, selecciona el botón |
56 | ".*" |
57 | e introduce lo siguiente: |
58 | Find:\s+ |
59 | Replace:/ |
60 | |
61 | Clic en el icono Reemplaza todo o Replace All y luego teclea ESC |
62 | |
63 | 9. Cambia las "/ por " con las siguientes combinaciones de teclas: |
64 | |
65 | Ctrl+H En el diálogo que aparece, quita la selección del botón |
66 | ".*" |
67 | e introduce lo siguiente: |
68 | Find:"/ |
69 | Replace:" |
70 | |
71 | Clic en el icono Reemplaza todo o Replace All y luego teclea ESC |
72 | |
73 | 10. Copia el texto al archivo |
74 | sw.js |
75 | en el contenido del arreglo llamado ARCHIVOS, pero recuerda |
76 | mantener el último elemento, que dice: |
77 | "/" |
1 | "favicon.ico", |
2 | "index.html", |
3 | "site.webmanifest", |
4 | "css/estilos.css", |
5 | "img/icono2048.png", |
6 | "img/maskable_icon.png", |
7 | "img/maskable_icon_x128.png", |
8 | "img/maskable_icon_x192.png", |
9 | "img/maskable_icon_x384.png", |
10 | "img/maskable_icon_x48.png", |
11 | "img/maskable_icon_x512.png", |
12 | "img/maskable_icon_x72.png", |
13 | "img/maskable_icon_x96.png", |
14 | "img/screenshot_horizontal.png", |
15 | "img/screenshot_vertical.png", |
16 | "js/registraServiceWorker.js", |
1 | /* Este archivo debe estar colocado en la carpeta raíz del sitio. |
2 | * |
3 | * Cualquier cambio en el contenido de este archivo hace que el service |
4 | * worker se reinstale. */ |
5 | |
6 | /** |
7 | * Cambia el número de la versión cuando cambia el contenido de los |
8 | * archivos. |
9 | * |
10 | * El número a la izquierda del punto (.), en este caso <q>1</q>, se |
11 | * conoce como número mayor y se cambia cuando se realizan |
12 | * modificaciones grandes o importantes. |
13 | * |
14 | * El número a la derecha del punto (.), en este caso <q>00</q>, se |
15 | * conoce como número menor y se cambia cuando se realizan |
16 | * modificaciones menores. |
17 | */ |
18 | const VERSION = "1.00" |
19 | |
20 | /** Nombre del archivo de cache. */ |
21 | const CACHE = "ejemploPWA" |
22 | |
23 | /** |
24 | * Archivos requeridos para que la aplicación funcione fuera de |
25 | * línea. |
26 | */ |
27 | const ARCHIVOS = [ |
28 | "favicon.ico", |
29 | "index.html", |
30 | "site.webmanifest", |
31 | "css/estilos.css", |
32 | "img/icono2048.png", |
33 | "img/maskable_icon.png", |
34 | "img/maskable_icon_x128.png", |
35 | "img/maskable_icon_x192.png", |
36 | "img/maskable_icon_x384.png", |
37 | "img/maskable_icon_x48.png", |
38 | "img/maskable_icon_x512.png", |
39 | "img/maskable_icon_x72.png", |
40 | "img/maskable_icon_x96.png", |
41 | "img/screenshot_horizontal.png", |
42 | "img/screenshot_vertical.png", |
43 | "js/registraServiceWorker.js", |
44 | "/" |
45 | ] |
46 | |
47 | // Verifica si el código corre dentro de un service worker. |
48 | if (self instanceof ServiceWorkerGlobalScope) { |
49 | // Evento al empezar a instalar el servide worker, |
50 | self.addEventListener("install", |
51 | (/** @type {ExtendableEvent} */ evt) => { |
52 | console.log("El service worker se está instalando.") |
53 | evt.waitUntil(llenaElCache()) |
54 | }) |
55 | |
56 | // Evento al solicitar información a la red. |
57 | self.addEventListener("fetch", (/** @type {FetchEvent} */ evt) => { |
58 | if (evt.request.method === "GET") { |
59 | evt.respondWith(buscaLaRespuestaEnElCache(evt)) |
60 | } |
61 | }) |
62 | |
63 | // Evento cuando el service worker se vuelve activo. |
64 | self.addEventListener("activate", |
65 | () => console.log("El service worker está activo.")) |
66 | } |
67 | |
68 | async function llenaElCache() { |
69 | console.log("Intentando cargar caché:", CACHE) |
70 | // Borra todos los cachés. |
71 | const keys = await caches.keys() |
72 | for (const key of keys) { |
73 | await caches.delete(key) |
74 | } |
75 | // Abre el caché de este service worker. |
76 | const cache = await caches.open(CACHE) |
77 | // Carga el listado de ARCHIVOS. |
78 | await cache.addAll(ARCHIVOS) |
79 | console.log("Cache cargado:", CACHE) |
80 | console.log("Versión:", VERSION) |
81 | } |
82 | |
83 | /** @param {FetchEvent} evt */ |
84 | async function buscaLaRespuestaEnElCache(evt) { |
85 | // Abre el caché. |
86 | const cache = await caches.open(CACHE) |
87 | const request = evt.request |
88 | /* Busca la respuesta a la solicitud en el contenido del caché, sin |
89 | * tomar en cuenta la parte después del símbolo "?" en la URL. */ |
90 | const response = await cache.match(request, { ignoreSearch: true }) |
91 | if (response === undefined) { |
92 | /* Si no la encuentra, empieza a descargar de la red y devuelve |
93 | * la promesa. */ |
94 | return fetch(request) |
95 | } else { |
96 | // Si la encuentra, devuelve la respuesta encontrada en el caché. |
97 | return response |
98 | } |
99 | } |
1 | AddType application/manifest+json .webmanifest |
2 | |
3 | ExpiresActive On |
4 | |
5 | Header set Cache-Control "max-age=1, must-revalidate" |
6 | |
7 | RewriteEngine On |
8 | |
9 | RewriteCond %{HTTP:X-Forwarded-Proto} !https |
10 | RewriteCond %{HTTPS} off |
11 | RewriteCond %{HTTP:CF-Visitor} !{"scheme":"https"} |
12 | RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301] |
13 |
Este archivo ayuda a detectar errores en los archivos del proyecto.
Lo utiliza principalmente Visual Studio Code.
No se explica aquí su estructura, pero puede encontrarse la explicación de todo en la documentación del sitio de Visual Studio Code.
1 | { |
2 | "compilerOptions": { |
3 | "checkJs": true, |
4 | "strictNullChecks": true, |
5 | "target": "ES6", |
6 | "module": "Node16", |
7 | "moduleResolution": "Node16", |
8 | "lib": [ |
9 | "ES2017", |
10 | "WebWorker", |
11 | "DOM" |
12 | ] |
13 | } |
14 | } |
En esta lección se presentó la estructura básica de una PWA.