Single-page apps: Razones y herramientas

Hola,

Para un proyecto he estado trabajando con el concepto de las single-page apps y he estado investigando alternativas para hacerlas. Hoy os comparto el análisis que hice, a mi manera como siempre, empezando por el contexto de estas cosas modernas y terminando por las opciones que yo tomé para ese caso concreto pero siempre enseñándoos de dónde viene la movida y basándonos en el lado teórico, que es lo que nos interesa.

Mi caso concreto era una aplicación relativamente sencilla, que me encantaría enseñaros pero no puedo (acuerdos de confidencialidad y esas cosas, cuando seáis mayores los tendréis). Como he dicho antes, mi situación me hizo tomar decisiones en una dirección pero la vuestra puede ser otra y puede que necesitéis elegir otras alternativas

Voy a hacerlo básico y no voy a profundizar en los detalles. Empezará muy tonto y subirá de level, no lo dejéis a medias porque os parezca simple que habrá movida para rato.


Contexto: Single page apps

Normalmente solemos tener un escenario con esta pinta cuando se trata de aplicaciones web: Un servidor con toda la lógica, lo que luego llamaremos backend, y una parte que se encarga de visualizar lo que sale del mismo y permitir cambios, lo que se llamará frontend.

El backend a veces expone métodos, a través de una API RESTful normalmente (que está de moda), consultables directamente desde fuera sin pasar por el frontend.

El frontend puede tener muchas formas aunque para el caso de hoy trataremos el caso de una página web. Otro ejemplo sería una aplicación móvil, por ejemplo.

Normalmente las páginas web de este tipo, tienen un lado de servidor y un lado de cliente (lo conté aquí). El lado de servidor entrega los ficheros estáticos y crea las diferentes páginas con su server side templating en su Flask, PHP (yuyu) o lo que sea accediendo a la parte backend, ya sea por métodos externos o porque son el mismo servidor y la línea entre frontend y backend es muy delgada.

En este documento se plantea la manera de eliminar completamente la parte de servidor de ese frontend para que haya un backend que exponga toda su funcionalidad de forma estándar y un frontend que le diga lo que tiene que hacer.

¿Pero qué ganamos con esto, tío loco? Ganamos que el servidor podría ser accedido por cualquier software donde se incluyen aplicaciones de escritorio, terminales, demonios, un chip que se ha inventado mi cuñao o una aplicación móvil. Además, quitamos la parte de visualización del servidor, entregando siempre datos parseables por cualquiera.

Con esto hemos, colateralmente, convertido nuestro frontend clásico en un programa que se le entrega al navegador y corre en él y ya no necesita de un trabajo especial para ser entregado. Ahora el frontend es una single-page app entregada de forma estática.

Implicaciones en el backend

Poca cosa. Entregar JSON (API REST) en lugar de HTML construido es incluso más sencillo.

Implicaciones en el frontend

  • Requiere de más carga en el browser para compensar lo que le hemos quitado de nuestro servidor. No es muy grave, ahora los dispositivos están acostumbrados.
  • Se convierte en un programa en sí mismo. Necesita:
    • Más librerías para hacer todas las labores nuevas.
    • Manejar dependencias para que no se desmadren las librerías.
    • Una arquitectura. Una forma ordenada para atacar la API.
    • Facilidades para gestionar un código más complejo.

Elecciones tomadas

