TDD WordPress Theme

Tanto al comenzar a desarrollar un tema desde cero como cuando ya adquirió mayor complejidad tenemos que hacer una serie de chequeos de forma manual para asegurarnos que no nos olvidamos de incluir ninguna funcionalidad o no quedó código muerto.

Obviamente una buena checklist y una organización bien planificada de nuestro código simplifica mucho el tema pero siempre es menester automatizar este tipo de tareas.

WordPress provee una Test Suit pero está orientada principalmente al testeo del funcionamiento de WordPress más que a las cuestiones atinentes al desarrollo de themes.

Si bien puede ser utilizada como base para testear nuestros temas, a mi entender, agrega demasiado overhead para lo que nos aporta. Partiendo de este presupuesto decidí comenzar a desarrollar algunos tests propios con PHPUnit.

Primeramente, basado en el checklist que antes mencioné, definí una serie de chequeos que me interesa automatizar:

  • Existe los archivos functions.php, style.css y el screenshot
  • No hay archivos sin usar
  • Existe el template correspondiente a los template tags usados. Por ej.: get_header y header.php
  • Todas las uris locales son relativas
  • Todos los scripts/styles registrados son usados y existen
  • Hay sidebars y todas son usadas en algún lado
  • Todos los espacios de menu son usados en algún momento
  • Las páginas/artículos y el sitio tienen metadatos
  • El favicon está definido y la ruta existe
  • El theme no tiene que tomar funciones de plugin:
    • Dashboard widgets
    • Custom Post Types
    • Custom Taxonomies
    • Shortcodes
    • Metaboxes
    • Social integrations
    • Bloques de Gutenberg
  • La paginación respeta la configuración de Ajustes > Lectura
  • Customizer sanitization
  • Scripts/styles externos cargados de forma agnóstica
  • No se usa @import en los stylesheet
  • Soporte para title tag
  • Uso de $content_width
  • CSS básicos definidos:
    • alignleft, aligncenter, alignright,
    • wp-caption,
    • size-full, size-large, size-medium, size-thumbnail
  • Uso de WP_Filesystem en vez de las PHP File Functions: mkdir, fopen, fread, fwrite, fputs
  • Sin estilos o scripts hardcodeados
  • Scripts cargados únicamente en footer
  • i18n

A primera vista, algunos son sencillos como la verificación de la existencia de archivos, otros van a requerir análisis del código y algunos que podrían ser verificados utilizando la API que trae WordPress contra un servidor de desarrollo o staging.

Por ahora voy a ir haciendo el desarrollo y pruebas contra la copia de desarrollo del theme de un sitio que está en producción para tener un objetivo más real aunque la idea sería, a largo plazo, constituir un repositorio agnóstico que pueda ser rápidamente clonado en nuestro proyecto.

La primera aproximación quedaría con la siguiente estructura dentro de mi workflow habitual:

  • tests/bootstrap.php
  • tests/FaviconTest.php
  • tests/MissingFilesTest.php
  • tests/StylesheetTest.php
  • composer.json
  • phpunit.xml

Y el código propiamente de cada archivo sería:

Próximamente estaré publicando más novedades.

Recursos útiles para el desarrollo web

Serie de herramientas y bibliotecas online que son útiles durante el proceso de desarrollo web.

Google Analytics

Podemos usar otro pero definitivamente este es el gestor de estadísticas gratuito más completo. Información en tiempo real e histórica sobre los horarios de acceso, distribución geográfica, origen, étc. Y, más interesante al momento de pensar la, datos sobre el tipo de dispositivos usados para conectarse (mobile, tablet o desktop), el navegador o la resolución de pantalla. Por último, si tenés instalado el plugin (ahora sin soporte pero todavía funcional) podés incluso analizar el click heatmap de tu sitio.

Can I Use

Una vez que ya sabemos todo sobre los dispositivos que los usuarios van a usar para conectarse al sitio, es momento de empezar a delimitar las tecnologías que vamos a tener disponibles. En 10 años Can I Use se ha vuelto el standard de facto para obtener este tipo de información. Alcanza con ingresar en el buscador el nombre de la tecnología, un comando o etiqueta relacionado y tendremos información sobre el soporte por versión en los distintos navegadores, el uso relativo de los mismos y notas al pie sobre cosas a tener en cuenta.

MDN Web Docs

