23 septiembre, 2024

Simplificando el fullstack

Por ciertas circunstancias, he contado con unas semanas de "ajustes" laborales que me han permitido dedicarle tiempo a repensar el stack utilizado para desarrollar las aplicaciones corporativas. En los últimos años, abandoné PHP para abrazar un stack formado por un back-end que consistía en un API server hecho con Node y Koa. Dicho back-end servía peticiones realizadas desde un front-end estático (archivos html) que usaba Vanilla JS o bien Petite Vue o bien Vue (usado desde CDN y sin compilación y con el módulo httpVueLoader si necesito componentes SFC).

El caso es que el cambio más importante al pasar de una tecnología a otra ha sido pasar de una concepción centrada en contenido HTML (porque PHP está optimizado para ello) a una centrada en contenido JSON (y por tanto obligando al uso de JavaScript en el front-end). Que no se me malentienda, me encanta JavaScript, pero me encuentro entre los cada vez más numerosos desarrolladores que se preguntan si el camino hacia la extrema complejidad emprendido por la industria hacia frameworks como Angular, Vue o, especialmente, React (entre otros).

Siempre he tenido la teoría de que demasiados desarrolladores que vienen de lenguajes como Java, C, C++ o C# nunca se han encontrado "cómodos" con un lenguaje interpretado como Javascript (solo eso ya explica gran parte de la aceptación de TypeScript), pero a la larga, tampoco han aceptado HTML ni mucho menos CSS. Por ello tienden a sustituir todo lo que no "les gusta" por código Javascript, creando problemas graves de rendimiento y haciendo cada vez más grande una montaña de parches que solo pretenden aliviar ligeramente los problemas que no dejan de multiplicarse por el camino emprendido.

Digo esto porque tenía claro que mi movimiento anterior de estar centrado en HTML a estarlo en JSON me parece un error cada vez más evidente y que cualquier propuesta de nuevo cambio tenía que estar basado en volver a abrazar la herramienta básica: el navegador. Una aplicación extremadamente optimizada para trabajar con HTML y con el que es extremadamente rápido, y más con la potencia de los equipos/móviles de hoy día.

Así que con esa premisa y, no lo voy a negar, mis propias preferencia, me puse a diseñar un nuevo stack con el que me sintiese a gusto y que me simplificase en el futuro el desarrollo de webapps. Estas son mis especificaciones previas para dicho stack:


BACK-END


  • La idea principal es crear un código minúsculo para gestionar las peticiones web recibidas y que usase módulos para extender las funcionalidades.

  • Usé Node porque no deja de mejorar, por su comunidad y por su uso mayoritario. Aunque lo cierto es que estuve planteando también Bun y Deno como opciones. Finalmente quise aprovechar todo el ecosistema de herramientas y conocimiento alrededor la pieza de servidor de aplicaciones.

  • Respecto al framework para Node, descarté Koa. No tuve una mala experiencia, pero creo que es un proyecto medio abandonado y no me parecía lo más adecuado. En su momento lo elegí porque venía del equipo de Express pero corrigiendo muchos de los problemas que han tenido que mantener por compatibilidad (deuda técnica), prácticamente del mismo modo que Deno está basado en Node pero corrigiendo varios puntos que ya no se pueden corregir en Node. Estuve considerando primero Hyper-Express por su extremo rendimiento hasta que descubrí que tenía una dependencia que no me gustaba (interacción con una aplicación externa en C++) por lo que decidí abandonar esa vía. Después estuve probando con Fastify, pero me pareció excesivo para lo que trataba de hacer, y no me gustaban demasiado los compromisos adquiridos para conseguir mejorar el rendimiento. Finalmente descubrí Micro de Vercel. Es una opción muy minimalista, pero para lo que necesito creo que me viene como un guante. Nótese que ni siquiera trae un sistema para identificar rutas.

  • La idea de identificar peticiones es la siguiente: las aplicaciones estarán en su propio directorio, dentro de "public", que será el que use el backend. Cuando el servidor recibe una petición, intenta seguir manualmente cada segmento del path solicitado y busca por este orden:

    • un directorio con el mismo nombre que el segmento

    • archivo estático con el camino exacto (a excepción de archivos mjs o eta)

    • un archivo index.html en el directorio actual

    • un archivo llamado igual que el segmento comprobado pero con la extensión mjs

    • un archivo llamado igual que el segmento comprobado pero con la extensión eta (veremos esto después)

    • un archivo llamado igual que el método llamado (get/post/put/delete/...) y con la extensión mjs


    Esto me permite una flexibilidad tremenda a la hora de generar código personalizado en el lugar adecuado y organizar mejor el código.

  • Los archivo mjs són módulos JavaScript con una función exportada que recibirá los objetos (request y response) y devolverá el código HTML generado. Por ejemplo...

    
    /* check.mjs */
    export default async function handler(req, res) {
        const logged= (req.session?.id) ?1:0;
        res.end( logged.toString() );
    }
    


  • Los archivos eta son plantillas de código HTML que permiten generar contenido HTML pudiendo indicar segmentos de código Javascript (de nuevo tenemos request y response disponibles). Por ejemplo...

    
    <!DOCTYPE html>
    <html>
      <h3 title="[{{Object.keys(it)}}]">Eta file rendered</h3>
    {{JSON.stringify(it.params)}}
    {{{
        try {
            const result = await it.mssql.query('SELECT * FROM tabla WHERE offdate is null');
            // ...
    		} catch {}
    }}
    

    Hay tres tipos de expresión permitidos: interpolate: {{~ ... }} para establecer una cadena que debe ser enviada sin interpretar como html; exec: {{{ ... }} para código javascript que no genera una respuesta a enviar; y raw: {{ ... }} que envía como html el resultado de la ejecución de la expresión indicada.
    Este mecanismo me permite no definir rutas y basarlo todo en archivos y directorios, simplificando tremendamente la gestión.

  • He incluido una gestión de sesión extremadamente básica (actualmente basada en cookies para no depender de Javascript para el uso de JWT) y una gestión de errores que intenta dar información más explícita sobre los errores ocurridos, al estilo de PHP (aunque la idea es que pueda desactivarse para aplicaciones en producción). Con la información del error incluyo la posibilidad de solicitar a la api de OpenAI una sugerencia de resolución del error a partir del contexto de éste.

  • Me quedan pendientes de desarrollar varias ideas:

    • al igual que compruebo la existencia de un archivo mjs o eta, quiero hacerlo también de un archivo json con el nombre del segmento. Su función es especificar los pasos de un proceso bastante típico: origen de datos (archivo físico, recurso URL o consulta de base de datos), proceso de transformación a aplicarle a esos datos (indicando el archivo mjs que realizará la transformación) y, opcionalmente, un template eta para visualizar el resultado de la transformación.

    • sistema de caché de módulos para minimizar las consultas al sistema de archivos para comprobar la existencia del elemento a servir/ejecutar.

    • módulo de invalidación de caché para que Node vuelva a solicitar un módulo que ha sido modificado. La idea es que el editor de código envie una notificación a ese módulo con el archivo modificado, de forma que Node lo elimine de su caché de módulos y, por tanto, siempre se sirva la última versión.

    • facilitar el uso de SQLite a nivel de aplicación y de servidor.

    • módulo con mecanismo de subscripción y envío de notificaciones


    Código disponible en github


    FRONT-END

    Respecto al front-end, hay una serie de proyectos que he estado siguiendo y estudiando. Especialmente HTMX y sus simplificaciones htmf y incluso htmz. El concepto base de HTMX de recuperar el uso de HTML para las aplicaciones me encanta, pero veo demasiado pesado todo el sistema de triggers con lenguaje propio. También he visto una cantidad elevada de microframeworks frontend nuejs.org, strawberry.quest, https://github.com/reallygoodsoftware/minijs , https://github.com/anh-ld/nho , https://github.com/WebReflection/uce , alpinejs.dev entre otros muchos. Pero finalmente he optado por aprovechar PetiteVue pese a que es un proyecto "acabado" dado que lo he estado usando durante varios años sin incidencias remarcables.

    Mi idea es generar una extensión (la he llamado nanovue) basada principalmente en directivas (mecanismo del mismo Petite Vue) para incorporar estas características:

    • Atributos de carga parcial de contenido html. Se trata de los atributos v-get, v-post y v-target que contienen una URL y un selector CSS de elemento/s destino. La idea es que aplicados a un elemento FORM o BUTTON, capture el evento submit o click respectivamente para, mediante fetch, realizar una petición de contenido HTML que se asignará al elemento indicado.

    • Atributos de carga parcial de contenido html. Se trata de los atributos v-get-json, v-post-json, v-template y v-target que contienen una URL, un selector CSS para especificar un template y un selector CSS de elemento/s destino. La idea es que aplicados a un elemento FORM o BUTTON, capture el evento submit o click respectivamente para, mediante fetch, realizar una petición de contenido JSON que se aplicará al template Vue para generar un contenido HTML que se asignará al elemento indicado.

    • Autocarga de componentes web. PetiteVue no tiene componentes SFC, pero esto, en lugar de ser una limitación, la he convertido en una oportunidad. Mi idea es que al cargar la librería se compruebe si existen componentes web en el código HTML. De ser así, se intentará buscar un archivo con el mismo nombre que la etiqueta del componente y extensión html (por ejemplo click-counter.html). Si se encuentra, se asume que el archivo tiene tres etiquetas definidas: template con código HTML, script con código Javascript y style con código CSS. Se creará un web component a partir de esa definición. Si no encuentra el html, intentará la carga de un archivo js estándar con la definición del web component. Ya será responsabilidad del código registrar el componente. Se usa LocalStorage para guardar la ubicación del archivo cargado la primera vez, para evitar intentos de carga fallidos posteriormente. La idea de esta características es dar una versatilidad enorme a la hora de trabajar con componentes de frontend. Es posible que agregue la posibilidad de permitir cargar también ambos archivos (html o js) desde un repositorio centralizado para todas las aplicaciones.

    • Si no se especifica un atributo v-scope (inicialización de PetiteVue), uso el tag <body> para aplicar la funcionalidad de PetiteVue a toda la página.


      De nuevo, tengo pendientes distintas opciones:

      • Agregar la posibilidad de usar todos los atributos de vue con el prefijo "-". Por ejemplo, v-scope sería equivalente a -scope, y lo mismo con: -if -else -else-if -model -on-click -on-submit -on-mounted -class ...




    Código disponible en github

04 mayo, 2023

Un bookmarklet para integrar imágenes en un único archivo HTML

Tengo por costumbre leer libros en formato EPUB con la app Moon+ reader, tanto en el móvil (amoled con fondo negro y filtro de pantalla antireflejos) como en mi ebook de 10". Sin embargo, en ocasiones el libro que quiero leer no está disponible ese formato. Un caso que ya me ha ocurrido en ocasiones es que el libro esté online para ser leido en el navegador, como éste en el que se han preocupado incluso de dejarlo en un único archivo. Existen conversores online que reciben un único archivo HTML y genera un EPUB, como éste o éste. Sin embargo, las imágenes enlazadas no se convierten, por lo que el libro generado no está completo.

 He pensado en incrustar las imágenes enlazadas en el propio documento HTML convirtiéndolas a DATA-URIs. Para ello he creado el siguiente bookmarklet: imgs2dataurl (puedes arrastrarlo a la barra de marcadores para poder ejecutarlo sobre cualquier página que tengas cargada). Al acabar el proceso, si guardas la página como archivo HTML único, las imágenes se guardarán también en el mismo archivo, lo que permite que sean adjuntadas al convertirlo a EPUB.

javascript: (async function(){
  [...document.querySelectorAll('[loading=lazy]')].forEach(a=>a.removeAttribute('loading'));
  const imgs=[...document.querySelectorAll("img[src$='.jpg'],img[src$='.png']")];
  imgs.forEach(i=>{
      if (i.src.startsWith("data:")) return;
    const canvas=document.createElement("canvas");
    canvas.width=i.naturalWidth;
    canvas.height=i.naturalHeight;
    const ctx=canvas.getContext("2d");
    ctx.drawImage(i,0,0);
    const src= canvas.toDataURL();
    if (src!='data:,') i.src= src; else console.log(i.naturalWidth,i.naturalHeight,canvas.width);
  });
  const svgs= [...document.querySelectorAll("img[src$='.svg']")];
  svgs.forEach(async i=>{
    const t= await fetch(i.src);
    const text= await t.text();
    i.src='data:image/svg+xml,'+encodeURIComponent(text)
  });
})();

El código tiene tres partes. La primera se asegura de cargar todas las imágenes que especifiquen una carga lazy, o sea que se vayan cargando conforme se visualizan en el navegador. Así me aseguro de que todas las imágenes estén cargadas. De lo contrario la conversión a Data-uris fallaría al no tener imagen disponible.

A continuación realizo la conversión de las imágenes PNG y JPG usando un elemento canvas que incluye una función toDataURL para hacer la conversión directa. El resultado de esa función se guarda directamente en el img.src sustituyendo efectivamente la referencia a la imagen a cargar por la misma imagen ya codificada. Y por último hago el mismo proceso con los SVG.

Enjoy!

30 septiembre, 2021

Introducción a Petite Vue

 Hace tiempo que no publico en este blog. Aprovecho para crear una pequeña introducción a la nueva librería Petite Vue del mismo autor de Vue pero de tamaño mucho más reducido (6kB).


Veremos qué hace, cómo funciona, cómo compararlo con Vue y con Alpine.js y cómo empezar a usarlo.

petite-vue está optimizado para pequeñas interacciones sobre páginas HTML existentes generadas por un una plataforma de servidor, y con ello simplificando la mejora progresiva.

Características fundamentales

No necesita proceso de build

Puedes incluir simplemente petite-vue en una etiqueta script para conseguir sus ventajas en una página HTML:

<script src="https://unpkg.com/petite-vue" defer init></script>

<!-- en cualquier lugar de la página: -->
<div v-scope="{ count: 0 }">
  {{ count }}
  <button @click="count++">inc</button>
</div>

Pequeño tamaño

Las últimas versiones de Vue y de Alpine ocupan 22.9kB y 9.9kB minificadas y comprimidas lo que contrasta con los 6.4kB de petite-vue.

Sintaxis de templates compatible con Vue

Un desarrollador familiarizado con la sintaxis de templates de Vue encontrará muy fácil moverse entre Vue y petite-vue.

Como subconjunto de Vue, petite-vue usa la mayoría de la sintaxis familiar de Vue. Por ejemplo, petite-vue usa interpolación de templates del tipo {{cuenta}} y escuchadores de eventos de templates como @click.

Sin DOM virtual

A diferencia de Vue, React y de la mayoría de otras librerías y plataformas de frontend, petite-vue no usa virtual DOM. En su lugar, muta el DOM in situ. Como resultado, petite-vue no necesita un compilador decrementando su tamaño.

Dirigido por @vue/reactivity

El paquete @vue/reactivity es el responsable de la reactividad de ambos, Vue y Alpine. petite-vue usa la misma técnica de reactividad.

Cómo se compara petite-vue con Vue estándar

petite-vue es similar a Vue en muchas formas. Como se ha mencionado, ofrece la misma sintaxis de templates y modelo @vue/reactivity provisto por Vue estándar. Pero la diferencia más significativa es que petite-vue se ha creado para la mejora progresiva.

Vue estándar se diseñó para usar un paso de generación o build para crear aplicaciones de una página (SPAs) con fuertes interacciones. Utiliza funciones de renderizado que reemplazan los templates existentes en DOM. petite-vue, por otro lado, recorre el DOM existente y lo muta in situ, por lo que no requiere de un paso de generación.

Características exclusivas de petite-vue 

petite-vue introduce algunas características que no están disponibles en el Vue estándar y que ayudan a optimizar la mejora progresiva:

v-scope

En petite-vue, v-scope es una directiva que marca la región de la página controlada por petite-vue. También puedes usar la directiva v-scope para pasar estados a los que una región particular de la página tendrá acceso.

v-effect

v-effect es una directiva usada para ejecutar declaraciones reactivas inline con petite-vue. En  el código siguiente, la variable cuenta es reactiva , así que el v-effect se reejecutará cuando cambie la cuenta, entonces actualizar el div con el valor actual de cuenta

<div v-scope="{ count: 0 }">
  <div v-effect="$el.textContent = count"></div>
  <button @click="count++">++</button>
</div>

Eventos de ciclo de vida

petite-vue viene con dos eventos de ciclo de vida, @mounted y @unmounted que te permiten escuchar cuando petite-vue se monta o desmonta en la página.

Características compatibles con Vue

Ahora que hemos visto las nuevas características que petite-vue trae, veamos las que ya existían en Vue:

  • {{ }}: vinculaciones de texto
  • v-bind y : : manejo especial de estilos y clases
  • v-on y @ : manejo de eventos
  • v-model: representa todos los tipos de entradas y vinculaciones :value no cadenas
  • v-if/ v-else / v-else-if
  • v-for
  • v-show
  • v-hmtl
  • v-pre
  • v-once
  • v-cloak
  • reactive()
  • nextTick()
  • Template refs

Características exclusivas de Vue

Dado su ámbito reducido, petite-vue ha descartado algunas de las características de Vue estándar:

  • ref() ycomputed()
  • Funciones de renderizado: petite-vue no tiene virtual DOM
  • Reactividad para tipos de colección: Map, Set, etc.
  • Componentes Transition, keep-alive, <teleport>, y <suspense
  • v-for: desestructuración profunda
  • v-on="object"
  • v-is y <component :is="newComponent">
  • v-bind:style auto-prefijado

 

Cómo se compara petite-vue con Alpine

Aunque petite-vue está inspirado por Alpine y resuelve problemas similares, difiere de Alpine debido a su minimalismo y a su compatibilidad con Vue.

petite-vue ocupa unos dos tercios del tamaño de Alpine. A diferencia de Alpine, no trae un sistema de transiciones.

Alpine y petite-vue tienen diferentes diseños. Aunque Alpine se parece a la estructura de Vue de alguna forma, peite-vue está más alineado con el Vue estándar, minimizando el coste den cambios que tendrás si quieres pasar entre petite-vue y Vue.

Empezar con petite-vue

Para empezar con petite-vue, necesitas incluir una etiqueta script que apunte al paquete de petite-vue. Creemos una simple aplicación de votación impulsada por petite-vue.

Primero creemos un archivo index.html. En su cuerpo, añade el siguiente código:

 <script src="https://unpkg.com/petite-vue" defer init></script>
  <div v-scope="{ votosPositivos: 0, votosNegativos: 0 }">
    <p>
      {{ votosPositivos }} <button @click="votosPositivos++">&#128077;</button>
    </p>
    <p>
      {{ votosNegativos }} <button @click="votosNegativos++">&#128078;</button>
    </p>
  </div>

 El atributo defer de la etiqueta script hace que la carga del código de petite-vue se posponga hasta que el contenido HTML se haya parseado por el navegador.

El atributo init le dice a petite-vue que automáticamente consulte e inicialice todos los elementos que tengan v-scope.

El v-scope le dice a petite-vue que región de la página gestionar. También pasamos los estados votosPositivos y votosNegativos para que estén disponibles en esa región.

Inicialización manual

Si no quieres que petite-vue inicialice automáticamente todos los elementos que tengan el atributo v-scope, puedes inicializarlos manualmente cambiando el script:

<script src="https://unpkg.com/petite-vue"></script>
<script>
  PetiteVue.createApp().mount()
</script>

 Alternativamente puedes usar la distribución de petite-vue compatible con módulos ES.

<script type="module">
  import { createApp } from 'https://unpkg.com/petite-vue?module'
  createApp().mount()
</script>

URL de CDN de producción de petite-vue

Estamos accediendo a petite-vue mediante una URL de CDN (red de distribución de contenidos). Estamos usando una URL reducida https://unpkg.com/petite-vue que es perfecta para prototipos pero no para producción. Queremos evitar costes de resolución y de redirección, así que usaremos las URLs completas.

La URL de distribución en producción global https://unpkg.com/petite-vue@0.2.2/dist/petite-vue.iife.js expone PetiteVue global y también soporta autoinicialización.

La URL de distribución en producción ESM (módulos) https://unpkg.com/petite-vue@0.2.2/dist/petite-vue.es.js debe usarse en un bloque <script type="module">.

Cuándo usar petite-vue

Hemos aprendido las características de petite-vue y lo que puede hacer. Veamos las mejores situaciones para las que petite-vue está diseñado:

  • Prototipado rápido cuando no necesitas una herramienta de generación/build
  • Agregar funcionalidad Vue en plataformas de servidor como Sails, Laravel o Rails
  • Construir páginas landing o de marketing que sean estáticas con pocas interacciones
  • En cualquier lugar donde usarías Alpine normalmente

Conclusión

 peitet-vue es una versión más ligera de Vue que añade interaciones eficientes a las páginas. petite-vue es aún nuevo (2021) e incluye un aviso sobre errores potenciales. En cualquier caso, petite-vue ya es una opción funcional con una utilidad y potencial fuertes. Es especialmente útil para el prototipado rápido, rociando funcionalidad Vue en plataformas de servidor, y construyendo páginas estáticas.

04 enero, 2017

Aprender Javascript en 2016

http://www.etnassoft.com/2017/01/03/aprender-javascript-en-2016/

Interesante traducción de un artículo que da justo en el clavo. Programar en Javascript actualmente siguiendo las tendencias del mercado implica aprender una cantidad absurda de tecnologías. Simplemente recordar el principio KISS (Keep It Simple, Stupid), algo así como "Hazlo fácil, estúpido"... Complicarse la vida por intentar seguir las tendencias nunca ha sido tan fácil...

08 enero, 2013

Couchbase Server

Acaba de ser publicado para varias plataformas la versión 2.0 del Couchbase Server. Una interesante base de datos que sigue el paradigma NoSQL que se basa en un modelo flexible sobre JSON que facilitar modificar aplicaciones sin las restricciones de tener esquemas de datos fijos. Además posee un rendimiento elevado gracias a liberarse de las restricciones propias de las bases de datos relacionales, lo que ofrece una velocidad muy elevada en comparación con aquellas. Y otra ventaja es la escalabilidad, ya que la tecnología se ha diseñado con la idea de soportar cambios de topología sin necesidad de detener el servicio en ningún momento.

24 octubre, 2012

plv8js es un añadido de lenguaje procedural para PostgreSQL, lo que significa que puedes definir funciones JavaScript que se ejecutan en un servidor PostgreSQL usando el google V8 Engine. Una pareja perfecta para los recientes campos JSON de PostgreSQL.

15 octubre, 2012

QuirksBlog ha publicado los resultados de una encuesta sobre uso de librería en Javascript, según el cuál, el 80% de desarrolladores usa frecuentemente jQuery, el 35% Modernizr, el 16% Underscore, y el 11% Backbone

18 julio, 2012

Este artículo en inglés explica magistralmente la necesidad de mantener actualizado el navegador de Internet. Una referencia interesante orientada a realizar una labor pedagógica hacia los usuarios de productos basados en la web.

31 mayo, 2012

Stabilizing Couchbase Server 2.0. Sólo notificar el avance en el desarrollo de una nueva base de datos de documentos distribuida, fiable, eficiente y de alto rendimiento. Está basada en el conocido proyecto de "NoSQL" CouchDB...

14 mayo, 2012

Firebase es un servicio/nube que automáticamente sincroniza datos entre tus clientes y sus servidores. Libera a los desarrolladores de preocuparse por cómo sus datos serán transmitidos y almacenados, y les permite centrarse en la lógica de la aplicación. Las aplicaciones que usan Firebase pueden escribirse enteramente con Javascript en el navegador, actualizarse directamente en tiempo real, son inherentemente escalables e interoperan con servicios existentes.

Dataset es una librería JS de gestión y transformación de datos en el lado del cliente. Dataset facilita la gestión de datos en el lado del cliente gestionando la carga, proceso, ordenación, consulta y manipulación de datos desde todo tipo de fuentes. http://misoproject.com/dataset/

25 abril, 2012

Gracias a un mensaje de Google al respecto de Codejam, descubro ideone.com, un IDE (editor) para programar online con prácticamente cualquier lenguaje. Interesante.



Últimos links en indiza.com