En esta sección se mencionará qué se ha seleccionado en el caso del que os hablaba con el fin de resolver esas implicaciones que han aparecido pero más adelante se pretende introducir la teoría de cada una de las elecciones para que vosotros os planteéis otras y sepáis adaptaros a vuestro contexto con mejores armas. No os toméis esto ni siquiera como una recomendación. Sólo es un caso concreto.

  • Más librerías: Hay miles de maneras de gestionar las librerías: Descargarlas a mano, usar Bower, etc. Se ha optado por usar NPM (Node Package Manager) que a pesar de estar orientado para el server side nos valdrá porque haremos un truco encima.
  • Gestión de dependencias: Existen un par de maneras principales de gestionar dependencias en el lado de cliente. Se eligió Browserify porque unido a NPM funciona muy bien. Tiene sus desventajas, pero unido al resto de puntos (luego lo veréis) se convierten en facilidades.
  • Una arquitectura: Si no nos organizamos va a ser imposible pedir cosas al backend y ponerlas en la pantalla de forma sencilla. Para esto se ha seleccionado una de las arquitecturas más simples: MV (Model-View), porque diferencia la parte de los datos, el Model, de la visualización, la View, de forma sencilla sin elementos intermedios. Luego hablamos de la familia MV* y de otras cosas. Se ha usado una librería que facilita mucho este trabajo, se llama Backbone.js.
  • Facilidades de gestión: Este código empieza a tener entidad y programarlo todo en un único fichero de JavaScript kilométrico ya no es tan factible como antes. Necesitamos estructurar y separar. Gracias a Browserify y Backbone tenemos la perfecta oportunidad de usar CoffeeScript para programar. Este lenguaje de programación permite usar Programación Orientada a Objetos de forma sencilla, tiene una sintaxis muy simple y tiene un millón de trucos que nos vendrán bien y encajan perfectamente con el proyecto.

Teoría

Ahora cuento un poco de qué van todas las cosas que he mencionado arriba pero profundizando un poco en el sentido teórico de las mismas. No fear.

Dependencias y librerías: Bundles VS AMD

La mejor opción de las dos que se platean aquí como solución técnica es el AMD. AMD o, Asynchronous Module Definition, es una forma de definir módulos de código Javascript que se cargan en el cliente a medida que se van necesitando. De esta forma, no se descarga todo el código de golpe y si el hilo de ejecución no llega a utilizar un módulo pues no se descarga y punto. Ahorramos descargas y tenemos mejor rendimiento. Hay una librería muy famosa que implementa esto: Require.js. Aunque hay más. En su documentación podéis leer más al respecto e incluso una comparativa de por qué mola más que la otra alternativa.

La otra alternativa, la que en el título denomino Bundles, es CommonJS con magia extra aportada por Browserify. CommonJS es un proyecto para llevar JavaScript a otros entornos que no sean el Browser, por ejemplo, Node.js es una de sus implementaciones. CommonJS tiene una manera de gestionar dependencias basada en las sentencias require y module.exports. En el caso de Node.js, en cuanto el intérprete encuentra una sentencia require busca el fichero al que se hace referencia y lo importa. Esta búsqueda es lo que vamos a explotar.

Todos conocéis NPM (Node Package Manager), una herramienta para gestionar librerías y programas de Node.js y las dependencias de los mismos. Node.js, a la hora de buscar los ficheros requeridos busca la instalación hecha por NPM para encontrarlos (carpeta node_modules), con lo que, si tenemos todo instalado con NPM, es extremadamente sencillo gestionar las dependencias y cargar los módulos en nuestros programa, así como diseccionarlo en diferentes ficheros.

¿Pero no hemos dicho que esto es para entornos que no sean el Browser?

Sí, eso hemos dicho. Ahí es donde entra Browserify.

Browserify es una herramienta para convertir un bloque de código con pinta de Node.js en código ejecutable en el Browser. Browser-ify. Browser-izar.

Browserify resuelve todos nuestros require y module.exports de Node.js y browseriza nuestro código fuente creando un sólo fichero (el Bundle) con todo nuestro proyecto unido debidamente (con sus closures de protección y todo). Así sólo necesitamos añadir un tag script en nuestra web con el fichero generado y ya tendríamos todo bien ordenado como he dicho antes, usando NPM y todo su poder de gestión de dependencias por debajo.

