Trabajo de Fin de Grado - Desarrollo de un gestor de contenidos
Resumen
A la hora de gestionar el contenido de una web, los desarrolladores suelen aprovechar herramientas que facilitan esta tarea, como WordPress. Ha sido la solución preferida y dominante del mercado, tanto para empresas como para particulares. Este proyecto presenta una solución alternativa a WordPress. Aunque tiene una funcionalidad a menor escala, propone una estructura distinta y ofrece un nuevo método para mostrar el contenido.
Este gestor de contenidos se ha diseñado y desarrollado desde cero, usando Electron como plataforma para crear aplicaciones de escritorio, NodeJS como intérprete de JavaScript en el Back-End y, por último, HTML, CSS y JavaScript para la interfaz. Con una estructura modular, esta aplicación usa un panel común para los distintos módulos. Para dar más flexibilidad, cada uno de estos módulos se puede modificar de forma independiente, sin afectar al resto.
El objetivo de esta aplicación es generar páginas estáticas sin necesidad de ejecución, obteniendo mejor rendimiento y una carga de contenido más rápida respecto a otros sistemas. Además, estas páginas cumplen los estándares W3C y el contenido HTML se ha optimizado, obteniendo mejores resultados en los motores de búsqueda.
Como conclusión, he comparado el tiempo de respuesta de las páginas estáticas generadas y su equivalente en WordPress. Además, he ejecutado pruebas con Lighthouse para obtener una valoración global de las páginas y poder validar los resultados de rendimiento, accesibilidad, buenas prácticas y SEO.
Introducción
Motivación
A lo largo de los distintos desarrollos de aplicaciones y webs que he realizado, me he encontrado con un problema recurrente al gestionar el contenido, que surge de la necesidad de usar herramientas externas, cuando este varía con frecuencia o hace falta generar contenido nuevo a diario, como ocurre en un blog, por ejemplo. Un blog es hoy en día una sección casi imprescindible en cualquier proyecto, y la primera vez que me enfrenté al problema de gestionarlo fue en el desarrollo de la web para la empresa Alien Ventures, donde optaron por contenido estático generado a mano por un administrador. Esta opción no es la mejor porque implica mucho esfuerzo de mantenimiento, pero es la que requiere menos desarrollo al principio, y por eso elegí hacerlo así. La segunda, y la que me hizo pensar en una alternativa mejor, fue mi web personal, donde la necesidad de incluir un blog y poder publicar de forma rápida y eficiente era imprescindible para mí.
Por mi experiencia, hay tres opciones para gestionar el contenido de un blog: cada vez que hay que publicar contenido nuevo, generar a mano el HTML (HyperText Markup Language) correspondiente, usar una herramienta existente que se encargue de esta tarea, o desarrollar tu propio sistema de publicación de contenidos.
Según la frecuencia con la que haya que actualizar el contenido de una página web o requiera añadir o eliminar contenido, la opción de generar o editar estos ficheros HTML puede ser viable o no, y también hay que tener en cuenta que entrar a editar código no es algo accesible para cualquiera.
Usar un sistema de gestión de contenidos, o CMS (Content Management System) como lo llamaré a partir de ahora, tiene una serie de ventajas, pero también un buen número de desventajas que veré a continuación y que han motivado este trabajo.
Desarrollar tu propia herramienta de publicación es una tarea laboriosa y compleja, que no está al alcance de un usuario básico o medio que quiera gestionar su blog personal o incluso el de una empresa.
Objetivos
Antes de empezar a desarrollar la aplicación, tenía una serie de requisitos esenciales. La herramienta se basaría en tecnología web, esto es porque es un área en la que no había profundizado mucho en el grado y quería ampliar mis conocimientos, ya que tiene mucha demanda en el mercado laboral, algo que he podido comprobar buscando trabajo.
La herramienta debe estar diseñada para que una persona sin conocimientos de programación web sea capaz de escribir un artículo y publicarlo. El contenido generado por el gestor debe ser contenido estático y optimizado para SEO (Search Engine Optimization), un concepto que veré más adelante. La aplicación, aunque basada en tecnología web, no significa necesariamente que se vaya a acceder a ella desde un navegador; de hecho, la aplicación que se va a desarrollar en este TFG será de escritorio y multiplataforma, de modo que los usuarios de Windows, Mac y Linux podrán instalarla en sus ordenadores y empezar a trabajar.
Este trabajo de fin de grado tiene como objetivo presentar y explicar la aplicación resultante, cumpliendo los objetivos marcados, así como dar una visión general de las soluciones actualmente disponibles y aportar más información sobre ellas.
Estado del arte
Este trabajo se basa por completo en el desarrollo automatizado, gracias a mi aplicación, de software web. No solo el producto final que genera la aplicación es web, sino que la propia aplicación tiene su interfaz generada como si fuera una web con un Back-End en JavaScript interpretado por NodeJS, como veré más adelante. Para un lector no familiarizado con este tipo de tecnología, a continuación se explican los distintos lenguajes y herramientas que se van a usar a lo largo del TFG.
Composición de una página web
Por mi experiencia como desarrollador web, una página, en su mínima expresión, es un documento HTML que define los elementos que el navegador tiene que representar en pantalla. Un documento HTML se compone principalmente de dos bloques: una cabecera donde se especifica información para los navegadores y los motores de búsqueda, como Google, entre otra información que también puede aparecer en esta cabecera. El otro bloque principal es el cuerpo de la página, donde mediante distintas etiquetas defino los elementos; estos elementos pueden ser de distintos tipos y es importante mantener una estructura, para que mi página esté bien diseñada y se interprete fácilmente.
A este documento se le pueden añadir otros lenguajes que aportan más funcionalidades y me permiten personalizar y dotar de funcionalidad a mi página. El lenguaje CSS (Cascading Style Sheets) me da la posibilidad de especificar el estilo concreto con el que quiero representar un elemento del documento HTML, por ejemplo, que un texto determinado tenga un tamaño de doce píxeles o que un bloque tenga un fondo rojo.
JavaScript es un lenguaje de programación que se puede ejecutar en el navegador, y da dinamismo a las páginas web, permitiéndome modificar elementos del documento HTML, lanzar eventos en respuesta a interacciones del usuario con la interfaz (hacer clic en un botón, hacer scroll...) o incluso hacer peticiones adicionales desde el navegador al servidor.
SEO
La optimización de páginas web para motores de búsqueda es hoy en día uno de los objetivos más perseguidos por las empresas al desarrollar una web. Aprendí esto trabajando en Arsys (proveedor de infraestructura cloud), donde posicionar una de tus páginas en los primeros resultados para una búsqueda determinada se traduce en un aumento de las ventas y, por tanto, en un aumento de los beneficios.
En distintas formaciones impartidas por agencias especializadas en SEO, aprendí que obtener una buena puntuación en los distintos motores de búsqueda existentes depende de muchos factores y de los algoritmos usados para puntuar.
Tu página cambia cada cierto tiempo, así que posicionar tu web es un trabajo de actualización continua. Aunque estos criterios pueden variar con el tiempo, generar una página web con una estructura correcta —por ejemplo, que no haya un h2 (subtítulo) por encima de un h1 (título)— o la velocidad con la que carga tu web, son atributos de la página que siempre favorecerán una mejor puntuación y que se buscarán en las páginas generadas con mi aplicación.
Lighthouse
Para evaluar la calidad de una web, voy a usar la herramienta Lighthouse. Lighthouse es un software de código abierto desarrollado por la comunidad e impulsado por Google para auditar webs [1].
A partir de una URL (Uniform Resource Locator), Lighthouse lanza una serie de cargas sobre tu web y analiza los resultados obtenidos, simulando estas cargas tanto para una versión de escritorio como para una versión móvil. A partir de los resultados obtenidos, Lighthouse da una puntuación de cero a cien en cuatro categorías distintas: rendimiento, accesibilidad, buenas prácticas y SEO [2].
La puntuación obtenida en cada una de las categorías es una media ponderada del resultado de cada una de las métricas que la componen. La categoría que más me interesa para este trabajo es el rendimiento; dentro de ella encuentro como métricas el speed index de la página, el tiempo total que la web permanece bloqueada al cargar, el tiempo en el que el navegador hace una primera representación gráfica del contenido, entre otras [3].
El resumen de las auditorías realizadas sobre las distintas webs se mostrará como ilustración, mientras que el informe completo se adjuntará como anexo por si el lector quiere consultarlo en profundidad.
Generación manual de HTML
Un modelo de generación manual de contenido HTML embebido es un enfoque clásico, que con los años ha ido quedando atrás, aunque, por mi experiencia, todavía hay casos en los que se usa este modelo. El primer paso es construir una estructura básica que seguirán todas tus publicaciones en la página web. Una vez creada esta estructura y acompañada de estilos y sus correspondientes scripts, generar páginas nuevas similares, pero con contenido distinto, no supone un gran esfuerzo de desarrollo. Los pasos a seguir serían replicar el código en una página nueva y sustituir el contenido existente por el nuevo.
Aunque no supone un gran esfuerzo en términos de desarrollo nuevo, sí que supone una mayor pérdida de tiempo a la hora de mantenerlo e incorporar contenido nuevo. Al principio, esta opción sería la más rápida de implementar, pero la menos útil a largo plazo.
También hay que tener en cuenta que, siguiendo este modelo, la persona que genera el contenido debe tener conocimientos básicos de maquetación HTML, mientras que con las otras dos alternativas podría extender el desarrollo de esta tarea a cualquier persona. En este modelo aumenta el error humano; puede haber errores en las etiquetas HTML y esto puede generar problemas de visualización.
Basándome en mi experiencia previa, elegir este método como solución para implementar un blog quedó totalmente descartado, buscando una solución más efectiva y cómoda para mí.
Gestores de contenido
¿Qué es un CMS?
Un CMS no es más que un software diseñado para ser usado por cualquier usuario, sin necesidad de conocimientos técnicos más allá del uso del programa, para la creación, edición y publicación de contenido, generalmente páginas web.
Hay distintos tipos de CMS, según el propósito para el que se vaya a usar, por ejemplo, un blog personal, una página de empresa o una tienda de comercio electrónico, entre otros propósitos.
Estos CMS gestionan bases de datos a través de una interfaz, usada por la página web para mostrar el contenido con independencia de los estilos, de forma que la manera en que se muestra se puede cambiar sin necesidad de modificarlo. Están diseñados para que, una vez implementados, puedan usarlos los editores, dejando las tareas de mantenimiento y actualización a los desarrolladores [4].
Historia del CMS
Los primeros CMS de la historia se basaban en comandos sencillos en el servidor que, gracias a la aparición del DOM (Document Object Model), que es la representación de la estructura del documento y que me da una API para manipularlo [5], eran capaces de modificar el contenido HTML que finalmente se sirve al cliente a partir de una base de datos.
Estos primeros CMS fueron creados por empresas para su propio uso, adaptándolos a sus propias necesidades entre 1990 y 2000. Desde entonces, estos CMS se han ampliado para incluir muchas más funciones para las empresas, que han visto simplificadas sus tareas de gestión de contenidos. Al mismo tiempo aparecieron CMS de código abierto disponibles para todos los usuarios, como WordPress (https://wordpress.org/), del que hablaré más adelante, Joomla (https://www.joomla.org/), que no solo permite gestionar contenido sino también crear foros, encuestas y calendarios entre otras funcionalidades, Drupal (https://www.drupal.org/), desarrollado por la comunidad con funcionalidades muy parecidas a las de Joomla, PrestaShop (https://www.prestashop.com/es) o Magento (https://magento.com/), estos dos últimos gestores orientados al comercio electrónico [6]. Todos estos CMS están desarrollados en PHP (Hypertext Preprocessor) y dependen del uso de bases de datos, un enfoque muy distinto al del gestor de contenidos desarrollado en este TFG.
Tipos de CMS
Hoy en día encuentro distintos tipos de CMS según la utilidad que se les vaya a dar [6], estos son:
- Los constructores de páginas estáticas están pensados para generar ficheros que no se van a modificar muy a menudo, como una página web que muestra información sencilla sobre una empresa. Wix (https://es.wix.com/) es un ejemplo de este tipo de constructor web [6].
- Headless, son gestores de contenido que separan el contenido y la presentación que hago de él siguiendo el modelo vista-controlador, donde el contenido no depende en absoluto de los estilos aplicados [6].
- Híbridos, son una mezcla entre constructores de páginas web y CMS headless; me permiten crear y editar mis páginas, pero al mismo tiempo puedo enviar contenido a plataformas headless [6].
- Las plataformas de experiencia digital, o DXP, son paquetes que incluyen todas las herramientas que necesita una empresa para automatizar el contenido en páginas web, aplicaciones y dispositivos IoT (Internet of Things), así como el aprendizaje automático de los datos generados [7].
Si me fijo en el modelo de comercialización de estos CMS, puedo distinguir tres tipos [8]:
- CMS de código abierto, este tipo de software no tiene coste de entrada, es el más habitual entre los gestores usados por el público general y es el modelo de comercialización que va a seguir mi aplicación [8].
- CMS propietario, estos CMS pueden ser propietarios de una empresa para uso interno y no se comparten con el resto de usuarios, o se venden mediante un proceso de licencias a terceros [8]. Aquí encontraría, por ejemplo, los gestores usados por los periódicos para sus páginas web.
- CMS como SaaS (Software as a Service), consiste en CMS alojados en la nube y que normalmente se pagan mediante suscripción [8].
WordPress
WordPress es un CMS de código abierto lanzado en 2003 por su fundador Matt Mullenweg, pensado originalmente para la creación de blogs en páginas web. Es un fork del ya desaparecido CMS b2/cafeblog [9]. Con los años, WordPress se ha convertido en una herramienta capaz de crear cualquier tipo de página web gracias a su evolución y a la multitud de extensiones y componentes desarrollados en PHP por la comunidad o por uno mismo, lo que le permite no ser algo que te limite a lo que traiga de base, convirtiéndose así en el CMS más popular hoy en día. Se dice que tiene una cuota del 30% de todas las páginas web del mundo [10] [11].
Tecnología
WordPress ofrece una interfaz gráfica accesible desde el navegador y alojada en el mismo servidor web donde se ha instalado, permitiendo a desarrolladores o a usuarios finales gestionar o actualizar contenido. Este proceso consiste en ejecutar código PHP en el lado del servidor, para hacer consultas SQL (Structured Query Language) a una base de datos relacional, concretamente usando MySQL. Después, al mostrar el resultado final de la página, se ejecuta más código PHP que, tras consultar esta base de datos, genera un documento HTML para enviar al navegador. La forma en que el navegador muestra este contenido a un usuario final depende del tema, que consiste en un conjunto de código PHP que marca la distribución del contenido, hojas de estilo que definen cómo mostrarlo y JavaScript para la gestión de la interfaz [12].
En mi opinión, aunque se ha diseñado para que sea fácil de gestionar, con el paso del tiempo la realidad es que el proceso de implementar un sitio WordPress nuevo es una tarea tediosa que requiere conocimientos previos. Por eso suelen ser desarrolladores quienes realizan el proceso de implementación y mantenimiento, y el cliente final solo gestiona la publicación y modificación del contenido.
Auditoría web
Me parece interesante, a modo de prueba, ver cómo se comporta un WordPress recién instalado al auditarlo con Lighthouse. Para ello he instalado WordPress en mi servidor web y no he hecho ninguna configuración ni modificación del contenido que viene por defecto; está con el tema por defecto y sin ningún plugin añadido. Este es el resultado de la auditoría.
A la vista de los resultados obtenidos en el Anexo A, puedo decir que he conseguido una buena puntuación; sin embargo, llama la atención que, sin haber añadido absolutamente nada a WordPress, no se obtenga una puntuación de cien de serie en rendimiento. No obtener la puntuación máxima en SEO es algo que entiendo, ya que esta valoración depende de completar unos metadatos usados después por los motores de búsqueda que no se han completado.
Para añadir funcionalidad a mi web, WordPress ofrece la posibilidad de instalar plugins de terceros, desarrollados por la comunidad. Cada uno de ellos tiene un comportamiento distinto y puede llegar a ser complicado usar un plugin para casi cualquier cosa que quieras añadir. A medida que tu sitio WordPress se hace más grande, más complejo y tiene más contenido, es prácticamente imposible mantener alto su rendimiento, obteniendo valoraciones pésimas de Lighthouse. Por ejemplo, TechCrunch, uno de los portales de tecnología más famosos del mundo, está hecho con WordPress, y a continuación puedo ver el resultado de su auditoría en el Anexo B.
Es lógico pensar que, a medida que aumenta la complejidad de una web, es muy difícil mantener un buen rendimiento, y todavía más difícil mantenerlo con WordPress, porque llegas a perder el control de toda la ejecución y las peticiones que existen por detrás.
Seguridad
Si tengo en cuenta que el 30% de todas las páginas web del planeta están construidas con WordPress [11], tiene sentido que sea una de las plataformas más hackeadas del mundo [13]. Tener mucha popularidad no siempre es bueno, y si construyes tu web con WordPress, estás expuesto a cualquier problema de seguridad que tenga esta plataforma, ya que usa ejecución en el servidor para cualquier tarea.
El panel de control de WordPress es accesible por Internet para cualquiera, normalmente en la URL «dominio/wp-login.php», lo que expone el proceso de login a cualquier atacante.
¿Por qué no uso WordPress?
WordPress es una herramienta muy potente y útil precisamente para la tarea que necesitaba, implementar un blog en mi web personal; sin embargo, nunca fue una opción válida para mí. Uno de los mayores problemas que encontré fue que necesitaba una herramienta capaz de gestionar solamente el contenido con total independencia de dónde se fuera a insertar, ya que el resto de la web ya estaba construida con desarrollo propio, así que insertar un blog de WordPress requeriría la laboriosa tarea de crear un tema que replicara exactamente la interfaz y el comportamiento del resto de mi web.
Acceder al panel de gestión a través del navegador y tenerlo alojado en el servidor es una forma de exponer innecesariamente la plataforma a posibles intrusiones no deseadas o ataques maliciosos; por este motivo decidí hacer de mi aplicación una aplicación de escritorio, protegiéndola más del mundo exterior al tenerla localmente en mi ordenador.
WordPress añade demasiada funcionalidad y código que se vuelve innecesario cuando solo necesitas una tarea concreta, en mi caso una gestión sencilla de artículos, así que veo mejor otra herramienta enfocada específicamente a lo que necesito y que no añada más código del necesario. Crear estas páginas de forma dinámica es otro motivo por el que no estoy dispuesto a usar la herramienta, ya que el contenido de mi web debe ser estático y el objetivo es conseguir el mejor rendimiento posible, evitando consultas a bases de datos o ejecuciones en el lado del servidor.
Desarrollo de un CMS propio
Como las dos primeras alternativas mencionadas antes no eran de mi agrado, ya que no cumplían mis requisitos esenciales, decidí embarcarme en el proyecto de diseñar mi propia herramienta. Para entender las decisiones que tomé sobre su diseño, es importante tener claros algunos conceptos sobre las distintas arquitecturas posibles.
Petición de un documento HTML
Un usuario pide al navegador acceder a un recurso a través de una dirección URL; esta dirección no es más que un alias que los servidores DNS (Domain Name System) traducen a una dirección IP (Internet Protocol) perteneciente al servidor web donde está alojada la página. El navegador envía una petición HTTP/HTTPS (Hypertext Transfer Protocol Secure), con sus cabeceras y el método correspondiente, al servidor web. Usando las cabeceras, el servidor web responde con el recurso que el navegador interpretará después. Aparte de los elementos que componen el recurso, se pueden especificar recursos adicionales necesarios para la construcción final de lo que el usuario podrá ver; estos pueden ser hojas de estilo, código JavaScript, fotografías, vídeos... Es al encontrar estas referencias cuando el navegador pide al servidor web estos recursos adicionales, que se envían de vuelta.
Uno de estos recursos que pide el navegador puede ser un documento HTML, y este no tiene por qué estar completo en el momento previo a la petición, sino que se puede generar o modificar en el momento de la petición o a posteriori. Durante todo este proceso encuentro distintos pasos hasta obtener un resultado final.
Reglas del servidor
Ya hable de un servidor web Apache, Nginx o NodeJS, por ejemplo, todos tienen un conjunto de reglas al procesar una petición para saber cómo gestionar cada una de ellas. En el caso de Apache, se usa el fichero «.htaccess», en el que se pueden especificar redirecciones de una URL a otra, reescritura de la propia URL, métodos de compresión para los recursos...
Este paso está siempre presente en cualquier servidor web, y sea cual sea la forma en la que finalmente vaya a generar el HTML resultante, tengo que contar con este tiempo extra que añade pasar por este procesado de la petición.
Devolución de documentos HTML
Una vez procesada la petición según las reglas especificadas, el servidor web procede a devolver el documento pedido por el navegador. Llegados a este punto hay dos casos posibles.
Ejecución
El documento pedido no está listo para devolverse de entrada, ya que le falta todo o parte de su contenido y requiere un procesado previo. Este procesado se realiza mediante la ejecución de un código en el servidor, por lo que estaré usando recursos de CPU (Central Processing Unit) y RAM (Random Access Memory) de la máquina que, según su tamaño, tipo de ejecución y número de peticiones que reciba, puede ser un problema para el rendimiento de mi servidor web.
Se usan muchos lenguajes del lado del servidor, por ejemplo PHP, Java, Ruby, Python o incluso JavaScript. Este modelo es una gran ventaja a la hora de simplificar el contenido de una página web, ya que, de forma automatizada y según el recurso pedido por el usuario, el servidor web es capaz de generar el documento HTML correspondiente. Además, puedo ir un paso más allá y consultar datos, ya sea en una base de datos, en un fichero JSON (JavaScript Object Notation) o incluso en un fichero de texto plano, para generar el contenido HTML resultante.
Independientemente de la optimización del código, la potencia del servidor o los motores de bases de datos usados, esto supone añadir más pasos al devolver un documento HTML, lo que se traduce en más tiempo de espera para el usuario final. Además, si mi modelo se vuelve más complejo y consta de muchas dependencias externas, cada una de estas llamadas aumenta el tiempo de respuesta.
Ficheros estáticos
El fichero pedido está listo para que lo devuelva el servidor web, ya que tiene todo su contenido y no requiere procesado adicional para completarlo ni consultas a bases de datos. En este modelo evito pasos adicionales, consiguiendo un tiempo de respuesta más corto y liberando al servidor de consumo de recursos.
Por otro lado, pierdo la flexibilidad y el dinamismo que ofrece la ejecución en el lado del servidor, limitando ciertas funcionalidades.
Ejecución en el lado del cliente
Una vez que el navegador ha obtenido una respuesta, procede a interpretarla y renderizarla a partir de las etiquetas HTML, los estilos CSS y la ejecución del código JavaScript presente. Dentro de esta ejecución en el lado del cliente puedo encontrar otros lenguajes como C o C++ gracias a la aparición de WebAssembly, que no es más que un formato de código binario portable [14].
Estos scripts pueden gestionar eventos en la propia interfaz web, pero también hay otros scripts encargados de modificar el contenido de la propia página haciendo peticiones adicionales al servidor. Esto último es lo que hacen frameworks como Angular, React o Vue: no son más que librerías de JavaScript que dan herramientas a los desarrolladores para el desarrollo de interfaces y, sobre todo, de páginas SPA (Single Page Application). Este tipo de generación de páginas tiene una serie de desventajas, igual que la ejecución en el lado del servidor, ya que estoy añadiendo más pasos antes de poder mostrar el contenido final al usuario, y esto supone más tiempo de espera para el usuario.
Modelo de mi aplicación
Teniendo en cuenta que el objetivo de mi aplicación es gestionar el contenido de una página web, tiene sentido pensar que el modelo perfecto a seguir es la ejecución en el lado del servidor consultando bases de datos de donde obtener el contenido de cada uno de los artículos del blog, y quizá usar algún tipo de framework de JavaScript para la generación de la interfaz. Esta es la forma más usada y habitual de hacerlo; sin embargo, por mi experiencia como analista SEO, este modelo no es el óptimo por varios motivos.
Los motores de búsqueda como Google, hoy en día, recorren Internet consultando las páginas existentes e indexando su contenido en sus páginas de resultados. En concreto, Google renderiza tu página web dos veces: una, y la más importante de las dos, simulando estar en un dispositivo móvil, y una segunda vez, simulando estar en un escritorio. Durante el proceso de renderizado toma varias capturas de pantalla en distintos momentos del proceso de carga. Cuando tienes HTML generado desde cero en el cliente, te aseguras de que, cuando Google hace una captura de tu web para comprobar el contenido visible, este está presente. Esto lo consigo tanto con ficheros estáticos que después no requieren peticiones adicionales del cliente, como con páginas generadas en ejecución en el servidor. Sin embargo, cuando uso frameworks de JavaScript del lado del cliente, me arriesgo a que, cuando Google compruebe mi web, parte del contenido, o el más relevante, todavía no se esté mostrando y por tanto no se indexe en los resultados de búsqueda; esto ocurre porque Google no se queda mirando tu web indefinidamente, sino que espera unos segundos mientras toma las capturas y se va. Como uno de los objetivos de las páginas generadas con mi aplicación es favorecer la optimización en buscadores, uso HTML con todo el contenido embebido en él.
Otro objetivo que me marqué es intentar que el tiempo de respuesta al consultar la web sea lo más corto posible, y por otro lado, también quería que la aplicación y los ficheros de contenido que genera fueran independientes del servidor donde se vaya a alojar la web y de las tecnologías que pueda tener, de forma que no sea un requisito que el servidor tenga PHP o NodeJS, sino que se pueda usar en cualquier tipo de servidor web. Por eso se descartó la ejecución en el lado del servidor, y por tanto también se descartó el modelo de consulta a bases de datos, reduciendo así los recursos del servidor, las llamadas y los tiempos de espera.
Tecnología utilizada
NodeJS
Los lenguajes de scripting están pensados para usarse junto con otros lenguajes de programación o de marcado como HTML; estos lenguajes se interpretan y no requieren un proceso de compilación previo a la ejecución del código [15].
Uno de los lenguajes de scripting más populares del mundo es JavaScript; este lenguaje orientado a objetos se incorporó primero en el navegador Netscape y lo copió Microsoft en Internet Explorer con el nombre de JScript. Desde entonces, JavaScript no ha dejado de aumentar en popularidad y funcionalidad [16]. Junto con la aparición del DOM, los programadores web son capaces de manipular los elementos de la página y crear dinamismo en ciertos elementos o en las interacciones del usuario con la interfaz. JavaScript lo interpretan motores de JavaScript integrados en los navegadores; por ejemplo, Google Chrome usa un motor de código abierto escrito en C++ llamado V8.
Teniendo en cuenta la popularidad y utilidad de este lenguaje de scripting, tiene sentido la idea de poder usarlo fuera de los navegadores, ya sea en un ordenador personal o en un servidor. NodeJS es un intérprete de JavaScript, que usa el motor V8 de Google, basado en la ejecución de eventos asíncronos, diseñado principalmente para aplicaciones de red que puedan ser escalables [17].
Para gestionar las llamadas asíncronas, NodeJS incorpora la posibilidad de enviar funciones en las llamadas como callbacks, que se ejecutan una vez ha terminado el proceso. Una de las ventajas es que los procesos nunca se bloquean, y está diseñado para funcionar sin hilos, aunque existe esa opción si el desarrollador lo desea [18].
Personalmente, usar NodeJS para desarrollar aplicaciones de escritorio, así como para la ejecución en el servidor, es una gran ventaja porque unifica el desarrollo de Back-End y Front-End en un único lenguaje, JavaScript. Esto siempre supone un mantenimiento más sencillo y menos trabajo de especialización del desarrollador en distintos lenguajes. Gracias a las llamadas asíncronas, puedo conseguir fácilmente mejor rendimiento que en un lenguaje concurrente.
NodeJS, por mi experiencia, es una tecnología en auge cada vez más presente en todos los desarrollos informáticos y que no se ha estudiado en el grado, que es también el motivo por el que aprovecho mi trabajo de fin de grado para obtener información y habilidades con esta tecnología.
Electron
Electron es un framework para desarrollar aplicaciones de escritorio multiplataforma con JavaScript, propiedad de Github, que a su vez es propiedad de Microsoft. Ofrece una serie de APIs (Application Programming Interface) a nivel de sistema operativo, y usa Chromium para mostrar páginas web como interfaz.
Aplicaciones web
Desde la creación de Electron por Github y el desarrollo de sus dos aplicaciones originalmente más famosas, el editor de código Atom y el propio cliente de Github, es una tecnología que ha ido ganando cada vez más terreno. La adquisición de Github por parte de Microsoft hizo que aplicaciones populares como Skype y Teams se construyeran con esta tecnología. Hoy en día encuentro un gran número de aplicaciones famosas creadas con Electron, como Whatsapp, VSCode, Discord... [19].
La fuerte presencia que está teniendo Electron se debe principalmente a la facilidad con la que se pueden portar aplicaciones de la web al escritorio o viceversa, ya que el código usado es muy reutilizable. Así se puede mantener tanto la versión web como la versión de escritorio prácticamente como si fuera el mismo desarrollo, sin tener dos completamente independientes en paralelo. También es una gran ventaja poder exportar tus aplicaciones a MacOS, Linux y Windows automáticamente sin tener que hacer modificaciones en el código, teniendo incluso la posibilidad de generar la aplicación en modo portable.
En mi opinión, las aplicaciones de escritorio con tecnología web son el futuro y el camino que sigue el mercado; el buen rendimiento, la seguridad y la accesibilidad que ofrece este modelo de desarrollo suponen ventajas considerables para los desarrolladores.
Otras empresas como Google también apuestan fuerte por las aplicaciones web; sin embargo, optan por su propia tecnología llamada Progressive Web Apps, que viene a ser aproximadamente una instancia de Chrome con una página concreta y eliminando elementos de la interfaz del navegador.
Arquitectura
Electron consta de dos tipos distintos de procesos. El llamado proceso principal (main process) es el encargado de generar páginas web como interfaz gráfica; solo hay uno de este tipo por aplicación, y se especifica en el JSON «package.json» el fichero que contiene el código de este proceso. Cada vez que se genera una página web nueva, se crea un nuevo proceso llamado proceso de visualización (viewer process), aislado de otros procesos del mismo tipo, y puede haber tantos como requiera la aplicación. Una vez que el proceso principal destruye la instancia de la página, este proceso de visualización también se destruye. El proceso principal se ejecuta con NodeJS, por lo que puedo acceder a todas sus librerías y rutinas [20].
La principal diferencia al implementar páginas web como interfaz en este tipo de aplicaciones, respecto a la ejecución que podrían tener en un navegador, es que Electron le da a JavaScript la opción de comunicarse con rutinas del sistema operativo, algo impensable en páginas web convencionales, ya que esto conllevaría graves problemas de seguridad y fuga de recursos.
Para entender cómo funciona Electron, voy a hacer un símil con la arquitectura de una página web convencional. Electron consta de un Front-End y un Back-End. El Front-End lo gestiona el proceso de visualización y sería equivalente a un navegador, igual que cualquier página web consta de ficheros HTML, ficheros CSS y ficheros JavaScript que, aparte de su funcionalidad habitual, son capaces de comunicarse con el Back-End usando módulos ofrecidos por Electron. El Back-End lo gestiona el proceso principal y sería el equivalente a un servidor web; usa JavaScript interpretado por NodeJS y se encarga de responder a las peticiones de los procesos de visualización y de crearlos y destruirlos.
La comunicación entre Front-End y Back-End, es decir, entre el proceso principal y los distintos procesos de visualización, se lleva a cabo mediante los métodos «ipcRender» e «ipcMain». El módulo «ipcRender» ofrece una serie de métodos para enviar eventos síncronos (el proceso de visualización se detiene hasta obtener una respuesta) o asíncronos (el proceso de visualización continúa su ejecución) al proceso principal [21]. El módulo «ipcMain» ofrece métodos para gestionar los eventos recibidos en el proceso principal, con la posibilidad de devolver una respuesta [22].
Características
Como desde Electron puedo acceder a rutinas del sistema operativo, me da la opción de interactuar con características generales de los sistemas operativos, como atajos de teclado, notificaciones, arrastrar y soltar ficheros, o soporte para modo oscuro si el usuario lo tiene configurado en el sistema operativo. También me da la posibilidad de integrar características de plataformas específicas, como la compatibilidad con la TouchBar de MacBook.
Desarrollo del proyecto
La aplicación desarrollada se llama Gestor de Bloques, o GdB, como me referiré a ella a partir de ahora. En las siguientes secciones veré su funcionalidad y cómo se ha implementado.
Arquitectura
Para el correcto funcionamiento de la aplicación solo hace falta tener un servidor web y un dominio que resuelva a la dirección IP y al directorio donde GdB sincroniza los ficheros HTML. Al ser contenido estático, este gestor de contenidos es compatible con servidores Apache con PHP, servidores NodeJS o cualquier otro tipo de servidor web. No hace falta ninguna base de datos.
GdB se instala en el ordenador del usuario que va a gestionar el contenido de la página web; una vez configurada correctamente, esta aplicación se comunica por SSH (Secure Shell) con el servidor web. A través de este túnel cifrado, GdB es capaz de subir o eliminar ficheros HTML que después se servirán a un navegador por HTTPS.
Una de las ventajas de tener la aplicación instalada en el ordenador es la posibilidad de hacerla compatible con un modo de edición de contenido offline y sincronización posterior con el servidor web. Esta funcionalidad no está implementada en este TFG y queda como una posible mejora futura.
Estructura de ficheros
La aplicación consta de unos pocos ficheros principales en el directorio raíz y algunas carpetas donde se concentran los distintos recursos y funcionalidades de la aplicación.
Ficheros en la raíz
En la raíz del directorio encuentro los siguientes ficheros:
- «package.json»: cuando creo una aplicación en Electron, lo que estoy creando en realidad es una aplicación NodeJS en la que especifico que estoy usando el framework Electron. Esto lo especifico con el metadato «scripts», en el que indico que el comando «start» de NPM (Node Package Manager), el gestor de paquetes de NodeJS, ejecuta Electron. En este fichero también se especifican las dependencias de la aplicación, es decir, paquetes de NodeJS, así como la versión de la aplicación, su nombre, autor, descripción...
- «main.js»: este fichero JavaScript es el Back-End de la aplicación, ejecutado por el proceso principal. Se encarga de responder a las llamadas de JavaScript del Front-End y de crear la ventana de la interfaz web.
- «index.html»: es el fichero HTML usado para la interfaz gráfica de la aplicación; en este fichero se muestra toda la aplicación, ya que es una SPA, es decir, al moverme por las distintas secciones de la interfaz no genero ni destruyo ventanas, cambio el contenido sobre el mismo HTML.
- «style.css»: define los estilos generales del panel de la aplicación; hay otros ficheros de estilos, pero para otras partes más específicas de la aplicación.
Carpetas en la raíz
En la raíz del directorio encuentro los siguientes directorios:
- «node_modules»: este directorio contiene cada uno de los módulos de NPM usados por la aplicación; aparte de las dependencias que he necesitado, también están todos los ficheros pertenecientes al framework Electron.
- «modules»: cada una de las secciones del panel de la aplicación es un módulo distinto, esto se verá en profundidad más adelante. En este directorio se encuentran las carpetas de cada uno de los módulos que componen el panel.
- «font»: contiene la tipografía usada por los textos de la aplicación (Roboto), así como la tipografía de iconos (Material Icons).
- «auto»: la carpeta auto contiene todos los ficheros JavaScript que se usan en el lado del Front-End.
- «data», almacena ficheros con información que usa la aplicación localmente.
- «pages», contiene el HTML y los JSON generados por la aplicación que después se sincronizarán con el servidor.
- «template», contiene los ficheros necesarios para exportar el contenido en el formato deseado, según cómo se vaya a construir el HTML final.
Panel
Como he comentado antes, el panel se compone de un único fichero HTML sobre el que cargo y descargo, muestro y oculto contenido desde el JavaScript. El primer paso para la generación de la interfaz es la creación de la ventana desde el Back-End; para ello, el framework me da el objeto «BrowserWindow», en el que especifico que el ancho y el alto de la ventana serán por defecto 1200 píxeles, siendo el alto y el ancho mínimos permitidos 800 píxeles. Además, elimino el marco por defecto del sistema operativo para después crear y gestionar yo mismo los eventos de la ventana.
El panel consta de tres elementos básicos: una cabecera, un menú lateral y la sección donde se muestra el contenido. A continuación se puede ver el panel vacío, sin ningún módulo cargado.
Tanto la cabecera como el menú lateral tienen una posición fija, así que no se ven afectados cuando ocurre el evento de scroll. La sección principal del panel, donde aparecerá el contenido de cada sección del menú, tiene márgenes internos arriba y a la izquierda para que los otros dos elementos no se superpongan sobre el contenido. En la cabecera he incluido mis propios iconos para minimizar, maximizar y cerrar la aplicación.
Panel modular
Una de las intenciones de este panel es que estuviera abierto a nuevos desarrolladores que quieran usarlo para aplicaciones distintas con otros usos, por eso la funcionalidad de moverse por los módulos del menú y cargarlos en el panel es independiente del propio gestor de contenidos. Tiene dos peculiaridades que lo hacen modular.
Carga automática de JavaScript
El JavaScript que usa la aplicación desde el Front-End, como en las páginas web, se referencia desde el fichero HTML que especifica que lo necesita. La aplicación necesita muchos JavaScript distintos; sin embargo, desde el único HTML que tengo solo referencio uno, «auto.js».
Para contener mi código JavaScript, uso un objeto que contiene todos los métodos que necesito. Para generar esta carga automática de JavaScript he creado un objeto proxy que actúa de intermediario entre cualquier llamada a un método del objeto y el propio objeto.
El objeto proxy se ha llamado «auto»; las únicas condiciones al crear un fichero JavaScript nuevo son que sea una extensión de este objeto y que el fichero se llame igual que el método que implementa. Cada vez que llamo a un método de este proxy, comprueba si este método existe en el objeto final. Si existe, procede a llamar al método; si no, el proxy busca en el directorio «auto» un fichero JavaScript con el mismo nombre que el método pedido, y por último inserta una nueva etiqueta «script» en el HTML incluyendo este fichero.
Con todo esto consigo que incorporar scripts nuevos en este panel sea tan fácil como crear un fichero nuevo en el directorio «auto» y sin tener que incluir su carga en el HTML; la primera vez que se llame a ese método se cargará automáticamente. Como no se cargan todos los scripts al principio, ahorro tiempo en la primera instancia, y solo llevo a cabo la carga cuando es realmente necesario.
Carga automática de módulos
Llamo módulo del panel a cada una de las secciones del menú que, una vez accedidas, cargan su contenido en el espacio destinado para ello. Cada uno de estos módulos es un directorio, dentro del directorio «modules», que contiene cuatro ficheros distintos.
- «content.html»: este fichero contiene todo el código HTML del propio módulo, que después se embeberá en el HTML principal del panel.
- «content.css»: en este fichero va todo el código CSS del propio módulo, que después se añadirá mediante la etiqueta «style» en el HTML principal para cargar esta hoja de estilos.
- «content.js»: este fichero contiene todo el código JavaScript que afecta a las partes de interfaz de la parte HTML de este módulo, así como su propia funcionalidad.
-
«module.json»: define las características propias del módulo, usadas por el panel para su correcta carga. Es un fichero JSON con las propiedades:
- «icon»: especifica el icono que se mostrará en el menú.
- «name»: especifica el nombre del módulo, también usado por el menú.
- «html»: especifica el nombre del fichero HTML del módulo.
- «css»: especifica el nombre del fichero CSS del módulo.
- «js»: especifica el nombre del fichero JavaScript del módulo.
- «order»: especifica el orden en el que debe aparecer en el menú.
De esta forma, la creación de secciones del panel es completamente independiente del propio panel, dando la posibilidad de incluir lo que quiera, o incluso abriendo la puerta a que otros desarrolladores creen módulos nuevos que amplíen la funcionalidad del gestor de contenidos.
Funcionamiento
Una vez arrancada la aplicación, el JavaScript recorre el directorio «modules» para ver los módulos existentes; para cada uno de ellos, accede al fichero JSON para obtener toda la información del módulo y poder fijar el icono y el nombre correspondientes.
Al añadir un módulo nuevo, tengo que crear su sección correspondiente en el menú, así que genero un elemento HTML «li» nuevo que contiene el atributo «data-folder=nombreModulo»; con esto, cuando el usuario hace clic en una sección del menú, sabe qué módulo cargar.
La primera vez que se entra a un módulo desde el panel, este no existe, así que tengo que cargarlo en el panel. Un fichero JavaScript se encarga de comprobar si ya existe o no; si existe, oculto el módulo anterior, y si no existe, gracias al atributo mencionado, sabe a qué directorio acceder para obtener los ficheros HTML, CSS y JavaScript correspondientes, que se insertan en el HTML principal del panel.
Conflicto entre estilos
Uno de los problemas que me encontré al seguir esta lógica de los módulos es que, si un desarrollador externo a mí aplica sus estilos en su módulo, estos entran en conflicto con los estilos generales del panel. Esto ocurre porque no hay forma en la tecnología web de indicar que un fichero de estilos afecte solo a un módulo del panel, así que podría darse el caso de que reglas de estilo afecten a otros elementos HTML no deseados, causando errores de visualización en la interfaz.
Para resolver este problema, todos los ficheros CSS de los módulos deben tener sus reglas de estilo empezando por la condición «[data-module=nombre-del-modulo]», así me aseguro de que estas reglas afectan solo al HTML contenido dentro de la etiqueta que contiene el propio módulo.
Conflicto de JavaScript
El mismo problema que tuve con los estilos me lo encontré con el JavaScript. Al tener un fichero para cada módulo, y estos pueden haberlos desarrollado personas distintas, se puede dar el caso de que se solapen nombres de variables, se sobrescriban métodos, o que al hacer el «querySelect» para tomar elementos del DOM esté seleccionando elementos que no pertenecen a ese módulo.
Para resolverlo, creo un objeto que contiene, para cada uno de los módulos, un atributo propio con todo el HTML de ese módulo; al hacer un «querySelect», en vez de hacerlo sobre todo el DOM, lo hago solo sobre este atributo del objeto (objeto.nombreModulo.querySelector('.clase');).
Comunicación entre módulos
Como los módulos son independientes entre sí, no hay forma directa de compartir información entre ellos, ya que su JavaScript no pertenece al mismo objeto ni depende de otro JavaScript común que pueda actuar de intermediario. La solución a este problema ha sido implementar un atributo en el objeto proxy del panel, accesible por cualquier módulo, para poder almacenar variables como una cookie local del ordenador.
Eventos en el Back-End
He visto cómo, desde el proceso principal, he creado la ventana donde se muestra la interfaz de la aplicación; aparte de esta función, desde aquí gestiono todas las llamadas que vienen del Front-End.
Por ejemplo, como he eliminado el marco del sistema operativo de la ventana, los iconos que se muestran en el HTML, una vez que se hace clic en ellos, el evento lo recoge el JavaScript del lado del Front-End, pero este no tiene la capacidad de ejecutar las instrucciones de Electron para realizar la tarea correspondiente. Lo que hacen estos eventos es enviar un mensaje al Back-End indicando que se quiere realizar esta tarea, y esto lo ejecuta el proceso principal.
Estos eventos se pueden haber enviado de forma síncrona o asíncrona; en el caso de los eventos síncronos, enviaré una respuesta de vuelta al Front-End para que pueda continuar con su ejecución.
Configuración de la aplicación
La primera vez que ejecutas la aplicación, una vez instalada en tu ordenador, aparece un proceso de configuración sencillo para indicar la dirección y las credenciales del servidor web que se va a usar.
El campo «Server» acepta tanto la dirección IP de un servidor como un dominio que resuelva a esa dirección IP. Los campos «Username» y «Password» corresponden a las credenciales SSH del servidor; preferiblemente, y para evitar problemas de permisos, se recomienda que el usuario indicado sea root. El campo «Path» especifica el directorio en el que tiene que trabajar la aplicación; este directorio tiene que estar dentro de los directorios del servidor web y tiene que poder resolverse la petición a estos ficheros por HTTPS.
Cuando el usuario pulsa el botón «START», la aplicación procede a establecer una conexión SSH con el servidor; esta conexión se consigue usando la librería «node-ssh» de NPM. Lo primero que hago es ejecutar el comando «ls» en el directorio indicado por el usuario y comprobar que no exista ya una carpeta llamada «blog», que es el nombre del directorio que usa la aplicación. Como lo estoy configurando por primera vez en un servidor donde nunca se ha conectado esta aplicación, este directorio no existirá, así que por SSH creo el directorio con otro directorio dentro llamado «data», que usaré para almacenar las credenciales de inicio de sesión. Por último, antes de terminar este proceso, genero un fichero JSON que guardaré localmente con los datos introducidos por el usuario; así, la próxima vez que entre en la aplicación, antes de pedirlos, comprobará si existen en este fichero, y si es así, se saltará este paso y leerá el JSON para tener las credenciales y poder establecer la comunicación.
Ya tengo la conexión establecida con el servidor, pero no tengo ningún usuario creado; esto es porque, al hacer SSH y un «ls» en la carpeta «data», no hay ningún «users.json», así que la aplicación me pide crear uno nuevo.
Una vez que el usuario hace clic en «SIGN IN», lo primero que compruebo es que ambas contraseñas introducidas sean idénticas; si es así, procedo a generar el usuario. Generar un usuario no es más que crear un fichero JSON con los datos; como este fichero residirá en el servidor, no puedo guardar la contraseña en claro, así que la paso por una función hash segura. Para esto uso la librería «bcrypt», en concreto el método «hash», que usa una función hash de implementación propia, a la que indico como argumento el número de rondas por las que pasará la contraseña, cada una de ellas con una sal distinta. Una vez que tengo el hash de la contraseña, genero un fichero «users.json» donde guardo todos estos datos y lo subo al servidor, al directorio «data».
El propósito de este trabajo no es crear una aplicación totalmente segura, y por eso no he profundizado más en la seguridad de las credenciales. Por la naturaleza de la aplicación, no es posible acceder al proceso de autenticación desde fuera, ya que la aplicación reside localmente en tu ordenador, pero, como la carpeta «data» reside en el servidor y contiene información sensible, se recomienda que el usuario no haga esta carpeta accesible mediante petición HTTP/HTTPS. Este tipo de reglas, en servidores web como Apache, se pueden especificar en el fichero «.htaccess». Para futuras actualizaciones de la aplicación sería interesante reforzarla para evitar posibles ataques y manipulaciones no deseadas de las páginas web generadas.
Inicio de sesión
Para poder usar la aplicación, hace falta pasar por un proceso de autenticación y autorización, para evitar que otra persona con acceso a tu ordenador pueda modificar la web. Este proceso también será importante para funcionalidades posteriores de esta aplicación, como la diferenciación entre roles de usuario y los permisos que tiene cada uno, mejoras no implementadas en este TFG.
La aplicación descarga del servidor, cada vez que inicio sesión, la última versión del fichero «users.json». Una vez que tengo el fichero localmente, lo paso a un objeto JavaScript y comparo los campos existentes con los introducidos por el usuario; como la contraseña no está en claro, uso el método «compare» de la librería «bcrypt», que pasa la contraseña en claro introducida por el usuario por la función hash y determina si es correcta.
Una vez que el Back-End le comunica al Front-End que el proceso ha sido correcto, procede a ocultar el cuadro de inicio de sesión y a cargar los módulos de la aplicación en el panel, como se ha explicado antes.
Tema oscuro y tema claro
Siguiendo la tendencia actual en los front ends web, las aplicaciones móviles y las aplicaciones de escritorio, GdB tiene soporte tanto para el tema oscuro (el visto hasta ahora) como para el tema claro.
Para desarrollar una única interfaz independiente de los colores, he desarrollado todas las reglas CSS referidas a colores usando, en vez de valores fijos, nombres de variables referidas a niveles de oscuridad. En concreto, la interfaz se mueve en siete tonos, es decir, siete niveles de color, que por detrás pueden ser lo que se quiera siguiendo una escala dentro del mismo rango.
Para decidir qué rango se aplica a la interfaz, se aplican variables CSS que contienen los colores a distintas clases del elemento «body» del documento HTML. Si el «body» no tiene ninguna clase definida, por defecto la interfaz es clara; sin embargo, cuando añado la clase «invert» se vuelve negra, esto ocurre porque sobrescribo las variables con esos colores y automáticamente cambian todos los elementos de la interfaz.
Módulo editor
Un gestor de contenidos debe tener un editor de texto que ofrezca a sus usuarios una interfaz sencilla donde escribir. El módulo encargado de esto se llama «editor»; se ha diseñado siguiendo una estructura de bloques, de ahí el nombre «gestor de bloques», en la que cada elemento para insertar contenido es un bloque. Cada uno de estos bloques consta de dos elementos, el texto del bloque y su tipo.
Un post de blog en este gestor de contenidos no es más que una sucesión de bloques, en la que cada uno de ellos tiene su propio espacio donde escribir el texto y un selector abajo a la derecha que me permite definir si el texto es un párrafo, título, subtítulo o cabecera. Los botones de la parte inferior izquierda de cada uno de estos bloques me permiten:
- El botón de más me permite añadir un bloque nuevo, que se insertará justo debajo del bloque desde el que estoy haciendo clic en el botón.
- El botón de menos me permite eliminar el bloque en el que estoy en ese momento.
- El botón de flecha hacia arriba permite mover el bloque una posición hacia arriba.
- El botón de flecha hacia abajo permite mover el bloque una posición hacia abajo.
Bloques
Cada uno de estos bloques en HTML es un «section» con la clase «elementEditor», que me permite después, desde el JavaScript, recorrer cada uno de ellos para extraer el contenido. A su vez, el bloque en el que estoy en un momento dado recibe la clase «active», que resalta el bloque para que el usuario sepa dónde está, muestra el menú inferior con los botones y permite al usuario editar el texto. Dentro de este «section» encuentro dos partes distintas.
La parte del bloque que permite al usuario insertar texto es un «div» que, mediante el atributo «data-type», especifica qué tipo de elemento contiene; en la ilustración 11 veo cómo es un bloque de tipo texto, por lo que el «data-type» de este bloque será «text» y el elemento HTML de dentro será un «p». Para que el usuario pueda insertar texto, al elemento HTML se le especifica el atributo «contenteditable=true»; esto permite que un usuario inserte texto desde la interfaz, y este quedará dentro del elemento HTML que recibió esa propiedad.
La segunda parte del bloque es otro «div» que contiene los iconos de los botones con sus respectivos ids para poder tratar los eventos desde el JavaScript, y un «select» para poder cambiar el tipo de bloque que quiera.
Interfaz del módulo
Cuando voy a crear un artículo nuevo para el blog, entro al módulo editor del panel; por defecto solo aparece un bloque de tipo título, es decir, un «h1». Este bloque es el único al que la aplicación no permite cambiarle el tipo, ni moverlo de posición respecto a otros bloques; esto es porque, por buena práctica al generar una web, todas deben contener un único «h1» y este debe estar situado al principio, ya que los motores de búsqueda dan mucha relevancia a su contenido para indexar resultados. Otros bloques posteriores tampoco permiten cambiar el tipo a título, para evitar tener más de un «h1» en la página.
Para una escritura más rápida, además de los botones disponibles en cada uno de los bloques, creo eventos que se disparan al pulsar ciertas teclas del teclado, como enter, que tiene el mismo comportamiento que el botón de más; las flechas del teclado me permiten moverme rápidamente entre los distintos bloques.
Cuando añado un bloque nuevo, lo que hago es generar el HTML del bloque desde el JavaScript e insertarlo después del bloque desde el que se está añadiendo. Lo mismo hago al eliminar o moverme entre bloques; son todo eventos de JavaScript que alteran el DOM para cumplir la funcionalidad de cada uno de los botones.
Abajo a la derecha encuentro un botón que, al hacer clic, muestra otros dos: con uno puedo guardar el artículo y con el otro accedo a los campos que tengo que rellenar con los metadatos del artículo.
Plantilla
Como ya he mencionado, uno de los objetivos es poder añadir este blog sobre páginas ya desarrolladas. Para que la exportación de los bloques se haga en un HTML adaptado a la página web en la que irá este blog, es tan fácil como añadir una plantilla en el directorio destinado para ello dentro de la aplicación.
Una plantilla se compone de tres SHTML distintos (un trozo de HTML que se inserta en algún sitio), y usa un sistema de marcado para su posterior sustitución, buscando palabras clave que se sustituyen por contenido. La idea es dividir la estructura HTML de tu página web actual en dos partes: por un lado, todo el HTML que debe ir por encima del contenido del artículo que vas a publicar (menú web, cabecera...) y, por otro, el código HTML que debe ir por debajo del artículo (footer, menú inferior...). El nombre dado al SHTML superior es «up.shtml» y al inferior «down.shtml». El tercer SHTML necesario es el que define la estructura HTML con la que se mostrará cada una de las entradas de artículo en la página principal del blog, desde la que se puede acceder a cada uno de los artículos.
En la ilustración 14 se puede ver la estructura definida para exportar cada una de las entradas tal como quiero que se vean en mi web, donde «#url#» se sustituye por la URL que lleva al post en cuestión, «#title#» por el título del artículo, «#description#» por la descripción general del artículo y, por último, «#date#» por la fecha en la que se escribió. Esto, una vez en el servidor y con el CSS de mi web, se vería así.
Exportación del artículo
Una vez que el usuario ha terminado de editar el artículo, solo tiene que pulsar el botón de guardar. Cuando esto ocurre, la aplicación genera dos ficheros, un fichero JSON y un fichero HTML.
Como he comentado antes, los bloques donde edita el usuario son un «div» que contiene dentro el elemento en cuestión (h1, h2, h3, p) según el tipo de bloque que sea, con la propiedad que se puede editar, así que para generar el HTML final que se publicará en el servidor solo tengo que tomar de la carpeta de plantillas el fichero «up.shtml» y concatenar esa cadena con todos los bloques que ha hecho el usuario, y por último concatenar esto con el fichero «down.shtml». Así tengo el fichero completo listo para subir al servidor, aunque de momento este fichero solo existe localmente en mi ordenador.
Al mismo tiempo, al guardar, genero un objeto nuevo que finalmente se guardará en un JSON; este objeto almacenará información relevante sobre el artículo que acabo de crear, como la fecha en la que se creó, si está online u offline en el servidor, un id único para diferenciar cada uno de ellos, y por último un objeto dentro de él y un array de objetos. El objeto «goals» contiene todos los metadatos que el usuario ha puesto en el artículo. El array de objetos «blocks» es el que contiene todos los bloques del editor; cada uno de estos bloques es un objeto con dos propiedades, el tipo del bloque y su contenido.
Este JSON que he generado será útil después para obtener información importante sobre cada uno de los HTML al sincronizar con el servidor, o para reconstruir el editor del artículo con el contenido del artículo, cuando un usuario quiera hacer una modificación sobre uno generado en otro momento.
Módulo buscador
Este módulo me permite visualizar las páginas existentes de mi blog, con la posibilidad de modificarlas, eliminarlas o establecer si en la siguiente sincronización tienen que estar online en el servidor web, o en el caso contrario, establecer que no se sincronicen y existan solo localmente en mi ordenador.
Cuando entro en este módulo, el código escanea el directorio donde están los ficheros JSON que se generan cada vez que creo una página nueva; para cada uno de estos ficheros, añade una entrada nueva en la interfaz con los datos de la página en cuestión, que se obtienen de este mismo fichero JSON, en concreto el nombre de la URL y la fecha en la que se creó la página.
Eventos en las páginas
En cada una de estas entradas, es decir, para cada una de estas páginas creadas previamente desde el editor, tengo tres opciones posibles.
Si el usuario quiere eliminar una página que ya no hace falta, hay un botón de papelera. Al hacer clic, la aplicación busca el fichero JSON y HTML correspondientes a esta entrada y los elimina, y a su vez quita la entrada del DOM en la interfaz.
Si quiero añadir contenido nuevo o simplemente editar contenido existente en una página ya creada, tengo el botón de editar. Editar una página consiste en enviar al usuario al módulo editor, pero estableciendo previamente en una cookie destinada para ello el identificador de la página a editar. Cuando carga el módulo editor, comprueba si esta cookie tiene algo; si es así, en vez de cargar desde cero, usa el fichero JSON de la página a editar y se reconstruyen en la interfaz los bloques existentes de ese artículo.
El interruptor azul y negro que se ve en la Figura 16 se usa para marcar si quiero que una página se sincronice con el servidor en la próxima sincronización; si está desactivado no se subirá, y si ya estaba subida, se eliminará del servidor. Para guardar la información de qué página se debe subir y cuál no, también uso su fichero JSON, en el que se modifica una propiedad booleana entre true y false cada vez que pulso el interruptor.
Por último, encuentro el botón «new», que me permite crear páginas nuevas desde cero. Si edito una página en el editor, por mucho que me mueva a otro módulo y vuelva, el editor seguirá teniendo el contenido que estaba escribiendo; para reiniciar el editor y empezar una página nueva hace falta acceder desde este botón dentro del módulo buscador.
Sincronización
La parte más importante de la aplicación es la sincronización entre los ficheros creados por el gestor de contenidos en el ordenador personal del usuario y el servidor web. Como el resto de comunicaciones con el servidor de la aplicación, la sincronización se lleva a cabo mediante instrucciones SSH.
Para iniciar la sincronización, se hace clic en el botón de sincronizar, situado abajo a la derecha en la Figura 16. Se recorre cada una de las páginas existentes en el gestor; si tienen la propiedad «online» con el valor true, se sube al servidor el fichero HTML correspondiente. Esta subida solo ocurre tras comprobar que el fichero no está ya en el servidor; esto lo sé por otra propiedad del JSON que indica si se ha subido al servidor en algún momento, o si está en el servidor comparando la fecha en la que se subió con la fecha de la última modificación del artículo, siendo esta última más reciente que la anterior. Todos los elementos que están como online los guardo en un array para la construcción de la página principal del blog.
Cuando un artículo se marca como no sincronizado, no basta con no hacer nada, porque puede que ya se haya sincronizado y esté online; por tanto, si el JSON indica que el artículo está en el servidor, por SSH eliminaré el fichero en el servidor.
Al sincronizar no basta con subir solo los ficheros de cada uno de los artículos, sino que hace falta generar una página índice que muestre las entradas del blog y permita acceder a cada uno de los artículos. Cada vez que sincronizo, se elimina la página principal del blog que existe en ese momento y se genera una nueva desde cero. Para cada uno de los artículos existentes se usa la plantilla «entry.shtml», como se explica en el apartado 7.9.3, y por encima y por debajo el resto de la plantilla de la web. Una vez creado este fichero HTML, como los demás, se sube al servidor, dejando el blog completamente actualizado.
Mejoras futuras de la aplicación
Block Manager es una pequeña demo funcional de un gestor de contenidos más potente que se desarrollará más adelante a partir de este trabajo. Como cualquier demo, y debido a las limitaciones de tiempo de un TFG, hay una serie de funcionalidades no implementadas en esta demo pero que merece la pena mencionar.
Más tipos de bloques
Como se ha mencionado antes, esta demo solo contiene cuatro tipos de bloques distintos: título, subtítulo, cabecera y texto. Sin embargo, para que los usuarios puedan escribir un artículo, hace falta darles alguna herramienta más, como bloques de imagen o bloques de código fuente, si el blog pertenece a un desarrollador.
Un bloque de imagen es algo más complejo que cualquier bloque de tipo texto por dos motivos: la vista previa de la imagen en el bloque una vez insertada por el usuario, y la funcionalidad de cómo insertar la imagen. Para resolver esto último de la forma más profesional posible, haría falta maquetar un input de tipo «file» con una sección grande que también admita arrastrar un fichero dentro (drag and drop); Electron incorpora en su API una funcionalidad específica para esto.
Módulo de productos
Un gran porcentaje de las páginas web de hoy en día son de comercio electrónico, pequeños negocios que venden sus productos a través de una página web. Uno de los objetivos futuros de este gestor será incorporar un módulo nuevo que permita crear un catálogo de productos; esta sería la forma de monetizar la aplicación, ofrecer a los usuarios una pasarela de pago propia del gestor y cobrar una comisión por cada venta realizada en páginas web creadas con mi aplicación.
Modo offline y sincronización asíncrona
Una de las ventajas de ser una aplicación de escritorio es que se podría implementar de forma que se pudiera usar sin necesidad de tener conexión a Internet. Esto se podría implementar usando el último fichero «users.json» descargado del servidor para el inicio de sesión; todos los cambios en las páginas se registrarían en un JSON con el historial de cambios que después se podría contrastar con el servidor y las posibles modificaciones hechas por otros usuarios, para resolver posibles conflictos. Cuando el usuario vuelva a estar online, todos los cambios hechos durante el uso offline se sincronizarían automáticamente con el servidor.
Actualmente, durante el proceso de sincronización la aplicación se queda congelada mientras se realiza este proceso; si el volumen de elementos es muy grande, este proceso puede tardar varios minutos. Sería interesante que la ejecución de este proceso se hiciera de forma asíncrona respecto al resto de la aplicación, así podría seguir usándola mientras sincroniza.
Pruebas de rendimiento
Una vez que la aplicación estaba completamente funcional, pensé que sería interesante hacer una comparación entre una página de mi blog generada con mi gestor de contenidos y el mismo artículo hecho en un WordPress recién instalado, para obtener los tiempos de procesado a nivel de servidor, carga y ejecución de la página. Esto me permite sacar conclusiones sobre cuál de los dos gestores obtiene mejor rendimiento.
En la comparación hay que tener en cuenta que le estoy «dando ventaja» a WordPress, ya que está sin ningún plugin instalado. Para poder hacer la comparación en igualdad de condiciones en cuanto a SEO, haría falta instalar al menos el plugin «Yoast». Para replicar exactamente la interfaz de mi web en WordPress necesitaría cargar plugins y personalizaciones adicionales que añadirían todavía más tiempo a este proceso de carga.
Para realizar las pruebas he usado dos servidores idénticos con el proveedor Arsys, en concreto dos servidores virtuales CentOS 8 con Plesk instalado, que no es más que un panel amigable para gestionar Apache por debajo. En cuanto a la resolución DNS, he forzado al ordenador a usar un servicio de resolución DNS 8.8.8.8 y 8.8.4.4, es decir, Google DNS, que cachean la resolución de las consultas DNS, dejando estos tiempos en nulo (0 ms) y, por tanto, descartando esta variable de los resultados obtenidos.
Para registrar el tiempo que tarda en procesarse cada recurso cargado en la página web, usé Chrome DevTools en una pestaña de incógnito, así me aseguro de que en la caché de mi navegador no hay recursos de las webs y se hace una petición desde cero [23].
En las ilustraciones 17 y 18 puedo ver el mismo post con el mismo texto. La ilustración 17 es mi página web generada con el CMS desarrollado en este TFG. La ilustración 18 es una página generada por WordPress con el mismo contenido que el artículo de mi web.
Al usar la pestaña Network de Chrome DevTools, hay tres medidas de tiempo distintas que puedo tomar y que es importante diferenciar:
- DOM Content Loaded representa el tiempo que tarda la página web desde que el navegador hace la petición de la URL hasta que el documento HTML se ha cargado por completo en el navegador y se ha interpretado; este tiempo se toma antes de pedir los recursos adicionales que necesita, como imágenes u hojas de estilo [24].
- Load es el tiempo de DOM Content Loaded sumándole también el tiempo empleado en pedir y descargar los recursos adicionales que se piden en el HTML.
- Finish marca el momento en el que la página ha terminado de representarse gráficamente en el navegador.
La ilustración 19 corresponde a los resultados obtenidos en la página web generada con mi gestor, mientras que la ilustración 20 muestra los resultados de la página generada con WordPress. Las muestras tomadas corresponden a una medición hecha en un momento concreto; cada vez que se hace una petición estos tiempos fluctúan, pero se mantiene la misma tendencia, así que es lo bastante relevante como para comentar los resultados. La diferencia es apreciable, tanto en tiempo como en espacio en disco ocupado por los recursos que componen la página y en número de peticiones, siendo que la página generada con mi gestor ha cargado en aproximadamente una sexta parte del tiempo y transfiriendo menos del 10% de los datos que la página generada por WordPress.
En cuanto al número de peticiones, al usar código hecho por y para esta página puedo reducir el número de ficheros necesarios y optimizar su compresión, lo que supone menos peticiones al servidor y un tamaño menor. WordPress, por su parte, carga una serie de recursos definidos por el propio gestor y por la plantilla usada, siendo esta una de las «plantillas más ligeras». La diferencia es apreciable, pasando de diez peticiones a cuatro. Con esta optimización, los datos transferidos se reducen un 80%, 122 kB de recursos en mi página web mientras que WordPress transfiere 582 kB, ya que, además, transfiere más código del necesario para mostrar esta página en concreto. Esta diferencia en recursos transferidos, a medida que la web se vuelve más compleja, se hará más relevante, obteniendo así mejores tiempos de respuesta que un sitio WordPress convencional.
Conseguir que el DOM Content Loaded, el Load y el Finish sean lo más bajos posible permite que los elementos de la página estén disponibles antes para el navegador. Desde el punto de vista de la interacción de un usuario humano con la página web, esto es importante porque, como se comenta a continuación, la experiencia de usuario es prácticamente inmediata (eventos controlados por JavaScript...). Por otro lado, desde el punto de vista de los motores de búsqueda, es una ventaja competitiva, porque tienen que dedicar menos tiempo a analizar la página y la puntuación obtenida es mejor.
Para poner en contexto los tiempos obtenidos respecto a la reacción de un ser humano ante un evento que requiere una interacción, hace falta tener datos sobre el tiempo de respuesta medio de los humanos.
El gráfico que se muestra en la Ilustración 21 muestra el tiempo de respuesta de un ser humano ante un elemento que requiere interacción; se ha obtenido tomando 81 millones de muestras y la media es de 284 ms. Esto implica que las interacciones con mi página web no requieren espera por parte del usuario: al haber obtenido un tiempo de 52 ms de representación gráfica (Finish), el usuario no tendrá que esperar a que cargue un contenido antes de interactuar con él. El estándar de vídeo, 24 fotogramas por segundo, me indica que el ojo humano no puede apreciar saltos por debajo de 42 ms, así que el tiempo de carga entre el final de la descarga del HTML (14 ms) y el momento en que se muestra la página entera (52 ms) da una diferencia de 38 ms, que serían aproximadamente 0,9 fotogramas, algo no perceptible, dando la sensación de una carga «instantánea». Esto mismo, en la carga de WordPress, sí es apreciable, ya que el tiempo entre la descarga del HTML (76 ms) y el Finish (369 ms) da una diferencia de 293 ms, que serían aproximadamente siete fotogramas, por lo que el ojo humano sí notaría la diferencia. En una página sencilla, estos datos en ambos casos son bajos, pero, a medida que aumenta la complejidad, la diferencia de tiempo es mucho más relevante.
Estas mejoras en tiempo y optimización de recursos de las que he hablado se reflejan en los informes generados por la herramienta Lighthouse en las Ilustraciones 22 y 23; sus informes completos están en el Anexo C y el Anexo D del trabajo. Puedo ver cómo la página generada por mi gestor de contenidos ha obtenido una valoración de cien, mientras que la página generada por WordPress solo ha obtenido un ochenta y cinco.
Si me fijo en el informe completo, veo cómo Lighthouse valora positivamente que mi página web haya cargado solo las reglas de estilo CSS completamente necesarias para el documento, así como el hecho de cargarlas de forma ordenada. Lo mismo se aplica a JavaScript, siendo solo las funciones usadas y sin forzar a que la página tenga que volver a renderizarse durante la ejecución.
El resultado obtenido en SEO se debe a que mi página se ha generado con todo el contenido necesario a nivel de optimización para motores de búsqueda. Se han cargado los metadatos necesarios para la correcta identificación de la página, mientras que WordPress, al no estar configurado con plugins adicionales (para no empeorar todavía más el resultado de WordPress en rendimiento), no tiene estos elementos.
Conclusión
Este proyecto surgió para resolver un problema que encontré desarrollando mi web personal, y para el que ninguna de las aplicaciones o tecnologías que evalué me daba una solución que me pareciera adecuada para el uso que le iba a dar. Aprovechando la necesidad y la motivación de profundizar en esta área, nació la idea de crear esta aplicación de escritorio compatible con todos los sistemas operativos y todo tipo de hosting web.
A lo largo del proceso del TFG he profundizado en tecnologías actuales como NodeJS y Electron, siguiendo la tendencia del mercado de llevar la tecnología web a las aplicaciones de escritorio. He podido entender mejor el comportamiento de los navegadores, sobre todo de Chrome, el navegador que usa Electron para mostrar la interfaz, y el motor de JavaScript que usa NodeJS como intérprete. También los elementos que influyen en la evaluación de una página web, sobre todo mediante Lighthouse y Chrome DevTools, de los que he sacado todo el partido para analizar los resultados obtenidos, descubriendo en muchos casos elementos que se valoran y que no se tienen en cuenta inicialmente.
Al principio, pensar en desarrollar una herramienta que te permita crear y gestionar contenido estático parece una idea sencilla. La realidad es bastante distinta cuando te pones manos a la obra. Los elementos que componen el DOM [26], así como los recursos que lo modifican, generan una página web con una estructura compleja que hay que tener en cuenta al generarla, complicando el desarrollo. A todo esto tengo que añadir los componentes de conexión con el servidor, el módulo de edición, la gestión de usuarios y la interfaz gráfica, que han resultado ser más complejos de lo que imaginaba al principio.
Con mi trabajo he confirmado mi hipótesis inicial, aunque las pruebas realizadas en este proyecto no son suficientes para poder afirmarlo categóricamente: WordPress, con la configuración por defecto y sin usar plugins adicionales, al crear una página web sencilla, no ha sido la aplicación más eficiente, generando una página más lenta y con peor valoración SEO.
La aplicación que he desarrollado, en cambio, ha resultado ser un muy buen generador de páginas web, consiguiendo un rendimiento excelente y un buen posicionamiento SEO en cada publicación. La carga de estas páginas es visualmente instantánea, 56 milisegundos, la transferencia de datos se reduce a lo justo y necesario para cada página, y el número de peticiones al servidor se reduce al máximo (como se puede ver en el apartado 8).
Descargar PDF de mi Trabajo de Fin de Grado.
¡Gracias por leer! Nos vemos en el próximo.