img_post_desktop_a_mobile

Siguiendo con la serie de artículos relacionados a la investigación e implementación de nuevas tecnologías implementadas en el Panel de Control de Mango que empezó con un post acerca de la experiencia de usuario y el manejo de sesiones, en esta entrega nos enfocamos en el mundo de las plataformas para móviles.

Desde hace ya varios años con la llegada de los smartphones la computación móvil cobró una relevancia antes impensada. Los requerimientos a la hora de hacer aplicaciones se modificaron: las pantallas son en general más pequeñas, los recursos más escasos y la conectividad errática.

Este cambio de paradigma dejó a las aplicaciones web mal paradas. Las llamadas aplicaciones nativas coparon al mundo móvil. La capacidad de instalarse en el sistema operativo y acceder a APIs del mismo dejaron en claro la brecha entre esta nueva ola de aplicaciones específicas para la plataforma y la web. Se destaca la capacidad de respuesta ante situaciones de mala o nula conectividad y la performance a la hora de responder a patrones y eventos propios de pantallas táctiles, especialmente el scroll.

Entonces surge naturalmente la pregunta:

¿Cómo se puede adaptar la web a estos cambios?

Apps instalables

Tu ícono en la vidriera principal

Nos encanta ordenar el Homescreen de nuestro teléfono por orden alfabético, uso, categoría y hasta color. Es muy importante para las aplicaciones tener su ícono en tu pantalla y que la tengas siempre presente. Los navegadores suelen tener una opción de add to Homescreen pero es tan molesto que casi nadie lo usa. Tan así que hay documentos y widgets para ayudar a los usuarios.

Chrome para Android agrego una feature llamada Web App install banners. Si un usuario entra seguido a un sitio web que tiene un manifest.json  y un Service Worker (del cual hablaremos más adelante) declarados, el navegador le muestra al usuario un banner que permite agregar el sitio al Homescreen con un solo click.

mobile

El manifest.json nos permite entre otras cosas declarar un nombre, ícono y descripción de la app para el Homescreen. Otra propiedad muy interesante es display que al ser seteada como «standalone» quita los controles del navegador al ejecutar la app desde la pantalla de inicio, limpiando la interface.

Para que tengas, para que guardes, para que archives

El caballito de batalla que tiene la web para acercarse a las plataformas móviles son los llamados Service Workers, los cuales no vamos a explicar en detalle (existen excelentes artículos introductorios) sino a limitarnos a describir sus bondades. En un sentido muy amplio los Service Workers son scripts de JavaScript que corren en background y nos permiten hacer tareas que no requieren necesariamente de la interacción del usuario. Una cosa muy interesante que nos permiten hacer los Service Workers es manejar un caché de nuestra web.

Los Service Workers introducen el evento «install» que nos permite saber cuando una app va a ser instalada (en general es la primera vez que el usuario ingresa). En este momento podemos guardar todos los assets que queramos en un caché, permitiendo que los archivos se recuperen del dispositivo en vez de ir a internet. Los cachés son versionados y podemos gestionar y borrar cachés viejos. Esta feature permite web apps realmente instalables, que reaccionan instantáneamente al abrir, sin necesidad de ir a la web cada vez que las abrimos.

Offline first

Estado de la conexión – ¿Lobo estás?

Un problema que encontramos constantemente en el mundo móvil es la fluctuación en la conectividad. El cambio de antenas, servicios limitados y áreas sin cobertura producen incertidumbre ante el estado de conexión del usuario.

Desde IE8 tenemos disponible la propiedad navigator.onLine. Por un lado podemos consultar este booleano para saber si el usuario está conectado o no, y también podemos escuchar los eventos «online» y «offline» para detectar cambios en la conectividad. En el Panel de Control de Mango advertimos al usuario cuando se queda sin conexión y avisamos cuando se restablece.

Screen Shot 2015-09-23 at 1.14.18 PM

 

Disponible en Chrome para Android y en más navegadores en un futuro cercano se encuentra la Network Information API. Esta API nos brinda información más detallada sobre el tipo y estado de la conexión. En muchos casos bajo 3G tiene sentido que nuestras apps se comporten más parecido a un estado offline que al escenario de una conexión óptima.