En comparativa con los AMDs vemos varias cosas:

  • El Bundle es todo nuestro código fuente, así que la descarga inicial es mayor que el AMD, que lo descargaba todo por partes cuando lo necesitaba.
  • Gestionar las dependencias con NPM es más fácil que rascarse la oreja. Tenemos los package.json y todas esas cosas y, además, podemos crear un paquete con nuestra web como resultado y facilitar también los deploys.
  • El AMD mola más pero tiene una sintaxis loca. No he hablado de esto antes pero requiere que aprendamos otra sintaxis extra, que no es muy difícil pero hay que pensar que nuestros módulos se cargan de forma asíncrona y otros temas. No es copiar la sintaxis de Node.js que ya sabíamos por otras cosas.
  • Con Browserify podemos compartir las mismas dependencias en el Browser y en el Server si lo tuviéramos.
  • Y la más importante de todas: Browserify, como habéis entendido sin que lo haya puesto de forma explícita, requiere de un preprocesamiento del código antes de que sea funcional. Antes de ver nuestros cambios tenemos que ejecutar Browserify para que nos construya nuestro bundle. Esto genera problemas a la hora de debuggear porque la línea de tu fichero no es la misma que la del Bundle, por ejemplo. Pero hay workarounds y tampoco es tan grave en aplicaciones pequeñas o de tamaño medio, donde podemos tener un mapa mental completo de lo que hacen.

No lo hemos seleccionado para este proyecto pero el AMD mola mucho también. No lo ignoréis en vuestras aplicaciones futuras.

Arquitectura: familia MV* y Backbone.js

Hemos dicho antes que necesitábamos una manera ordenada de coger los datos, editarlos y pintarlos. Esto se resolvió hace mil de tiempo con la movida del MVC (Model View Controller), que es una arquitectura de tres elementos interconectados y parlanchines. El Model representa los datos de la aplicación, el View la representación visual de los mismos y el Controller una entidad capaz de editar los datos. Dicho de forma sencilla: el Usuario usa el Controller, que manipula el Modelo, que actualiza la View, que el Usuario ve.

Una arquitectura sencilla para crear UIs.

Pues partiendo de esto, es fácil pensar que podemos fusionar sin mucha dificultad el View con el Controller porque, al fin y al cabo, ambas se encargan de facilitar al usuario visión y acceso al Model. Pues con eso generamos un MV (Model View), como Backbone (luego vamos a eso).

También se nos podría ocurrir, ojo, introducir un elemento intermedio en el MV para tener una interfaz entre el Model y el View que se encargue de convertir datos de backend (el Model) en datos de frontend (la View). Esto se le llama MVVM (Model View ViewModel).

¿Y si…? Sí hijo, sí, tranquilo, puedes cambiar la arquitectura mil veces y, si te basas en MV para hacer los cambios, generarás una arquitectura de UI de la familia MV*.

Pues esa es la historia. Mil diferentes pero el mismo fondo.

Backbone.js

Backbone es una librería (no framework, si quieres un framework usa Marionette.js) de MV bastante sencilla. Dispone de varios tipos de elementos que facilitan esto y lo automatizan bastante. La parte positiva de Backbone es que no se las da de listo y supone muy pocas cosas, sólo considera que tienes una API RESTful detrás, nada más (y encima puede pisarse ese comportamiento con otro).

