Diseño basado en componentes usando Single Directory Components (SDC) en Drupal
En esta guía explicaremos qué es y como funciona SDC, en qué te puede beneficiar, cómo se integra con otras herramientas internas y externas, cuál es su potencial y en qué modo podría incluso revolucionar el modo en el que trabajamos el front-end de Drupal.
Las versiones más recientes del core de Drupal 10 contienen el módulo experimental SDC (Single Directory Components). Este módulo quiere acercar el desarrollo front-end en Drupal al diseño basado en componentes, un paradigma que en los últimos años ha tomado gran fuerza (React, Vue, Web Components, Storybook, etc...) pero que no era fácil de implementar en versiones anteriores del core de Drupal.
En esta guía se basa en mi charla de la pasada DrupalCamp de Sevilla 2023. Si prefieres el formato audiovidual, puedes ver la presentación aquí.
1) El camino hacia SDC
Existen dos grandes presiones que han derivado en la creación de Single Directory Components (de ahora en adelante, SDC).
Por un lado, una presión externa: en los últimos años, el mundo que rodea a Drupal se ha movido claramente a favor del diseño basado en componentes. Todos conocemos frameworks como React, Vue, Svelte; pero también herramientas como Storybook, metodologías como Atomic Design o incluso venerables herramientas como Bootstrap han inaugurado o se han movido en esta dirección.
Por otro lado, existe una presión también interna. Como ya comenté en la charla que di el año 2022 en Zaragoza, desde hace un tiempo en Drupal existen varios módulos y temas contribuídos que apuntan de una manera u otra hacia hacia el diseño basado en componentes. Algunos de ellos son Single File Components, UI Patterns, Components!, Emulsify o Radix.
SDC viene a ser la solidificación y concreción de una respuesta a estas necesidades en una propuesta oficial del Core de Drupal.
Pero antes de conocer la respuesta a un problema hemos de preguntarnos: ¿cuál es el problema?
1.1) El problema del desarrollo front-end en Drupal
Aparte de la curva de aprendizaje elevada, un problema específico del front-end de Drupal es que los sistemas que interfieren en el renderizado de una página están muy distribuidos entre diferentes capas y lugares del código.
Al final del día, lo que un desarrollador front-end necesita saber es quién está interfiriendo en el HTML, CSS y JS que acaba en el navegador.
En Drupal, en muchas ocasiones, hay demasiadas capas interviniendo.
Esto dificulta el aprendizaje de los nuevos desarrolladores de Drupal y puede ser incluso complicado para perfiles más avanzados. Se producen con demasiada facilidad pérdidas de contexto, sobrescrituras indeseadas, acoplamientos.
Esta frustración resultará familiar a muchos desarrolladores de Drupal que se hayan enfrentado a un bug difícil de cazar: ¿Estará el problema en las plantillas? ¿Qué plantilla tiene precedencia? ¿Qué suggestions se están aplicando? ¿A nivel de módulo o de tema? ¿Hay una sobrescritura de librería en el info.yml
? ¿Quien declara la librería? ¿Y si hay un hook que la altera? ¿Cuántos hooks
están interviniendo, y en qué orden?
Todas las soluciones que preceden a SDC tienen un punto en común y es que intentan ordenar el desarrollo front-end en Drupal a partir de la abstracción de componentes.
El objetivo es que cada elemento de la interfaz de usuario potencialmente reutilizable e individualizable sea capaz de gestionarse a sí misma, esto es, gestionar su propio HTML, CSS y JS y ser invocada desde otros puntos del código.
De este modo se consigue ya un primer paso y nos acercamos al principio de responsabilidad única: todo lo que concierne a un componente dado debe de ser manejado en un mismo lugar. De este modo, cualquier cambio o actualización que se haga a nivel de componente debería de ser inmediatamente compartido por todas las instancias que hayan sido creadas a lo largo del proyecto:
Llegados a este punto, cualquier desarrolladora dirá: ¡pero esto ya se puede hacer en Drupal! Basta con usar cualquiera de los módulos citados anteriormente (Components!, por ejemplo). Incluso usando capacidades que ya están en Twig y en el Core de Drupal se podría conseguir.
Por tanto: ¿qué aporta SDC?
Vamos a intentar resolver la pregunta en tres grandes apartados:
- Cómo funciona SDC
- Qué problemas resuelve ya mismo
- Qué problemas ayudará a resolver en el futuro.
2) Cómo funciona SDC
Sólo necesitas usar Drupal Core en la versión 10.1.x, activar el módulo SDC (experimental) y crear una carpeta llamada components
dentro del módulo o tema donde quieras dar de alta un componente para comenzar a trabajar con SDC.
No es obligatorio pero es altamente recomendado instalar el paquete drupal/core-dev para beneficiarse de la validación de componentes. Desde el punto de vista de la DX (developer experience, experiencia del desarrollador), es casi imprescindible.
Dentro de la carpeta components
añadiremos un subdirectorio por cada componente. Por tanto, lo primero que podemos notar es que SDC opta por la estrategia de organizar los componentes en directorios, frente a otras estrategias como los Single File Componentes, también presentes en Drupal y popularizados por Vue.
2.1) SDC puede ser extremadamente simple...
De entrada, SDC es conceptualmente muy simple: todos los activos (assets) deben de llamarse igual y estar agrupados bajo un mismo directorio — el cual, por cierto, no es obligatorio que siga la misma convención.
Te estarás preguntando por ese misterioso myComponent.component.yml
. Este archivo (que más adelante) es de importancia capital ya que el mecanismo de auto-descubrimiento de componentes depende de él y además incluye muchas de las novedades de SDC.
De hecho, un componente podría perfectamente estar formado por un archivo *.twig
y un *.component.yml
, que son los únicos archivos requeridos.
Esta simple implementación ya nos permitirá acceder al componente con id mytheme:mycomponent
.
Los componentes se identifican no sólo por su nombre, sino también por el módulo o tema que los declara, es decir, namespace:component
. Más adelante veremos usos de esto, como la sobrescritura de componentes.
2.2) ...o tan complejo como lo queramos hacer
Del mismo modo, SDC puede ser tan complejo como queramos. La idea es que todo lo relacionado con el componente viva dentro de un mismo directorio: documentación, archivos de Storybook, imágenes, archivos .scss
o .ts
, dependencias en un package.json
...
Recuerda que los componentes se pueden declarar tanto a nivel de módulo como de tema.
Autogeneración de librerías
Sólo con usar SDC de esta manera ya hemos obtenido una ventaja: el CSS y JS del componente serán añadidos al tema automáticamente, sin necesidad de dar de alta una library
de Drupal.
Es decir, nos podemos desprender del attach_library('mytheme/mycomponent')
y del $variables['#attached']['library][]
. ¡Fantástico!
Esto tiene implicaciones positivas a nivel de rendimiento y mantenibilidad que veremos más adelante.
2.3) El archivo *.component.yml
Hasta aquí la única gran novedad es ese misterioso archivo mycomponent.component.yml
del cual todavía no sabemos nada.
La realidad es que una gran parte del poder de SDC reside en este archivo.
Pero, al mismo tiempo, y para facilitar la implementación de SDC, este archivo puede incorporarse de modo gradual a nuestros componentes. Vamos a ver las particularidades del *.component.yml
partiendo de su implementación mínima.
Un archivo vacío
# ¡No tengo nada que decir!
Para empezar, nuestro mycomponent.component.yml
puede estar vacío. De hecho, es la implementación mínima necesaria para garantizar que nuestro componente será descubierto y dado de alta en Drupal.
Esto es una gran ayuda a la hora de migrar a SDC: puedes empezar por una definición vacía e ir aumentándola poco a poco.
Identificación del componente
name: Navbar
status: experimental
group: Navigation
La información inicial acerca del componente son sus metadatos más básicos: nombre, estado y grupo. Más adelante veremos para qué se pueden usar estos datos.
Por cierto: la declaración del componente utiliza el estándar json-schema.org. Es por ello que verás ciertas particularidades respecto a otros schema
de Drupal, y es por ello también que es recomendable añadir una referencia al schema para ayudar a nuestros IDEs a proporcionar ayuda, autocompletado o indicación de errores.
Añadir el schema de SDC es así de fácil:
$schema: https://git.drupalcode.org/project/sdc/-/raw/1.x/src/metadata.schema.json
name: Navbar
status: experimental
group: Navigation
Tomar el control de la librería
Decíamos antes que una de las ventajas de SDC es que declara y carga por nosotros la library
del componente. Es decir, nuestro CSS y JS son añadidos automáticamente, sin que tengamos que hacer nada.
No obstante, esto es sólo válido para los archivos mycomponent.css
y mycomponent.js
. ¿Qué ocurre si queremos...
- ...declarar dependencias?
- ...añadir más archivos CSS / JS?
- ...usar otros nombres?
En ese caso, podemos tomar el control de la librería usando el archivo mycomponent.component.yml
name: Navbar
status: experimental
group: Navigation
libraryOverrides:
css:
theme:
assets/css/theme.css : {} # Añadimos un archivo CSS adicional
dependencies: # Añadimos dependencias con librerías del Core
- core/drupal
- core/once
Los valores de mycomponent.component.yml
serán unidos (merge) con la librería autogenerada, por lo que no es necesario volver a declarar aquí los archivos mycomponent.(css|js)
.
Definir el schema de nuestro componente: props
y slots
Hora de entrar en harina: lo más importante de nuestro componente es el contrato que firmamos con él.
Dicho de otro modo: SDC permite acotar y definir con precisión qué valores (inputs) acepta y cuáles son sus condiciones:
name: Navbar
props:
type: object
required: [type]
properties:
type:
type: string
description: Badge type
enum: [primary, secondary, info, danger]
slots:
content:
title: Alert content
description: The alert content.
En este ejemplo nuestro componente Navbar
acepta un prop
llamado type
que es una cadena de texto (string) y que acepta sólo tres valores: primary
, secondary
, info
o danger
. Este type
es obligatorio (required)
Además, nuestro componte declara un slot
llamado content
.
¿Vamos muy rápido? No te preocupes: antes de continuar vamos a clarificar a qué nos referimos con los conceptos de prop
y slot
:
Pero... ¿qué narices son los props
y slots
?
Esta diferencia conceptual no proviene de Drupal, sino que ha sido tomada prestada de Vue.js y aparece, de un modo u otro, en muchas librerías que usan diseño basado en componentes. Si quieres una descripción más completa, te recomiendo acudir a la documentación oficial.
Aquí haremos una diferenciación breve y funcional.
Props
Los props
son los valores que puede tomar un componente, cuya naturaleza y estructura es conocida de antemano.
Es decir, los props
pertenecen al mundo de lo conocido, lo controlado, lo definido. A través de ellos definimos y permitimos modificar la estructura y comportamiento del componente.
De los props
conocemos, por ejemplo, su tipado (¿es una cadena de texto? ¿un array? ¿un número?), así como si es requerido o no y si tiene un número acotado de valores posibles.
Slots
Por su naturaleza, los props
no pueden renderizar ni HTML arbitrario ni otros componentes anidados.
Esta capacidad queda reservada para los slots
, que declaran zonas donde tenemos permiso para tomar el control de su renderizado. Es decir: inserte aquí lo que quiera. Ese «lo que quiera» suele ser el HTML arbitrario o los componentes anidados que no podíamos pasar como props
.
Un ejemplo de props
vs slots
Veamos con un ejemplo ilustrativo.
Dado el siguiente componente (una especie de card
), los elementos marcados en violeta serían props
y el elemento naranja sería un slot
Los props
se distinguen porque son los que definen la estructura y variantes del componente. En este caso, podemos fácilmente imaginar que:
title
es un string que será renderizado dentro de unheading
o similardismissable
es un booleano que usaremos para controlar si se renderiza el botón de cierreborder_width
es un número entero que podría tomar un valor restringido a entre 1 y 3cta_url
ycta_text
son dos strings que usamos para crear unbutton
(¡y que a su vez podría ser otro componente de SDC!)
Sin embargo, en los componentes de tipo card
es muy normal que no sepamos (ni debamos saber) qué es lo que se va a renderizar dentro de la card. Es decir, la zona definida por content
podría ser cualquier cosa: texto plano, texto con HTML, imágenes, otros componentes...
Es por eso que content
se declara como un slot
, una zona de libre uso.
Definición de props
y slots
en el archivo *.component.yml
Manos a la obra: vamos a definir nuestro componente. Empecemos por los props
, que podrían ser algo así (consideremos el CTA como obligatorio para simplificar):
(...)
props:
type: object
required: [title, cta_url, cta_text]
properties:
title:
type: string
description: Badge type
dismissable:
type: boolean
default: false
border_width:
type: number
default: 1
enum: [1, 2, 3]
cta_url:
type: string
cta_text:
type: string
attributes:
type: Drupal\Core\Template\Attribute
Observamos lo siguiente:
- Hemos indicado que los
props
obligatorios sontitle
,cta_url
ycta_text
- Es posible indicar valores por defecto (
default
). - Es posible restringir los valores usando
enum
- Es posible indicar como
type
objetos de Drupal como el archiconocidoAttribute
Por su parte, definir slots
es mucho más sencillo ya que basta con indicar su existencia:
slots:
content:
title: Card content
description: The card content.
¡Socorro! ¡Demasiada información!
¡Lo siento! Esta parte es la más densa, pero dentro de poco comenzarás a ver los beneficios. No obstante, no te agobies: tienes a tu disposición una documentación completa acerca del archivo *.component.yml
, incluyendo un ejemplo anotado y un drush generate de componentes.
Sobrescritura de componentes
Por último en el archivo *.component.yml
es donde se produce también la sobrescritura de unos componentes por parte de otros:
name: Navbar
status: experimental
group: Navigation
props: {}
slots: {}
replaces: “mymodule:mycomponent”
En este caso, estamos indicando que queremos reemplazar con nuestro componente el componente con id mymodule:mycomponent
(recuerda que un componente se identifica por su nombre y por el módulo o tema que lo declara).
Es importante tener en cuenta que sólo los temas pueden sobrescribir componentes, y que sólo se puede sobrescribir componentes con el schema definido en mycomponent.component.yml
.
Fork!
La sobrescritura de componentes en SDC se hace mediante fork, esto es:
- Clona el componente que deseas sobrescribir en tu tema (recuerda: sólo los temas pueden reemplazar componentes)
- Realiza los cambios deseados
- Indica en el
*.component.yml
a qué componente estás sustituyendo usandoreplaces
2.4) Cómo implementar un componente
Todo esto está muy bien, pero es el momento de bajarlo a tierra: dado un componente con slots
y props
, ¿cómo lo implementaríamos en Drupal?
La respuesta es de nuevo sencilla: usando herramientas que ya conoces de sobra: los {% include %}
y {% embed %}
de twig.
Vamos a partir de un ejemplo sencillo: el componente badge
de Bootstrap (ver documentación):
Esto es una badge!
Una versión simple de este componente podría implementarse en twig de esta manera:
{% set classes = [
'badge',
'text-bg-' ~ color,
]%}
<div class="{{ classes | join( )}}">
{{ content }}
</div>
Como puedes ver, este componente no contiene ningún drupalismo. Nada de node.getLabel()
o de field_dismissable.value
. No está acoplado a ninguna implementación particular y por tanto es fácilmente reutilizable: de hecho, podríamos utilizarlo fuera de Drupal.
Desacoplar los componentes de Drupal lo máximo posible es uno de los objetivos de SDC, y ayudará a la reutilización e incluso a la generación de librerías de componentes compartidas.
Del código anterior es fácil inferir cuáles son sus props
:
props:
type: object
required: [color, content]
properties:
content:
type: string
color:
type: string
enum: ['primary, 'secondary', 'light', 'dark']
classes:
type: array
La única particularidad es que hemos añadido un enum
para limitar las opciones de color.
La implementación de este componente en nuestro código es tan fácil como utilizar un {% include %}
referenciando directamente al componente por su ID mytheme:badge
:
{% include "mytheme:badge" with {
color: 'primary',
content: 'Esto es una badge!',
classes: [
'additional-class'
]
} %}
Hacer el componente más flexible
Este componente tiene un problema: estamos asumiendo que content
siempre va a ser una cadena de texto, pero ¿qué ocurre si el usuario quiere añadir, por ejemplo, un pequeño icono?
<i class="fa fa-help"></i> Esto es una badge!
O, incluso, añadir un icono que es un componente a su vez:
{% include "mytheme:icon" with { icon: 'help' } %} Esto es una badge!
Con props
no podemos, pero sí que podemos declarar un slot
:
props:
type: object
required: [color]
properties:
color:
type: string
enum: ['primary, 'secondary', 'light', 'dark']
classes:
type: array
# Declaramos el content como slot:
slots:
content:
title: badge content
A nivel de twig, los slots
se implementan como un {% block %}
:
{% set classes = [
'badge',
'bg-' ~ color,
]%}
<div{{ attributes.addClass(classes) }}>
{% block content %}
Put your content here...
{% endblock %}
</div>
Y podemos incorporarlos a nuestro código utilizando {% embed %}
y pasando los props
mediante with
y los slots
como bloques.
{% embed "mytheme:badge" with {
color: 'primary'
} only %}
{% block content %}
<i class="fa fa-help"></i>
<span>My content</span>
{% endblock %}
{% endembed %}
Puesto que estamos usando twig, nos podemos beneficiar de las capacidades que ya conocemos. Por ejemplo, dentro de un block
nada nos impide anidar otro componente, que a su vez podría anidar otros tantos:
{% embed "mytheme:badge" with {
color: 'primary'
} only %}
{% block content %}
{# Embedded component #}
{% include "mytheme:icon" with {
icon: 'help'
} only %}
<span>Esto es una badge!</span>
{% endblock %}
{% endembed %}
Implementación como array de renderizado
Por último, hay que decir que los componentes de SDC también pueden ser implementados como arrays de renderizado, utilizando el nuevo tipo component
:
$build['badge'] = [
'#type' => 'component',
'#component' => 'mytheme:bagde',
'#props' => [
'color' => 'primary',
],
'#slots' => [
'content' => [
'#type' => 'html_tag',
'#tag' => 'span',
'#value' => t('Esto es una badge!'),
]
]
]
Este uso quizá no sea de tanto interés para su uso en temas, pero desde luego puede ser muy interesante a la hora de desarrollar módulos contribuidos, field formatters, etc.
2.5) ¿Y el hook de preprocesado del componente?
Llegado a este punto, y a la vista de lo familiar que resulta todo, es posible que estés esperando la aparición de un hook_preprocess_mycomponent
que permita alterar y masajear los props
y slots
que recibe el component antes de llegar al archivo twig.
La respuesta es que no hay hooks de preprocesado.
¿Por qué? Puedes leer (y participar) en esta issue, pero podemos citar los siguientes motivos:
- Las alteraciones, masajeados de datos, procesados... normalmente pertenecerán a un momento anterior, puesto que suelen ser específicos de Drupal. Es decir, si tienes que manejar el objeto
node
y sus métodos comonode.get(field_name)
, normalmente te verás obligado a hacerlo en un paso anterior al componente, manteniendo al componente desacoplado de las particularidades de Drupal y de tu proyecto: - Cualquier lógica necesaria que quede por implementar a nivel de componente se podrá llevar normalmente a twig.
- SDC nace con la idea de mantener todo lo necesario para renderizar un componente dentro de un directorio. Crear un
hook
abriría la puerta a que este sea invocado desde una miríada de sitios, reintroduciendo el problema del que intentamos huir.
2.6) Ok, pero... ¿qué hay en realidad de nuevo?
Llegados a este punto alguien podría declararse en estado de escepticismo y decir: no has respondido a mi pregunta, ¿qué hay realmente de nuevo en SDC?
- Organizar en directorios es algo que ya se pude hacer con components! o incluso manualmente.
- Usar
{% include %}
y{% embed %}
para abstraer y reutilizar componentes no es algo de SDC, sino de twig. Sólo se ha añadido un cómodo idnamespace:component
- La carga automática de
libraries
está muy bien pero:- Esto es algo que ya hacían temas como Radix
- La complejidad que sale por la puerta, entra por la ventana: puede que ya no tengamos que mantener el
*.libraries.yml
, pero a cambio tenemos que encargarnos del*.component.yml
.
Vamos a intentar responder analizando los problemas que SDC soluciona hoy y, sobre todo, los que podrá solucionar en el futuro:
3) Los problemas que soluciona SDC hoy
3.1) Un modelo mental más simple
El primer beneficio obvio de SDC es que organizar los componentes en directorios reduce la carga cognitiva y es un avance respecto a un modelo en el que hay una gran cantidad de capas y subsistemas interviniendo a diferentes niveles y obstaculizando la reutilización, la generación de estándares compartidos y, en general, el trabajo del desarrollador front-end.
3.2) Mejor rendimiento por defecto (sin depender de libraries.yml)
En mi experiencia, existen dos grandes modelos a la hora de manejar los activos CSS y JS en Drupal:
- Generar un gigantesco bundle que incluya todo el CSS y JS del sitio. Es mucho más rápido, pero es una mala práctica que tiene como consecuencia cargar muchos archivos innecesarios y suele provocar quejas de las herramientas de análisis, como el temido Lighthouse/Pagespeed Insights de Google.
- Componentizar todo y declarar una
library
por componente, siguiendo de hecho las mejores prácticas de Drupal. Es ideal, puesto que las librerías sólo se adjuntan cuando son invocadas, pero introduce problemas de mantenibilidad.
La realidad es que Drupal propone claramente el modelo 2, pero en muchos casos se sigue viendo el modelo 1. El motivo lo podemos encontrar si acudimos al mismo core de Drupal: el archivo libraries.yml
del tema Olivero tiene un tamaño considerable puesto que cada componente CSS, aunque incluya un simple archivo css
, ha de ser tratado como una librería independiente.
Demasiado trabajo para una tarea tan banal va contra el principio de máxima vagancia (si no existe tal principio, debería existir).
SDC lo soluciona al cargar las librerías automáticamente, proporcionando una experiencia de desarrollador agradable desde el minuto uno: simplemente lanza tus archivos a un directorio, usa el componente y recibirás sólo y exclusivamente el css
y js
necesarios.
3.3) Validación, tipado y errores útiles
Otro pequeño gran paso adelante para el front-end de Drupal es el tipado, que nos permite por fin tener información útil acerca de nuestros errores o incluso prevenirlos antes de que ocurran. Esto es, SDC es también una herramienta contra las regresiones y el temido WSOD.
Pongamos como ejemplo el siguiente código twig:
{% if anArray | length > 5 %}
{% set first = anArray | first %}
{% for item in array %}
{{ item }}
{% endfor %}
{% endif %}
Como podemos ver, opera usando filtros de array y for
loops sobre la variable anArray
. Pero... ¿qué ocurre si pasamos un valor que no es un array?
{% include “@theme/mycomponent.twig”
with {
anArray: 3
}
%}
La respuesta es... nada. Y esto es el problema: en lugar de un error, dado que twig es un lenguaje permisivo 3 | length
evalúa a 1. Este renderizado vacío es un error silencioso que puede ser desplegado sin que nadie se dé cuenta hasta que sea demasiado tarde.
Sin embargo, si usamos SDC y declaramos el tipo del prop
en el mycomponent.component.yml
properties:
anArray:
type: array
title: This is an Array
...cualquier tipo erróneo nos lanzará no sólo un error o excepción, sino que además será un error útil, esto es, con información concreta y precisa acerca del origen del problema y su solución. En este caso: Drupal\sdc\Exception\InvalidComponentException: [anArray] Integer value found, but an array or an object is required
3.4) Props obligatorios y enumerables
La fiesta de la validación no acaba aquí: puesto que los props
pueden ser indicados como obligatorios y pueden tener enum
s, obtenemos una protección extra a la hora de evitar que un componente reciba un número insuficiente de valores — o valores que no podemos manejar.
Un ejemplo potencialmente peligroso es el siguiente: un componente de tipo button
que permita definir la etiqueta HTML del componente en su prop tag
: <button>
o <a>
:
<{{ tag }}{{ attributes.addClass(button_classes) }}>
{{ content }}
</{{ tag }}>
Este componente tiene un grave problema: sin SDC, olvidarse de la tag
daría lugar a un HTML malformado que, en ausencia de etiqueta, se renderizará como texto plano:
Si hubiéramos indicado que tag
es un valor required
usando SDC, en su lugar hubieramos obtenido un error útil: [tag] The property tag is required
.
Incluso con SDC, es posible pasarle un valor no aceptable a tag
. Por ejemplo: parachute
. Este error es peor aún ya que twig felizmente renderizará la cadena de texto como una etiqueta HTML y los navegadores no informarán de ningún problema. Hasta que no se analize el código fuente, el error puede permanecer escondido durante años:
Sin embargo, bastaría con indicar un enum
para obtener, de nuevo, una advertencia útil: [tag] Does not have a value in the enumeration ["button","a"]
.
Y todo ello con dos líneas de código en el mycomponent.component.yml
:
props:
type: object
required:
- tag
properties:
tag:
type: string
title: Tag
default: 'button'
enum: ['button', 'a']
4) Los problemas que solucionará: el futuro de SDC
Quizá lo más ilusionante de SDC es lo que está por llegar.
En mi opinión, la comunidad front-end de Drupal ha acogido SDC con particular entusiasmo, y la prueba de ello es que ya hay múltiples integraciones en marcha, y eso que el módulo aún está en el camino a su versión Beta.
Vamos a repasar algunas de las integraciones ya existentes / en desarrollo y los caminos hacia los que apuntan, pero antes de ello hay que hablar de un aspecto fundamental de SDC que hemos dejado para el final.
4.1) La revolución silenciosa: un componente es un plugin
Así como suena: cada componente es, a nivel interno de Drupal, un plugin.
Uno de los aspectos más revolucionarios de SDC parte de algo tan sencillo como esto.
Esto significa que tenemos un servicio pluginManager
que podemos usar. Significa que podemos fácilmente listar todos los componentes existentes, analizar sus metadatos, modificarlos, acceder a su schema. Significa que el propio SDC puede ser alterado, extendido, modificado por otros módulos.
Significa, sobre todo, que SDC tiende un puente entre el front-end y el back-end de Drupal que era muy necesario.
Hasta ahora, la mayoría de decisiones de arquitectura en el front-end de Drupal eran invisibles para el back-end. El hecho de que el view_mode
de tipo teaser
fuera renderizado por el componente card.twig
y estilado por el archivo card.css
; y que ese componente aceptara ciertos valores y fuera reutilizado en un punto y sobrecrito en otro era algo que sólo estaba en la cabeza del desarrollador.
Con SDC, esto se acaba. Y tiene consecuencias inmediatas.
4.2) Mapeado directo de entidades a componentes
Un problema clásico de todos los intentos de componentizar el front-end de Drupal es que, al final, siempre hay que contar con una plantilla (template) como intermediario.
Es decir, por mucho que abstraigas tu componente en un mycomponent.twig
, siempre ha de ser invocado desde un template, que al fin y al cabo es lo que conoce Drupal.
De partida, SDC no soluciona esto, ya que el sistema de theme hooks y suggestions permanece inalterado. El resultado es una cierta repetición:
Sin embargo, usando el módulo Single Directory Componentes: Display sí que es posible mapear directamente desde Drupal (modos de vista, campos) a componentes.
Es decir, es posible saltarse la plantilla como intermediario y elegir directamente qué componente debe encargarse de renderizar un modo de vista o campo directamente desde la interfaz de usuario:
Es posible que a algunos esto os suene familiar: es algo que ya popularizó el módulo UI Patterns en su día. Buenas noticias para vosotros: la rama 2.0.x de UI Patterns utilizará SDC como base, en lugar de su propio sistema. Una feliz convergencia.
El poder de los plugins
Decíamos antes que el hecho de que los componentes sean plugins es una revolución, y aquí podemos ver un ejemplo: el módulo SDC Display puede mostrar un listado de todos los componentes, acceder a sus metadatos (nombre, descripción) y conocer sus props y slots.
¿Más fácil todavía? Bloques autogenerados a partir de componentes SDC
¿Podría ser mejor todavía la experiencia del desarrollador? ¡Claro que sí! Podríamos tener bloques de Drupal que se generaran automáticamente a partir de componentes.
Dado que el archivo *.component.yml
ya contiene toda la información acerca de los props
y slots
que toma un componente, es posible generar bloques al vuelo.
Es lo que hace el módulo CL Blocks, un módulo preparado para el predecesor de SDC (Component Libraries), que todavía no es compatible con SDC pero está en camino de serlo.
Estos bloques puede ser utilizado, por ejemplo, en Layout Builder. Su formulario se genera a partir de la información presente en el propio *.component.yml
(por ejemplo, un prop
de tipo number
dará lugar a un campo de formulario de tipo <input type="number">
.
Revolucionando el flujo de trabajo de Drupal
En cualquier caso, sea a través de mapeado manual de componentes, mediante bloques autogenerados o cualquier otra soluciń futura, SDC acerca Drupal a un flujo de trabajo mucho más sencillo por el cual el desarrollador dispondrá automáticamente de herramientas para usar sus componentes desde la interfaz de administración de Drupal.
4.3) Integración con Storybook: tests y autodocumentación
Otra integración ya existente para Drupal y SDC es Storybook, a través de CL Server y el plugin de Storybook asociado.
CL Server activa un endpoint en Drupal que puede ser accedido por una instancia de Storybook configurada para ello.
La configuración reviste algo de complejidad, pero el resultado es una guía de estilos / documentación / herramienta de testeo en vivo que es capaz de mostrar todos los componentes dados de alta en Drupal.
¿Por qué CL Server?
Un problema clásico de las integraciones con Storybook es que usan twig.js para renderizar twig.
Twig.js no se encuentra en paridad de capacidades con twig php, por lo que no es una herramienta fiable: pueden surgir discrepancias, los tests pueden arrojar falsos positivos / negativos o pueden haber diferencias visuales.
CL Server renderiza los componentes usando el propio Drupal, por lo que se garantiza que el renderizado en Storybook y el de Drupal son idénticos básicamente porque... son idénticos.
Storybook es una herramienta muy potente que va mucho más allá de una guía de estilo en vivo. Por ejemplo, contra los componentes Storybook es capaz de ejecutar tests. En este pantallazo podemos ver un test de accesibilidad:
Un concepto central de Storybook son las stories
, siendo cada story
un estado de un componente dado. En la imagen superior, por ejemplo, podemos ver la story
del componente alert
en modo dismissibile
.
Es fácil identificar, a nivel de testado, las stories
con scenarios
.
Otra ventaja del uso de Storybook es que el componente se puede auto-documentar a partir de información presente en su propio directorio. Storybook es capaz de crear una página con toda la información presentada de una manera concisa y ordenada, incluyendo los props
y slots
del componente, el README
o incluso el propio código fuente.
¡Atención!
Lo estamos pintando muy bonito, pero la realidad es que la integración entre Drupal y Storybook todavía está incompleta y no permite que Storybook sea capaz de leer e interpretar el mycomponent.component.yml
, por lo que otro archivo (el mycomponent.stories.yml
) tiene que ser añadido.
Existe un generador de Drupal (disclaimer: lo escribí yo) que convierte los archivos *.component.yml
en *.stories.yml
automáticamente. Ahí puedes ver también las diferencias entre los dos formatos.
Esta es una de las integraciones que más me han interesado, por lo que es posible que pronto escriba una extensión de este artículo explicando cómo realizar una integración lo más completa posible entre SDC y Storybook.
4.4) Y aún hay más...
Integración de WebComponents
El módulo Pokemon Card muestra lo sencillo que puede ser integrar un WebComponent en SDC, abriendo la puerta a la generación o integración de librerías de WebComponentes junto con otras herramientas como SDC Display.
Una de las cosas más interesantes es lo parecida que se vuelve la experiencia de desarrollo a React o Vue.
Integrar Pokemon Card es tan fácil como requerir la dependencia (composer require
), escribir el siguiente código...
{% include "pokemon_card:pokemon_card with {
name: "Gengar"
}%}
..y ¡voilà!. Un componente renderizado perfectamente que es capaz incluso de auto-gestionar su conexión con la API externa de donde extrae los datos.
¿Cómo de diferente es para el desarrollador esta experiencia de lo que estamos acostumbrados a ver en React y compañía? Las distancias se acortan.
<Pokemon name="Gengar" />
WebProfiler y CL Devel
El módulo WebProfiler ya ha añadido soporte para componentes, incluyendo una pequeña documentación de cada componente. Del mismo modo, CL Devel es capaz de generar tambiémn una autodocumentación de componentes basada en sus metadatos.
Generadores
Para reducir la fricción a la hora de generar componentes y sus archivos (en especial el *.component.yml
) existen ya al menos tres generadores que proporcionan comandos de drush
:
- CL Generator, para componentes. Si usas Drush 12, el generador ya está incorporado.
- SDC Story Generator, para stories de Storybook basadas en componentes
- SDC Web Componentes Generator, para WebComponentes
...y lo que está por llegar.
Integración con UI Patterns, el módulo SDC Styleguide proporcionando una experiencia similar a Storybook pero en Drupal, temas del Core usando SDC... la comunidad de Drupal ha recibido con los brazos abiertos SDC y las iniciativas se multiplican.
5) En resumen
Single Directory Components es un pequeño gran añadido al desarrollo front-end en Drupal. Parte de su poder radica en su simplicidad y otra parte en el enorme alcance que algunos de sus cambios suponen, principalmente el hecho de que los componentes sean plugins.
Podemos decir que SDC hace todo lo siguiente:
- Acerca Drupal a las mejores prácticas front-end.
- Simplifica y elimina tareas repetitivas.
- Es fácil de implementar, puede ser implementado gradualmente y no es un «refactor» de la capa de renderizado de Drupal sino un añadido.
- Tiende un puente entre el front-end y el back-end que era muy necesario.
- Añade capas de seguridad al desarrollo front-end
- Permitirá el desarrollo de mejores herramientas enfocadas a los desarrolladores
- Permitirá flujos de trabajo más sencillos para los site-builders