Los caballeros sí tienen memoria

Anteriormente hablamos un poco de los Service Workers y la capacidad de manipular cachés. ¿Qué pasa si utilizamos este poder para guardar los recursos necesarios a la hora de inicializar la app? El resultado son web apps o sitios web que funcionan offline. El caché nos permite guardar todo tipo de recursos que necesitemos más adelante. Si queremos guardar un artículo para ver más tarde, una font o un script, podemos hacerlo. Un ejemplo de ello es la landing page de BAFrontend.

Screen Shot 2015-09-23 at 3.00.50 PM

Más interesante aún, podemos manejar el evento «fetch» desde un Service Worker y decidir por cada request que haga nuestro sitio que decisión tomar. Podemos devolver un recurso de caché, hacer el request a nuestro servidor o incluso devolver un resultado totalmente nuevo basado en información relevante que nos da el navegador.

«Quedate tranquilo/a nosotros te llamamos»

Una gran ventaja que vemos en las apps nativas es la posibilidad de ejecutar tareas en background, sin necesidad de tener la aplicación abierta y las notificaciones push.

Estas funcionalidades llegan a la web a través de la Push y Task Scheduler APIs. Esto nos permite una mayor conciencia y capacidad de respuesta ante eventos que ocurren en momentos donde no necesariamente el usuario interactúa con la app pero que son realmente relevantes y/o que requieren acciones.

Mirando para adelante

Creo que es un buen momento para plantearnos como podemos adaptar nuestras aplicaciones y sitios web a los requerimientos derivados del uso de dispositivos móviles. Los navegadores están sumando características que nos permiten interacciones muy interesantes con usuarios con nuevas necesidades y que intentan eliminar esta dicotomía entre aplicaciones nativas y web para pasar a un contexto de cooperación mutua. Como se suele decir, utilizar «la herramienta correcta para el trabajo», conociendo las fortalezas que tiene la web y sumando estas nuevas APIs que ayudan a hacer nuestro trabajo.

Un tema muy importante que no fue incluído en este artículo pero seguramente lo será en futuras entregas son las técnicas que podemos utilizar para mejorar la performance a la hora de dibujar la UI e interactuar con navegadores en estos dispositivos.

Esta es una entrega de nuestra serie sobre Front End y nuevas tecnologías web, seguí nuestra cuenta de Twitter (@getmango) para conocer nuevos artículos del tema.

Toda duda o sugerencia es bienvenida. Te podés contactar conmigo vía Twitter (@impronunciable).

Funciones async en JavaScript

El comité TC39, encargado de la estandarización de ECMAScript (JavaScript para los amigos), incluyó en la versión ES2016/ES7 las llamadas Async Functions. Esta nueva característica, proveniente de C#, permite estructurar nuestro código asíncrono utilizando sintaxis del mundo síncrono, facilitando la manera en que pensamos el flujo de la aplicación.

Utilizando el caso de los llamados XHR vamos a mostrar la evolución en el trato del código asíncrono en JavaScript para llegar a esta nueva construcción llamada «Async Functions».

En el comienzo todo era callback

Hace mucho tiempo, en una galaxia muy muy lejana, la gente de Microsoft inventó una tecnología que luego se haría conocer como AJAX. Como los requests al servidor son muy lentos y el navegador solo permitía el uso de un thread para nuestro código (el mismo thread de UI), realizar un request xhr y esperar por la respuesta para seguir con el flujo de la app no es una opción ya que bloquearía el uso del resto de la aplicación para el usuario. Es por eso que cada vez que ejecutábamos/ejecutamos un request utilizando el objeto XMLHttpRequest pasamos una función (o más) a ejecutar cuando la respuesta es recibida.

Usando jQuery para el ejemplo, un pedido AJAX al servidor se ve así:

function pedirPartidos(competencia, success, error) {
  $.ajax({
    "url": "/api/partidos.json",
    "success": success,
    "error": error,
    "data": {
      "competencia": competencia      
    }
  });
}