Definidas las tecnologías empieza el momento de picar código y comienzan, también las dudas que si un checkbox lanza un evento click o un change, que cual era el comando para agregar un elemento al principio de un Array, étc. Mientras más tecnologías están involucradas en el desarrollo web más difícil es acordarse al dedillo de todo y ahí entra a jugar MDN Web Docs una de las referencias más completas en el tema.

CSS-Tricks

Si bien MDN Web Docs tiene un apartado para CSS, la autoridad en el tema es de CSS-Tricks. Desde snippets a, como su nombre lo indica, trucos y consejos relacionados con el uso de los estilos y una galería de ejemplos de los más interesantes.

CSS Triggers

Una vez que ya empieza a tomar forma el sitio, empezamos a agregar más interactividad al mismo mediante la captura de eventos y manipulación del DOM. Estas modificaciones del modelos de objetos repercuten directamente en el CSSOM y pueden implicar un reflow, un repaint o un relayout cada uno con sus respectivas demoras. Cuando necesitamos saber si es mejor usar transform o modificar directamente el width de un objeto ahí entra CSS Triggers para no equivocarse.

Regular Expressions 101

Hablando de tópicos donde hace falta un ayuda memoria, llegamos a las expresiones regulares. Karma de muchos programadores, Regular Expressions 101 trae tanto una referencia como un entorno de pruebas para poder generar ese pattern perfecto.

Debuggex

Ya ni nos acordamos que matchea ese pattern que tenemos delante o estamos leyendo código de otra persona y vamos medio perdidos nada mejor que Debuggex, un tester visual que nos permite ver de qué va la cosa.

CSS Stats

Recién habíamos terminado de armar todo cuando el cliente nos pidió «unos pocos cambios, algo acá y allá» o llevamos tiempo en producción y hasta el hijo de la vecina, «que sabe un poco de computadoras», metió mano. Por lo que sea terminamos con 402 colores únicos o 12k de reglas y no lo sabemos. Llevar un buen control sobre la situación de nuestros archivos de estilo es una tarea que se ve tremendamente simplicada con CSS Stats.

Pingdom

Siguiendo en la línea de controlar la performance del sitio, entramos en el área de las herramientas más generales como Pingdom que nos provee rápidamente con un resumen de la situación general. Vistoso por demás, también resulta útil para «robar» algunos gráficos para presentar al cliente.

WebPagetest

Lo que Pingdom tiene de bonito, WebPagetest lo tiene de detallado. Nunca lo vamos a usar como imagen en un Power Point pero si necesitamos estadísticas precisas para ver dónde está el cuello de botella en la carga o qué script está consumiendo demasiado tiempo este sitio nos va a permitir encontrar la información que buscamos.

Patrón de Inyección de Dependencias

La idea detrás del patrón de inyección de dependencias es lograr una mayor separación de las responsabilidades de nuestro código cambiando la forma en que se manejan las dependencias entre objetos.

Un forma común de establecer esta dependencia es codificar dentro de una clase, normalmente en el constructor, la creación de la instancia de un objeto que es necesario para su funcionamiento.

 
public class Dependencia { ... }

public class Clase {
	private Dependencia depend;
	
	public Clase() {
		depend = new Dependencia();
	}
	
	public void funcion() {
		depend.doSomething();
	}
}

class Program {
	public static void main(String [ ] args) {
		Clase miclase = new Clase();
		miclase.funcion();
	}
}

Este tipo de dependencia suele ser denominado fuerte porque una clase depende directamente de la otra para funcionar. Cualquier cambio en la clase de la que se depende obliga a la realización de cambios en la clase dependiente generando, en la práctica, una subordinación entre una y otra.

Como podemos suponer, al ser dos clases diferentes, las responsabilidades de cada una son distintas y no deberían estar tan ligadas porque dificulta el mantenimiento, la extensión del código y la escalabilidad del software. Aquí es donde entra la inyección de dependencias.

Para lograr una mayor separación de responsabilidades, este patrón recomienda la utilización de interfaces o clases abstractas para establecer las dependencias y, por lo tanto, el uso de objetos ya creados en vez de instanciarlos internamente.

En la práctica, cada clase debe indicar el «tipo» de objeto que necesita para trabajar y es otra parte del código la que se debe encargar de implementar la clase en función de la interfaz, crearlo y pasarlo al objeto.

 
public interface iDependencia { ... }

public class MiDependencia : iDependencia { ... }

public class Clase {
	private iDependencia Depend;
	
	public Clase(iDependencia d) {
		Depend = d;
	}
	
