1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
|
---
title: Estructura de una aplicación web progresiva
slug: Web/Progressive_web_apps/App_structure
tags:
- Estructura
- Intérprete de la aplicación
- PWAs
- Servicio workers
- Streams
- Transmisiones
- aplicaciones web progresivas
- js13kGames
- progresiva
translation_of: Web/Progressive_web_apps/App_structure
---
<div>{{PreviousMenuNext("Web/Progressive_web_apps/Introduction", "Web/Progressive_web_apps/Offline_Service_workers", "Web/Progressive_web_apps")}}</div>
<p class="summary">Ahora que conoces la teoría detrás de las PWAs, veamos la estructura recomendada de una aplicación real. Comenzaremos analizando la aplicación <a href="https://mdn.github.io/pwa-examples/js13kpwa/">js13kPWA</a>, veamos por qué está construida de esa manera y qué beneficios aporta.</p>
<h2 id="Arquitectura_de_una_aplicación">Arquitectura de una aplicación</h2>
<p>Hay dos principales y diferentes enfoques para representar un sitio web — en el servidor o en el cliente. Ambos tienen sus ventajas y desventajas, y puedes combinar los dos enfoques hasta cierto punto.</p>
<ul>
<li>La representación del lado del servidor (SSR) significa que un sitio web se representa en el servidor, por lo que ofrece una primera carga más rápida, pero navegar entre páginas requiere descargar contenido HTML nuevo. Funciona muy bien en todos los navegadores, pero adolece en términos de tiempo de navegación entre páginas y, por lo tanto, rendimiento percibido general — cargar una página requiere un nuevo viaje de ida y vuelta al servidor.</li>
<li>La representación de lado del cliente (CSR) permite que el sitio web se actualice en el navegador casi instantáneamente cuando se navega a diferentes páginas, pero requiere más de un golpe de descarga inicial y una representación adicional en el cliente al principio. El sitio web es más lento en una visita inicial, pero puede ser más rápido para navegar.</li>
</ul>
<p>La combinación de SSR con CSR puede generar los mejores resultados: puedes representar un sitio web en el servidor, almacenar en caché su contenido y luego actualizar la representación en el lado del cliente cuando sea necesario. La carga de la primera página es rápida debido al SSR y la navegación entre páginas es fluida porque el cliente puede volver a renderizar la página solo con las partes que han cambiado.</p>
<p>Las PWAs se pueden crear utilizando cualquier enfoque que desees, pero algunas funcionarán mejor que otras. El enfoque más popular es el concepto de "intérprete de la aplicación", que combina SSR y CSR exactamente de la manera descrita anteriormente, y además sigue la metodología "fuera de línea primero" que explicaremos en detalle en los próximos artículos y utilizaremos en nuestra aplicación de ejemplo. También hay un nuevo enfoque que involucra la {{web.link("/es/docs/Web/API/Streams_API", "API Streams")}}, que mencionaremos brevemente.</p>
<h2 id="Intérprete_de_la_aplicación">Intérprete de la aplicación</h2>
<p>El concepto de intérprete de la aplicación se ocupa de cargar una interfaz de usuario mínima lo antes posible y luego almacenarla en caché para que esté disponible sin conexión para visitas posteriores antes de cargar todo el contenido de la aplicación. De esa manera, la próxima vez que alguien visite la aplicación desde el dispositivo, la interfaz de usuario se cargará desde la caché de inmediato y se solicitará cualquier contenido nuevo del servidor (si aún no está disponible en la caché).</p>
<p>Esta estructura es rápida y también se siente rápida, ya que el usuario ve "algo" instantáneamente, en lugar de una ruleta de carga o una página en blanco. También permite que el sitio web sea accesible sin conexión si la conexión de red no está disponible.</p>
<p>Podemos controlar lo que se solicita del servidor y lo que se recupera de la caché con un {{web.link("/es/docs/Web/API/Service_Worker_API", "servicio worker")}}, que se explicará en detalle en el próximo artículo, por ahora centrémonos en la estructura en sí misma.</p>
<h3 id="¿Por_qué_debería_usarla">¿Por qué debería usarla?</h3>
<p>Esta arquitectura permite que un sitio web se beneficie al máximo de todas las funciones de PWA — almacena en caché el intérprete de la aplicación y administra el contenido dinámico de una manera que mejora enormemente el rendimiento. Además del intérprete básico, puedes agregar otras funciones como {{web.link("/es/docs/Web/Progressive_web_apps/Add_to_home_screen", "agregar a la pantalla de inicio")}} o {{web.link("/es/docs/Web/API/Push_API", "notificaciones push")}}, con la certeza de que la aplicación seguirá funcionando correctamente si no son compatibles con el navegador del usuario — esta es la belleza de la mejora progresiva.</p>
<p>El sitio web se siente como una aplicación nativa con interacción instantánea y un rendimiento sólido, al tiempo que conserva todos los beneficios de la web.</p>
<h3 id="Ser_enlazable_progresiva_y_adaptable_por_diseño">Ser enlazable, progresiva y adaptable por diseño</h3>
<p>Es importante recordar las ventajas de PWA y tenerlas en cuenta al diseñar la aplicación. El enfoque del intérprete de la aplicación permite que los sitios web sean:</p>
<ul>
<li>Enlazable: aunque se comporta como una aplicación nativa, sigue siendo un sitio web; puedes hacer clic en los enlaces dentro de la página y enviar una URL a alguien si deseas compartirla.</li>
<li>Progresiva: comienza con el "buen, antiguo sitio web básico" y agrega progresivamente nuevas funciones mientras recuerdas detectar si están disponibles en el navegador y manejas con elegancia cualquier error que surja si no hay soporte disponible. Por ejemplo, un modo fuera de línea con la ayuda del servicio <em>workers</em> es solo un rasgo adicional que mejora la experiencia del sitio web, pero aún se puede usar perfectamente sin él.</li>
<li>Adaptable: El diseño web adaptable también se aplica a las aplicaciones web progresivas, ya que ambas son principalmente para dispositivos móviles. Hay una gran variedad de dispositivos con navegadores — es importante preparar tu sitio web para que funcione en diferentes tamaños de pantalla, ventanas gráficas o densidades de píxeles, utilizando tecnologías como {{web.link("/es/docs/Mozilla/Mobile/Viewport_meta_tag", "metaetiqueta de la ventana gráfica")}}, {{web.link("/es/docs/Web/CSS/Media_Queries/Using_media_queries", "consultas de medios CSS")}}, {{web.link("/es/docs/Web/CSS/CSS_Flexible_Box_Layout", "Flexbox")}} y {{web.link("/es/docs/Web/CSS/CSS_Grid_Layout", "Rejilla CSS")}}.</li>
</ul>
<h2 id="Concepto_diferente_streams_o_transmisiones">Concepto diferente: <em>streams</em> o transmisiones</h2>
<p>Se puede lograr un enfoque completamente diferente para la representación del lado del servidor o del cliente con la {{web.link("/es/docs/Web/API/Streams_API", "API Streams")}}. Con un poco de ayuda del servicio <em>workers</em>, las transmisiones pueden mejorar en gran medida la forma en que analizamos el contenido.</p>
<p>El modelo de intérprete de la aplicación requiere que todos los recursos estén disponibles antes de que el sitio web pueda comenzar a renderizarse. Es diferente con HTML, ya que el navegador ya está transmitiendo los datos y puede ver cuándo se cargan y procesan los elementos en el sitio web. Sin embargo, para que JavaScript esté "operativo", se debe descargar en su totalidad.</p>
<p>La API de <em>Streams</em> permite a los desarrolladores tener acceso directo a la transmisión de datos desde el servidor — si deseas realizar una operación en los datos (por ejemplo, agregar un filtro a un video), ya no necesitas esperar a que se complete la descarga y convertirla en un blob (o lo que sea) — puedes comenzar de inmediato. Proporciona un control detallado: la transmisión se puede iniciar, encadenar con otra transmisión, cancelar, verificar errores y más.</p>
<p>En teoría, la transmisión es un mejor modelo, pero también es más complejo, y en el momento de redactar este artículo (marzo de 2018), la API de <em>Streams</em> todavía está en proceso y aún no está completamente disponible en ninguno de los principales navegadores. Cuando esté disponible, será la forma más rápida de servir el contenido — los beneficios serán enormes en términos de rendimiento.</p>
<p>Para obtener ejemplos trabajando y más información, consulta la {{web.link("/es/docs/Web/API/Streams_API", "documentación de la API de Streams")}}.</p>
<h2 id="Estructura_de_nuestra_aplicación_de_ejemplo">Estructura de nuestra aplicación de ejemplo</h2>
<p>La estructura del sitio web <a href="https://mdn.github.io/pwa-examples/js13kpwa/">js13kPWA</a> es bastante simple: consta de un solo archivo HTML (<a href="https://github.com/mdn/pwa-examples/blob/master/js13kpwa/index.html">index.html</a>) con estilo CSS básico (<a href="https://github.com/mdn/pwa-examples/blob/master/js13kpwa/style.css">style.css</a>) y algunas imágenes, scripts y tipos de letra. La estructura de la carpeta se ve así:</p>
<p><img alt="Estructura del directorio de js13kPWA." src="https://mdn.mozillademos.org/files/15925/js13kpwa-directory.png" style="border-style: solid; border-width: 1px; display: block; height: 356px; margin: 0px auto; width: 320px;"></p>
<h3 id="El_HTML">El HTML</h3>
<p>Desde el punto de vista HTML, el intérprete de la aplicación es todo lo que está fuera de la sección de contenido:</p>
<pre class="brush: html notranslate"><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>js13kGames — Entradas del marco A</title>
<meta name="description" content="Una lista de las entradas del marco A enviadas a la competencia js13kGames 2017, que se utiliza como ejemplo para los artículos de MDN sobre Aplicaciones Web Progresivas">
<meta name="author" content="end3r">
<meta name="theme-color" content="#B12A34">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta property="og:image" content="icons/icon-512.png">
<link rel="shortcut icon" href="favicon.ico">
<link rel="stylesheet" href="style.css">
<link rel="manifest" href="js13kpwa.webmanifest">
<script src="data/games.js" defer></script>
<script src="app.js" defer></script>
</head>
<body>
<header>
<p><a class="logo" href="http://js13kgames.com"><img src="img/js13kgames.png" alt="js13kGames"></a></p>
</header>
<main>
<h1>js13kGames — Entradas del marco A</h1>
<p class="description">Lista de juegos enviada a <a href="http://js13kgames.com/aframe">categoría Marco A</a> en la competencia de <a href="http://2017.js13kgames.com">js13kGames 2017</a>. Puedes <a href="https://github.com/mdn/pwa-examples/blob/master/js13kpwa">bifurcar js13kPWA en GitHub</a> para revisar su código fuente.</p>
<button id="notifications">Solicitar notificaciones ficticias</button>
<section id="content">
// Contenido insertado aquí
</section>
</main>
<footer>
<p>© js13kGames 2012-2020, creado y mantenido por <a href="http://end3r.com">Andrzej Mazur</a> de <a href="http://enclavegames.com">Enclave Games</a>.</p>
</footer>
</body>
</html></pre>
<p>La sección {{HTMLElement("head")}} contiene información básica como título, descripción y enlaces a CSS, manifiesto web, archivo JS de contenido de juegos y app.js; ahí es donde se inicia nuestra aplicación JavaScript. El {{HTMLElement("body")}} se divide en {{HTMLElement("header")}} (que contiene la imagen vinculada), {{HTMLElement("main")}} de la página (con título, descripción y lugar para el contenido) y {{HTMLElement("footer")}} (derechos de autor y enlaces).</p>
<p>El único trabajo de la aplicación es enumerar todas las entradas del Marco A de la competencia js13kGames 2017. Como puedes ver, es un sitio web muy común de una página — el objetivo es tener algo simple para que podamos centrarnos en la implementación de las características reales de PWA.</p>
<h3 id="El_CSS">El CSS</h3>
<p>El CSS también es lo más sencillo posible: usa {{CSSxRef("@font-face")}} para cargar y usar un tipo de letra personalizado, y aplica un estilo simple de los elementos HTML. El enfoque general es que el diseño se vea bien tanto en dispositivos móviles (con un enfoque de diseño web adaptable) como en dispositivos de escritorio.</p>
<h3 id="El_main_de_la_aplicación_JavaScript">El <code>main</code> de la aplicación JavaScript</h3>
<p>El archivo <code>app.js</code> hace algunas cosas que veremos de cerca en los próximos artículos. En primer lugar, genera el contenido en base a esta plantilla:</p>
<pre class="brush: js notranslate">var template = "<article>\n\
<img src='data/img/SLUG.jpg' alt='NAME'>\n\
<h3>#POS. NAME</h3>\n\
<ul>\n\
<li><span>Author:</span> <strong>AUTHOR</strong></li>\n\
<li><span>Twitter:</span> <a href='https://twitter.com/TWITTER'>@TWITTER</a></li>\n\
<li><span>Website:</span> <a href='http://WEBSITE/'>WEBSITE</a></li>\n\
<li><span>GitHub:</span> <a href='https://GITHUB'>GITHUB</a></li>\n\
<li><span>More:</span> <a href='http://js13kgames.com/entries/SLUG'>js13kgames.com/entries/SLUG</a></li>\n\
</ul>\n\
</article>";
var content = '';
for(var i=0; i<games.length; i++) {
var entry = template.replace(/POS/g,(i+1))
.replace(/SLUG/g,games[i].slug)
.replace(/NAME/g,games[i].name)
.replace(/AUTHOR/g,games[i].author)
.replace(/TWITTER/g,games[i].twitter)
.replace(/WEBSITE/g,games[i].website)
.replace(/GITHUB/g,games[i].github);
entry = entry.replace('<a href=\'http:///\'></a>','-');
content += entry;
};
document.getElementById('content').innerHTML = content;</pre>
<p>A continuación, registra un servicio <em>works</em>:</p>
<pre class="brush: js notranslate">if('serviceWorker' in navigator) {
navigator.serviceWorker.register('/pwa-examples/js13kpwa/sw.js');
};</pre>
<p>El siguiente bloque de código solicita permiso para recibir notificaciones cuando se hace clic en un botón:</p>
<pre class="brush: js notranslate">var button = document.getElementById("notifications");
button.addEventListener('click', function(e) {
Notification.requestPermission().then(function(result) {
if(result === 'granted') {
randomNotification();
}
});
});</pre>
<p>El último bloque crea notificaciones que muestran un elemento seleccionado al azar de la lista de juegos:</p>
<pre class="brush: js notranslate">function randomNotification() {
var randomItem = Math.floor(Math.random()*games.length);
var notifTitle = games[randomItem].name;
var notifBody = 'Creado por '+games[randomItem].author+'.';
var notifImg = 'data/img/'+games[randomItem].slug+'.jpg';
var options = {
body: notifBody,
icon: notifImg
}
var notif = new Notification(notifTitle, options);
setTimeout(randomNotification, 30000);
}</pre>
<h3 id="El_servicio_worker">El servicio <em>worker</em></h3>
<p>El último archivo que veremos rápidamente es el servicio <em>worker</em>: <code>sw.js</code> — primero importa datos del archivo <code>games.js</code>:</p>
<pre class="brush: js notranslate">self.importScripts('data/games.js');</pre>
<p>A continuación, crea una lista de todos los archivos que se almacenarán en caché, tanto del intérprete de la aplicación como del contenido:</p>
<pre class="brush: js notranslate">var cacheName = 'js13kPWA-v1';
var appShellFiles = [
'/pwa-examples/js13kpwa/',
'/pwa-examples/js13kpwa/index.html',
'/pwa-examples/js13kpwa/app.js',
'/pwa-examples/js13kpwa/style.css',
'/pwa-examples/js13kpwa/fonts/graduate.eot',
'/pwa-examples/js13kpwa/fonts/graduate.ttf',
'/pwa-examples/js13kpwa/fonts/graduate.woff',
'/pwa-examples/js13kpwa/favicon.ico',
'/pwa-examples/js13kpwa/img/js13kgames.png',
'/pwa-examples/js13kpwa/img/bg.png',
'/pwa-examples/js13kpwa/icons/icon-32.png',
'/pwa-examples/js13kpwa/icons/icon-64.png',
'/pwa-examples/js13kpwa/icons/icon-96.png',
'/pwa-examples/js13kpwa/icons/icon-128.png',
'/pwa-examples/js13kpwa/icons/icon-168.png',
'/pwa-examples/js13kpwa/icons/icon-192.png',
'/pwa-examples/js13kpwa/icons/icon-256.png',
'/pwa-examples/js13kpwa/icons/icon-512.png'
];
var gamesImages = [];
for(var i=0; i<games.length; i++) {
gamesImages.push('data/img/'+games[i].slug+'.jpg');
}
var contentToCache = appShellFiles.concat(gamesImages);</pre>
<p>El siguiente bloque instala el servicio <em>worker</em>, que luego almacena en caché todos los archivos contenidos en la lista anterior:</p>
<pre class="brush: js notranslate">self.addEventListener('install', function(e) {
console.log('[Service Worker] Install');
e.waitUntil(
caches.open(cacheName).then(function(cache) {
console.log('[Servicio Worker] Almacena todo en caché: contenido e intérprete de la aplicación');
return cache.addAll(contentToCache);
})
);
});</pre>
<p>Por último, el servicio <em>worker</em> obtiene contenido de la caché si está disponible allí, lo cual proporciona una funcionalidad fuera de línea:</p>
<pre class="brush: js notranslate">self.addEventListener('fetch', function(e) {
e.respondWith(
caches.match(e.request).then(function(r) {
console.log('[Servicio Worker] Obteniendo recurso: '+e.request.url);
return r || fetch(e.request).then(function(response) {
return caches.open(cacheName).then(function(cache) {
console.log('[Servicio Worker] Almacena el nuevo recurso: '+e.request.url);
cache.put(e.request, response.clone());
return response;
});
});
})
);
});</pre>
<h3 id="Los_datos_de_JavaScript">Los datos de JavaScript</h3>
<p>Los datos de los juegos están presentes en el directorio <em>data</em> en forma de un objeto JavaScript (<a href="https://github.com/mdn/pwa-examples/blob/master/js13kpwa/data/games.js"><code>games.js</code></a>):</p>
<pre class="brush: js notranslate">var games = [
{
slug: 'perdido-en-el-ciberespacio',
name: 'Perdido en el ciberespacio',
author: 'Zosia y Bartek',
twitter: 'bartaz',
website: '',
github: 'github.com/bartaz/lost-in-cyberspace'
},
{
slug: 'vernissage',
name: 'Vernissage',
author: 'Platane',
twitter: 'platane_',
website: 'github.com/Platane',
github: 'github.com/Platane/js13k-2017'
},
// ...
{
slug: 'emma-3d',
name: 'Emma-3D',
author: 'Prateek Roushan',
twitter: '',
website: '',
github: 'github.com/coderprateek/Emma-3D'
}
];</pre>
<p>Cada entrada tiene su propia imagen en el directorio <code>data/img</code>. Este es nuestro contenido, cargado en la sección de contenido con JavaScript.</p>
<h2 id="Siguiente">Siguiente</h2>
<p>En el próximo artículo veremos con más detalle cómo se almacenan en caché el intérprete de la aplicación y el contenido para su uso sin conexión con la ayuda del servicio <em>worker</em>.</p>
<p>{{PreviousMenuNext("Web/Progressive_web_apps/Introduction", "Web/Progressive_web_apps/Offline_Service_workers", "Web/Progressive_web_apps")}}</p>
<p>{{QuickLinksWithSubpages("/es/docs/Web/Progressive_web_apps/")}}</p>
|