function copaAmerica() {
  pedirPartidos('Copa America', function(data) {
    if(!data.juegaMessi) mostrarError(new Error('No se transmite'));
  }, mostrarError);
}

Este approach, que es el más utilizado hasta la actualidad, tiene algunos problemas. En principio el flujo de la función tiene ramas donde el órden de ejecución no se condice con el órden del código dentro de la misma. El programa va a ejecutarse en este órden:

  • Request a /api/partidos.json
  • Termina la ejecución de copaAmerica y el programa sigue ejecutando otras tareas y escuchando eventos.
  • Retorna el llamado a la función:
    1. Si el request fue exitoso se ejecuta la primera función pasada por parámetro y si todo está bien, luego se llama a prepararLaTele.
    2. Si falló el request se ejecuta la segunda función de error.

Por otra parte la función copaAmerica no puede retornar el valor de la data pedida al servidor ya que esta termina su ejecución antes de que el request pueda ser completado.

Además, si otro request depende de la respuesta del primero, tengo que llamarlo desde la función de callback, facilitando lo que se conoce como «callback hell».

Callback hell

Zona de promesas

Una respuesta a esta falta de estructura es la introducción de un objeto llamado «Promise» o (Promesa), con su implementación estrella Promises/A+. La idea de las promesas es mitigar algunos de los problemas de usar callbacks para operaciones async:

  1. Retorna un objeto que «promete» ser completado
  2. Hace uso de excepciones

Volviendo a nuestro ejemplo de la Copa América, podemos reescribirlo utilizando promesas. (Los métodos de AJAX de jQuery retornan promesas desde la versión 1.5).

function pedirPartidos(competencia) {
  return $.getJSON("/api/partidos.json", { "competencia": competencia });
}

function copaAmerica() {
  var promesaPartidos = pedirPartidos('Copa America')
  .then(function(data) {
    if(!data.juegaMessi) throw new Error('No se transmite');
    prepararLaTele(data, 'HD'); // Salió bien
  })
  .catch(function(error) {
    mostrarError(error);
  });

  return promesaPartidos;
}

En este caso vemos como las funciones ahora sí retornan valores, pudiendo pasar promesas entre secciones del programa. La función que llame a copaAmerica va a poder hacer uso de promesaPartidos y a la vez no es necesario el pasaje de callbacks entre funciones.

Otro punto de interés tiene que ver con la capacidad de las promesas de emitir errores y handlearlos. Esta es una mejora circunstancial respecto del uso de callbacks donde no es posible.

Promises/A+ fue incluido en el estándar de ES2015 (ES6), es decir que ya está en la última versión terminada del lenguaje y ya se puede usar en algunos navegadores sin la necesidad de librerías o polyfills.

Async/Await al rescate

Si bien las promesas ayudan en gran parte al problema del flujo de código asíncrono, no resuelven completamente, el mindset sigue siendo parecido al del uso de callbacks. Para mejorar la experiencia a la hora de desarrollar se va a incluir en el próximo estándar llamado ES2016, la estructura llamada «Async Functions». Gracias al uso de Promises y la introducción de generadores en el lenguaje, sumado a 2 nuevas keywords «async» y «await» podemos repensar como escribimos código async.

Nuevamente vamos a reescribir el ejemplo, ahora utilizando una función async:

function pedirPartidos(competencia) {
  return $.getJSON("/api/partidos.json", { "competencia": competencia });
}

async function copaAmerica() {
  try {
    var partidos = await pedirPartidos('Copa America');
    if(partidos.juegaMessi) throw new Error('No se transmite');
     prepararLaTele(data, 'HD'); // Salió bien
     return partidos;
  } catch(e) {
    mostrarError();
  }
}

Ahora sí, nuestro código tiene estructura de código síncrono y el valor de retorno de la función es simplemente la información que necesitamos y que puede ser recogida por otra función async. La estructura try/catch (de ES3) nos permite handlear errores sin necesidad de uso de estructuras ad-hoc.