	public void funcion() {
		Depend.doSomething();
	}
}

class Program {
	static void main() {
		MiDependencia dependencia1 = new MiDependencia();
		Clase miclase = new Clase(dependencia1);
		miclase.funcion();
	}
}

Como se puede observar ya no existe una relación directa entre ambas clases. Nuestra clase indica que necesita un objeto creado a partir de una clase que implemente la interfaz pero no específica qué clase. Este tipo de separación permite cambiar rápidamente la clase que pasamos como dependencia e incluso trabajar con varias clases.

Un ejemplo muy extendido de este tipo de diseño es el que utilizan los frameworks que se comunican con bases de datos. Estos establecen una interfaz con este fin y una o dos clases que la implementen para distintas BBDD dando la libertad al programador de crear una nueva si necesita trabajar con otra o instanciarlas varias veces si necesita más de una conexión.

Como toda solución tiene sus pros y sus cons. Como otra ventaja aparte de las mencionadas, al estar más modularizado el código, es más fácil realizar test unitarios.

Como contra la necesidad de que el lenguaje soporte interfaces o clases abstractas y la necesidad de escribir más código para implementar el diseño. También, a consecuencia de agregar un grado de separación entre la dependencia y la clase, se puede dificultar la depuración en caso de surgir errores de integración.

Patrón de Carga Diferida: Value Holder

Esta entrada es la parte 4 de 4 en la serie Patrón de Carga Diferida

El patrón de carga diferida es una técnica para mejorar la performance utilizada en casos en que nos encontramos con que no todas las características de un programa son usadas durante su ejecución o para distribuir los tiempos de carga, cuando estos son muy grandes, durante la ejecución para poder mejorar los tiempos de respuesta al iniciar.

Hay cuatro formas comunes de implementar la carga diferida: inicialización diferida, proxy virtual, fantasma y value holder.

Value Holder

Esta técnica implica la utilización de un objeto cuyo tiempo de carga es menor y que reemplaza al objeto original hasta que sea necesario su carga.

Por ejemplo, la utilización de una imagen con pocos colores y de muy  pequeño tamaño (un logo en escala de grises) que se utiliza en los sitios webs con muchas fotos en vez de la foto original hasta que esta es cargada.

Es necesario que el value holder esté optimizado al máximo, si termina siendo menos o igual de eficiente que cargar el objeto original el método pierde sentido.

<img class="lazy" src="placeholder-image.jpg" data-src="image-to-lazy-load-1x.jpg" data-srcset="image-to-lazy-load-2x.jpg 2x, image-to-lazy-load-1x.jpg 1x" alt="I'm an image!">
document.addEventListener("DOMContentLoaded", function() {
  var lazyImages = [].slice.call(document.querySelectorAll("img.lazy"));

  if ("IntersectionObserver" in window) {
    let lazyImageObserver = new IntersectionObserver(function(entries, observer) {
      entries.forEach(function(entry) {
        if (entry.isIntersecting) {
          let lazyImage = entry.target;
          lazyImage.src = lazyImage.dataset.src;
          lazyImage.srcset = lazyImage.dataset.srcset;
          lazyImage.classList.remove("lazy");
          lazyImageObserver.unobserve(lazyImage);
        }
      });
    });

    lazyImages.forEach(function(lazyImage) {
      lazyImageObserver.observe(lazyImage);
    });
  }
});

Fuente:  Lazy Loading Images and Video

Patrón de Carga Diferida: Fantasma

Esta entrada es la parte 3 de 4 en la serie Patrón de Carga Diferida

El patrón de carga diferida es una técnica para mejorar la performance utilizada en casos en que nos encontramos con que no todas las características de un programa son usadas durante su ejecución o para distribuir los tiempos de carga, cuando estos son muy grandes, durante la ejecución para poder mejorar los tiempos de respuesta al iniciar.

Hay cuatro formas comunes de implementar la carga diferida: inicialización diferida, proxy virtual, fantasma y value holder.

Ghost

Esta técnica implica la creación del recurso pero con una cantidad mínima de información y diferir el resto de la carga hasta que sea necesario.

Por ejemplo, al presentar un listado inicializamos el objeto con la información mínima para mostrar (por ejemplo un título o una descripción) y al hacer click en un ítem recién cargamos el resto del objeto para poder acceder a la información completa.