En Backbone existen los siguientes elementos: Model (¡sorpresa!), Collection, View ( 🙂 ), Router, History y Sync. Algunos sirven para el MV (adivinad cuales) y otros aportan funcionalidades que nos vendrán bien. Todos vienen en forma de clase para que los extendamos con nuestras funcionalidades concretas. Cuento un poco.

  • Model y Collection: Ambos representan el Model de la aplicación, pero la Collection es un conjunto de entidades únicas de tipo Model. La Collection se comporta como un array de modelos (con unas cuantas magias por encima). Los modelos y colecciones de Backbone son inteligentes saben pedir por una API REST sus contenidos y actualizarlos usando unos métodos sencillos, toda la lógica de la API está implementada ya. Que bien ¿no?
  • View: Representa la vista de un modelo o una colección. Se le dice cómo tiene que pintarse en la pantalla y cómo tiene que editar el modelo o colección subyacente y se encarga de ello. Con el sistema de eventos de Backbone se puede hacer fácilmente que cada vez que el modelo o colección subyacente cambie se actualice la vista al momento. Tiene varias cosas extra encima para facilitar el trabajo pero eso lo miráis.
  • Router y History: Dos componentes para gestionar las URLs y el Historial del navegador. Esto es importante porque como tenemos una single-page-app la página nunca cambia y, si no hacemos nada, siempre estamos en el índice. Esta parejita actualiza el historial y crea URLs para que podamos compartir enlaces y nos lleven a donde esperamos y podamos usar el botón de atrás del navegador sin problemas.
  • Sync: Esto es lo que los Models y Collections usan por debajo para comunicarse con su fuente. Como se ha dicho, se entiende que la fuente es una API REST pero no tiene por qué serlo. Podemos pisar éste objeto para que acceda a otra cosa, como el LocalStorage u otros, eso lo dejo a vuestras ingeniosas cabezas.

Resumiendo:

Creamos nuestros modelos y colecciones referenciando al modelo de datos del servidor de backend, los asociamos a vistas independientes y conectamos todo para que mágicamente funcione.

Y funciona.

¡Odio Javascript! CoffeeScript y la transpilación

¿Tú también?

Pensaba que estaba solo.

CoffeeScript es un lenguaje de programación que se compila contra JavaScript, es decir, el resultado de su compilación es un programa en JavaScript. Por qué hacer un lenguaje con esta pinta es una pregunta que tiene una respuesta muy simple: Porque JavaScript es una mierda.

Básicamente CoffeeScript añade Syntactic Sugar sobre JavaScript. Elimina las llaves, casi todos los paréntesis, y mucho código inútil, facilita el uso de clases (no prototype ni mierdas) y aporta array comprehensions, oneliners chulos y comprobaciones molonas (como la existencial y el == con cerebro).

El sistema de clases viene muy bien para Backbone, son dos proyectos que trabajan muy bien juntos (son del mismo autor), del que ya hablé in the past

No deja de ser un JavaScript con toques de Python y Ruby. Tenéis otros lenguajes de programación de este pelo, como TypeScript, LiveScript, ELM o ClojureScript, que tienen otro tipo de ideologías más perversas. Donald Trump.

Como habéis sobreentendido, nuestro código lo escribiríamos en CoffeeScript y tendríamos que convertirlo a JavaScript antes de probarlo y eso es un problemilla. En nuestro caso ya no lo es tanto porque, como tenemos Browserify y también requiere cierto procesamiento, ya no sentimos que perdemos nada. Además Browserify tiene la opción de instalar un módulo de CoffeeScript para hacer las dos conversiones a la vez así que no problemo. Se llama Coffeeify, por si queréis investigar.

Si nuestro código fuese para el lado del servidor también podríamos usar CoffeeScript y probarlo directamente con el interprete de CoffeeScript, pero para una aplicación final deberíamos convertirlo para evitar problemas de rendimiento.

Pues ya está, el código está bonito y es fácil de programar, no hay que aprender cosas nuevas para hacer CoffeeScript, sólo un poco de sintaxis sencilla.

EXTRA: CoffeeScript viene con un par de detalles muy locos. Tiene una cosa que se llama Literate CoffeeScript y un tipo de ficheros tipo Makefile llamados Cakefile que automatizan tareas. Ahí lo dejo.


Bueno, pues eso es todo. Iba a poner miles de links pero no lo he hecho. He puesto nombres y palabras clave y que cada uno busque.

Preguntas, comentarios y otras cosas, ya sabéis.

Un abrazo.

PD: Esta vez sí que me he pasado en la longitud pero ya que me he preparado el contenido pues lo cuento bien. 😀

Anuncios

Un pensamiento en “Single-page apps: Razones y herramientas

  1. Single-page apps: Razones y herramientas | PlanetaLibre

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s