¿Qué pasa si ahora quiero pedirle al servidor información de 2 competencias en paralelo y devolver cuando estén completas?

async function copaAmerica() {
  var partidos = await*([pedirPartidos('Copa América'), messi('Argentino B')]);
  console.log(partidos); // [{//data de copa america}, {//data de argentino b}]
}

Esta nueva estructura definitivamente nos permite pensar el código async como sync, simplemente utilizando funciones de tipo async.

Tengo que esperar hasta 2050?

No es necesario esperar a que los navegadores adopten esta feature para poder usarla hoy. De hecho si entran ahora al Panel de Control de Mango, van a ejecutarse requests utilizando esta técnica.

Gracias a transpilers como Babel pueden usar async/await en navegadores como IE8. En el caso de Babel, es necesario habilitar la transformación es7.asyncFunctions.

Otro caso de uso

Un caso de uso muy interesante es el uso de funciones async para el acceso a bases de datos en node.js.

El siguiente es un ejemplo, pidiendo prestadas las arrow functions de ES6 y utilizando express y el ORM Sequelize (que hace uso de promesas):

// antes
app.get('/partidos', (req, res) => {
  Partido.findAll({})
  .then(function(partidos){
    res.json(partidos);
  });
});

// usando async functions
app.get('/partidos', async (req, res) => res.json(await Partido.findAll({}) ));

Podemos concluir que esta nueva construcción nos permite creer que estamos ejecutando código de modo síncrono, pero como toda magia es solo una ilusión.

Cualquier duda/comentario o experiencias que quieran compartir, me pueden contactar vía Twitter a @impronunciable.

A very important part of the build phase of a single-page app it’s the bundling process, which consists in combining the app’s files and modules needed in order to run it.

In Mango’s Dashboard we used Browserify to structure the front-end.

Browserify provides lots of advantages and useful features as we outlined in this post.

The simplest bundling process using Browserify has an output of a single JavaScript file that contains the whole application logic.

The problem with single bundles

As new features are added to the app, the size of the bundle also increases and create some inconvenients:


– Download time increases.
– Time to interpret the code also increases.

Our Dashboard -like most single-page apps- needs to download, parse and interpret code before rendering the views.
This creates longer waiting periods before being able to use the app, directly affecting the user experience.

The first problem -although real- it does’t manifest in a linear fashion as the output code increases. As TJ VanToll points out, gzip compression is very powerful and works better with larger files (the bigger the file, the more repetitions it needs to find).

The bigger problem relies with the parsing and interpreting timeframe of the initial code.

Filament Group made an interesting analysis about the penalisation of using certain popular MVC frameworks that provides a clear vision of the problem.

factor-bundle to the rescue

factor-bundle works as a Browserify plugin that receives multiple files as entry points (in our case are mapped to the sections of our app), analyses their content and generates two things: an output file for each input file, and also an extra file that contains the related dependencies between them.

Thanks to this ‘factorization’ of dependencies, when users load the app, only downloads the JavaScript associated to the section that they’re visiting and the related dependencies. The remainder sections will only download in an asynchronic way and without repeating the dependencies downloaded while the users navigate the Dashboard.

A small optimization we make is for each output file creating another file with the shared dependencies. This avoids downloading an extra script (-common-.js).

The output after the process looks like this:

.
|-- config-0.3.6.js
|-- config-common-0.3.6.js
|-- login-0.3.6.js
|-- login-common-0.3.6.js
|-- store-single-0.3.6.js
|-- store-single-common-0.3.6.js
|-- stores-0.3.6.js
|-- stores-common-0.3.6.js
|-- user-0.3.6.js
`-- user-common-0.3.6.js

There are optimisations to delay downloading not used modules, i.e. detecting mouse proximity to links to different sections or similar strategies. We decided to talk about these optimisations in future posts.

Conclusions

Separating bundles resulted in an optimization of waiting times of around 30% when users logged in to our Dashboard; thanks to the decreased size of the initial script and also the fewer amount of code to interpret. Also, adding new sections doesn’t impact the performance of the app’s initial load.

There are a lot of techniques to enhance the initial load of single-page apps. One we’re investigating right now is the shared rendering between client and server. A very interesting advantage of bundle factorization is that it’s almost exclusively a change in the compilation process of the app.
Business logic and rules remained intact.

Any doubt, comment or experiences you want to share, please contact me via Twitter: @dzajdband.

Durante el pasaje a producción de una single-page application (SPA) es muy importante el proceso de bundling, que consiste en combinar los archivos y módulos de la aplicación para su puesta en marcha.

En el panel de Mango utilizamos Browserify para estructurar el Front-end.
Browserify nos proporciona muchas ventajas y utilidades como contamos en este post.
El proceso de bundling más simple utilizando browserify resulta en un único archivo de JavaScript que contiene toda la lógica de la aplicación.

El problema del bundle único

A medida que se agregan nuevas features el bundle resultante incrementa en peso acarreando dos inconvenientes:


– El tiempo de descarga es mayor.
– El tiempo de interpretado del código es mayor.

El panel, como la mayoría de las SPA, necesita descargar, parsear e interpretar el código antes de empezar a dibujar. Esto se traduce en tiempos más largos de espera antes de poder utilizar la aplicación, afectando a la experiencia del usuario.

El primer problema, si bien es real, no se manifiesta de modo lineal a medida que crece la cantidad de código resultante. Como apunta TJ VanToll la compresión gzip es muy fuerte y funciona mejor para archivos más grandes (mientras más grande el archivo hay más repeticiones por encontrar).

El inconveniente en materia de performance más grande está relacionado con el tiempo de parsing e interpretado del código inicial.

Filament group hizo un análisis interesante sobre la penalización en la utilización de ciertos frameworks MVC populares en materia de carga inicial que da un buen panorama del problema.

factor-bundle al rescate

factor-bundle funciona como un plugin de browserify que recibe múltiples archivos como puntos de entrada (en nuestro caso están mapeados a las secciones de nuestra aplicación), analiza sus contenidos y genera por un lado un archivo de salida por cada archivo de entrada sumado a un archivo extra que contiene las dependencias en común entre todos ellos.

Gracias a esta ‘factorización’ de dependencias, cuando un usuario entra a la app solo descarga el JavaScript asociado a la sección que visita, sumado a las dependencias compartidas. El resto de las secciones se descargan de modo asincrónico y sin repetir la descarga de dependencias, mientras el usuario navega por el panel.

Una pequeña optimización que hacemos es producir, por cada archivo de salida, otro que surge de concatenarle a este el archivo de dependencias comunes. Esto previene la descarga de un script adicional (-common-.js).

El output luego del procesado se visualiza de este modo:

.
|-- config-0.3.6.js
|-- config-common-0.3.6.js
|-- login-0.3.6.js
|-- login-common-0.3.6.js
|-- store-single-0.3.6.js
|-- store-single-common-0.3.6.js
|-- stores-0.3.6.js
|-- stores-common-0.3.6.js
|-- user-0.3.6.js
`-- user-common-0.3.6.js

Existen optimizaciones para aplazar la carga de módulos no usados. Por ejemplo detectar el acercamiento del mouse a links de otras secciones. Decidimos dejar esas optimizaciones para más adelante (y para otros posts).

Conclusiones

La técnica de separación de bundles resultó en una mejora de aproximadamente un 30% de ahorro en el tiempo de espera promedio de los usuarios a la hora de ingresar a nuestro Panel de administración, dado por la baja en el peso del script inicial como la menor cantidad de código a interpetar. Además, el agregado de nuevas secciones a la aplicación no resulta en una reducción de la performance en la carga inicial del sistema.

Hay otras técnicas para mejorar la carga inicial de SPAs. Una de las que estamos investigando para integrar es la implementación de rendering compartido entre el cliente y el servidor. Una ventaja interesante de la factorización de bundles es que es un cambio casi exclusivamente del proceso de compilación de la aplicación. La lógica de negocio permaneció intacta.

Cualquier duda/comentario o experiencias que quieran compartir, me pueden contactar vía Twitter a @dzajdband.