Algunas consideraciones que es necesario tener en cuenta al utilizar este patrón son:

  • Como la carga se realiza en dos tramos, puede que cuando hagamos la carga inicial el objeto esté disponible y cuando querramos obtener el resto de la información no.
  • Para que tenga sentido usar este patrón la carga mínima tiene que ser considerablemente más chica que la carga total del recurso sino el overhead por realizar dos accesos puede terminar haciendo todo el proceso menos eficiente.
function Documento(id) {
  var self = this;

  $.get('/get_preview/' + id, function (response) {
    self.title = response.title;
    self.path = response.path;
  });

  function mostrar_documento() {
    $.get('/get/' + id, function (response) {
      self.data = response.data;
      console.log(self.data);
    });
  }
}

var documentos = [
  new Documento(1),
  new Documento(3),
  new Documento(4),
];

documentos.forEach( function(e) {
  $('body').append( function(html, index) {
    var $item = $('<p><strong>' + e.id + ': ' + e.title + '</strong>');
    $item.click( this.mostrar_documento() );
  });
});

Patrón de Carga Diferida: Proxy Virtual

Esta entrada es la parte 2 de 4 en la serie Patrón de Carga Diferida

El patrón de carga diferida es una técnica para mejorar la performance utilizada en casos en que nos encontramos con que no todas las características de un programa son usadas durante su ejecución o para distribuir los tiempos de carga, cuando estos son muy grandes, durante la ejecución para poder mejorar los tiempos de respuesta al iniciar.

Hay cuatro formas comunes de implementar la carga diferida: inicialización diferida, proxy virtual, fantasma y value holder.

Proxy virtual

Esta técnica implica la creación de un contenedor con la misma interfaz que el recurso original que carga el mismo cuando se realiza el primer llamado a uno de sus métodos.

Por ejemplo, una galería con cientos de imágenes que crea los objetos que representan todas las imágenes al iniciar el programa pero estos recién leerán la imagen del disco cuando tengan que dibujarla.

Algunas consideraciones que es necesario tener en cuenta al utilizar este patrón son:

  • Dependiendo del lenguaje de implementación y el tamaño de la clase puede terminar siendo un código engorroso y repetitivo con un montón de métodos públicos que únicamente llamen al loader.
  • La carga del recurso es transparente. A los efectos del resto del programa se hace al momento de crear el objeto.
  • Como en cualquier otro método de carga diferida, nos podemos encontrar conque el recurso no existe o no está accesible. Además, si toma mucho tiempo cargarlo, la ejecución del programa se puede ver interrumpida.
public class Image {

  public Image(filename) {
    /* Ahora cargaría la imagen desde el disco */
  }
  
  public void draw() { /***/ }
}

public class ImageProxy {
  private String filename;
  private Image image;

  public ImageProxy( filename ) {
    this.filename = filename;
  }

  public void draw() {
    if (this->image == null) {
      this.image = new Image(this.filename)
    }

    this.image.draw();
  }
}

 

Workflow en WordPress

La estructura de directorios que uso en mi workflow se aprovecha de una feature de WordPress que permite distribuir grupos de themes. La idea es que en vez de almacenar un theme por directorio se puedan poner varios usando subdirectorios.

El funcionamiento de esta feature es bastante sencillo, cuando WP lista las carpetas en wp-content/themes para ver que themes hay instalados, revisa si en la raíz  de las mismas está el archivo style.css; si no lo encuentra busca en los subdirectorios de primer nivel de la misma por si es un grupo de themes.

La idea entonces es que en nuestro entorno de desarrollo ese subdirectorio, que WP va a identificar como el que contiene el theme, sea el de las builds y que cualquier otro subdirectorio de primer nivel no tenga un archivo style.css.

  • build
  • dist/theme
  • src
    • images
    • languages
    • scripts
      • admin
      • customize
      • modules
    • styles
    • templates
      • admin
      • customize
      • modules
    • vendor
  • tasks

Build y Dist

Como se deduce de sus nombre, build, contiene las builds con todo el código sin comprimir y preparado para debug mientras que en dist se guarda el theme listo para producción.

Para evitar confundir a WP, es importante que en la raiz del directorio dist no se encuentre el style.css sino en un subdirectorio de este. Esto, además, nos simplifica el proceso de crear el zip / tar.gz para distribuir porque ya nos queda todo en un directorio listo para comprimir.

Src

Los «fuentes» del theme en el que estamos trabajando: php, imágenes y scss sin procesar, archivos de traducción, étc. Casi todo lo que está en este directorio va a ser procesado, optimizado, comprimido y demás operaciones que van a dar como resultado nuestro theme.

La idea original del directorio images era, justamente, guardar imágenes que se usaran en el diseño del theme pero, hoy en día, con los avances de CSS únicamente almacena el screenshot.

La carpeta templates contiene todo el código php, el ordenamiento interno tiene que cumplir con las limitaciones y recomendaciones de cualquier theme.

Me gusta mucho trabajar con el customizer así que suelo generar bastante código para este y, por lo tanto, tanto en la carpeta de scripts como de templates lo guardo en un directorio separado.

Además, a lo largo del tiempo, he creado algunos snipets de código que utilizo tan habitualmente como preload y lazyload, google fonts, étc que los mantego de forma separada y que cuando los incluyo en un proyecto los guardo en la carpeta modules.

Por una cuestión de separación también guardo en otro directorio, admin, todo el código que está pensado únicamente para ejecutarse en el dashboard de WP.

Por último, como no siempre hay paquetes con la librería que necesito, la versión que busco o no quiero usar un CDN, o por lo que sea, tengo el directorio vendor que se copia íntegramente tanto a las build como a la dist.

Tasks

Las tasks de Gulp.js: build, clean, clean-vendor, images, scripts, styles, templates, translation, vendor, watch.

Mantengo separadas las partes de código propio del código de vendor: build y clean para uno y vendor y clean-vendor para el otro. Ídem con las traducciones: translation.

Normalmente, build se encarga tanto de generar tanto la versión de pruebas como la versión para distribución. Antes las generaba con tareas específicas pero siempre se terminaban dando dos escenarios: dos tareas muy parecidas que había que mantener sincronizadas manualmente o una tarea con un montón de código duplicado y un enredo de condicionales.

La más importante de las tareas es watch que monitorea los cambios y actualiza la build. Para esto utilizo el modulo que trae Gulp por defecto en conjunto con BrowserSync para la inyección del nuevo CSS pero sin actualización automática. Pero, cuando trabajo sobre el customizer, desactivo completamente BS porque no se llevan bien y se suele quedar con pantallas en blanco u errores.

Gists

WP_Deduplicator: Clase estática que permite llevar el control de los post mostrados para evitar duplicar el contenido.

WP_Keepalive: Función que agrega o modifica la cabecera Connection para informar al cliente que es posible reutilizar la conexión abierta ahorrándonos establecer una conexión nueva para cada archivo.

WP_Clipboard: Función que usa una lista blanca para filtrar las etiquetas HTML del texto que se pega en el editor.

Patrón de Carga Diferida: Inicialización diferida

Esta entrada es la parte 1 de 4 en la serie Patrón de Carga Diferida

El patrón de carga diferida es una técnica para mejorar la performance utilizada en casos en que nos encontramos con que no todas las características de un programa son usadas durante su ejecución o para distribuir los tiempos de carga, cuando estos son muy grandes, durante la ejecución para poder mejorar los tiempos de respuesta al iniciar.

Hay cuatro formas comunes de implementar la carga diferida: inicialización diferida, proxy virtual, fantasma y value holder.

Inicialización diferida

Esta técnica implica la inicialización del contenedor del recurso como nulo, o equivalente en el lenguaje, y recién realizar la carga en el momento de su primer uso.

Por ejemplo, establecer el identificador de la conexión al servidor de bases de datos como null y al momento de realizar una consulta, verificar si la conexión está inicializada o hace falta establecerla.

Algunas consideraciones que es necesario tener en cuenta al utilizar este patrón son:

  • Es necesario que cada vez que vayamos a usar el recurso del que estamos difiriendo su inicialización verifiquemos que haya sido cargado. Si bien a nivel performance no debería influir, si puede ser fuente de otros errores por olvido o por tener código duplicado.
  • Como en cualquier otro método de carga diferida, nos podemos encontrar conque el recurso no existe o no está accesible. Además, si toma mucho tiempo cargarlo, la ejecución del programa se puede ver interrumpida.
<?php

class BBDD {
  private $user;
  private $pass;
  private $host;
  private $name;

  private $connection;

  function __construct( $user, $pass, $host, $name ) {
    $this->user = $user;
    $this->pass = $pass;
    $this->host = $host;
    $this->name = $name;

    $this->connection = null;
  }

  function conectar() { /***/ }

  function query( $sql ) {
    if ($this->connection == null)
      $this->conectar();

    /***/
  }
}