Si has estado observando cómo evoluciona el mundo front-end con React, Vue y todos esos frameworks modernos de JavaScript, quizás te preguntes si Drupal puede mantenerse al día. ¿Las buenas noticias? Definitivamente puede. Con Single Directory Components (SDC), Tailwind CSS v4, Vite y Storybook, puedes crear temas de Drupal basados en componentes que se ven sorprendentemente modernos.
Esta guía te llevará a través de la configuración de un flujo de trabajo moderno completo. He probado cada comando aquí, corregido los problemas comunes y documentado las cosas que suelen causar dificultades. Comencemos.
Antes de comenzar
Necesitarás algunas cosas ya instaladas en tu máquina:
- Drupal 10 o superior
- DDEV (para desarrollo local)
- Drush (herramienta de línea de comandos de Drupal)
- Bun o npm (estoy usando Bun aquí porque es más rápido, pero npm también funciona bien)
Si te falta algo de esto, configúralo primero. Créeme, te ahorrará dolores de cabeza más adelante.
Configurando la base de tu tema
Primero, creemos un lugar adecuado para tu tema personalizado. Drupal espera que los temas personalizados estén en un lugar específico:
mkdir -p web/themes/customAhora viene la parte divertida. Drush tiene un generador de temas que te hará un montón de preguntas. No te preocupes, te diré qué responder:
ddev drush generate themeEsto es lo que te preguntarán:
- Theme name:
Blog SDC(este es el nombre legible para humanos) - Theme machine name:
blog_sdc(solo minúsculas y guiones bajos) - Base theme:
stable9(esto es importante, no lo omitas o tendrás errores de dependencias más tarde) - Description:
A custom theme for testing SDC components - Package:
Custom - Create breakpoints?
No(usaremos Tailwind para diseño responsive) - Create theme settings form?
No(mantengámoslo simple por ahora)
Una vez generado tu tema, instalémoslo y hagámoslo el tema predeterminado para tu sitio:
ddev drush theme:install blog_sdc -y
ddev drush config-set system.theme default blog_sdc -y
ddev drush config-get system.theme defaultEse último comando solo confirma que tu tema ahora está activo. Deberías ver blog_sdc en la salida.
Creando tu primer componente
Single Directory Components (SDC) son revolucionarios. En lugar de dispersar archivos de componentes en múltiples directorios, todo vive junto: template, CSS, JavaScript y configuración, todo en una carpeta. Es la arquitectura de componentes que querías en Drupal.
Generemos un componente Card:
ddev drush generate sdcResponde las preguntas:
- Theme machine name:
blog_sdc - Component name:
Card Component - Component machine name:
card - Component description:
A reusable card component - Library dependencies: (solo presiona Enter para omitir)
- Need CSS?
y - Need JS?
n(agregaremos esto más tarde) - Need component props?
n - Need slots?
n
Después de la generación, limpia el caché de Drupal. Harás esto mucho, así que acostúmbrate a este comando:
ddev drush crHaciendo que tu componente funcione
Ahora es donde las cosas se ponen interesantes. Necesitamos conectar nuestro componente al contenido real de Drupal. Primero, configuremos la depuración de Twig para que puedas ver qué está pasando bajo el capó.
Copia el archivo de services predeterminado:
cp web/sites/default/default.services.yml web/sites/default/services.ymlAbre web/sites/default/services.yml y encuentra la sección twig.config. Solo estás cambiando tres valores aquí, no reemplaces toda la sección:
parameters:
twig.config:
debug: true
auto_reload: true
cache: falseEstas configuraciones te mostrarán comentarios útiles en tu código fuente HTML y recargarán templates automáticamente. Salvavidas durante el desarrollo.
Ahora instalemos algunas herramientas de desarrollo que facilitarán tu vida:
ddev composer require drupal/devel drupal/twig_tweak 'drupal/kint-kint:^2.2'
ddev drush en devel devel_generate twig_tweak kint -yEstos módulos son esenciales para el desarrollo: Devel proporciona herramientas de depuración, Twig Tweak agrega funciones útiles de Twig, y Kint te da volcados de variables hermosos.
Nota rápida: Sí, es drupal/kint-kint con el nombre repetido. Es raro, pero ese es el nombre real del paquete debido a un conflicto con otro paquete.
Genera un artículo de prueba para trabajar:
ddev drush devel-generate:content 1 --bundles=articleEncuentra el ID de nodo de tu artículo (necesitarás esto):
ddev drush sqlq "SELECT nid, title FROM node_field_data WHERE type='article' LIMIT 1"Ahora sobrescribamos el template del artículo para usar nuestro componente card. Crea el directorio de template y copia el template base:
mkdir -p web/themes/custom/blog_sdc/templates/node
cp web/core/themes/stable9/templates/content/node.html.twig web/themes/custom/blog_sdc/templates/node/node--article.html.twigAbre templates/node/node--article.html.twig y reemplaza todo con esta única declaración include:
{% include 'blog_sdc:card' with {
title: label,
content: content.body,
image: content.field_image,
tags: content.field_tags,
url: url
} %}Aquí es donde ocurre la magia. Le estás diciendo a Drupal "usa mi componente card y pásale estos valores". Nota qué limpio es esto comparado con las sobrescrituras de template tradicionales.
Pero necesitamos decirle a nuestro componente qué datos esperar. Edita components/card/card.component.yml:
'$schema': 'https://git.drupalcode.org/project/drupal/-/raw/10.1.x/core/modules/sdc/src/metadata.schema.json'
name: Card Component
status: stable
description: A reusable card component
props:
type: object
properties:
title:
type: string
title: Card Title
content:
type: string
title: Card Content
image:
type: string
title: Card Image
tags:
type: string
title: Card Tags
url:
type: string
title: Card URLLimpia el caché nuevamente:
ddev drush crDesarrollando el template del componente
Antes de agregar Tailwind, asegurémonos de que nuestro componente realmente muestre algo. Edita components/card/card.twig:
<article {{ attributes.addClass('card') }}>
{% if title %}
<h2 class="card__title">
{% if url %}
<a href="{{ url }}">{{ title }}</a>
{% else %}
{{ title }}
{% endif %}
</h2>
{% endif %}
{% if content %}
<div class="card__content">
{{ content }}
</div>
{% endif %}
</article>Y agrega algunos estilos básicos a components/card/card.css (pronto reemplazaremos esto con Tailwind):
[data-component-id="blog_sdc:card"] {
border: 1px solid #ddd;
border-radius: 8px;
padding: 1.5rem;
margin-bottom: 1.5rem;
background: #fff;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
transition: box-shadow 0.3s ease;
}
[data-component-id="blog_sdc:card"]:hover {
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}
[data-component-id="blog_sdc:card"] .card__title {
margin: 0 0 1rem 0;
font-size: 1.5rem;
color: #333;
}
[data-component-id="blog_sdc:card"] .card__title a {
color: #0073e6;
text-decoration: none;
}
[data-component-id="blog_sdc:card"] .card__title a:hover {
text-decoration: underline;
}
[data-component-id="blog_sdc:card"] .card__content {
color: #666;
line-height: 1.6;
}Limpia el caché y prueba:
ddev drush crVisita tu artículo (probablemente en /node/1) y deberías ver tu componente card en acción. Si ejecutaste esa consulta SQL antes, sabrás exactamente qué URL visitar.
Incorporando Tailwind CSS v4 y Vite
Aquí es donde las cosas se ponen realmente interesantes. Vamos a configurar un sistema de compilación moderno con Vite y Tailwind CSS v4. Navega al directorio de tu tema:
cd web/themes/custom/blog_sdc
bun init -yInstala las dependencias que necesitamos:
bun add -D vite @tailwindcss/vite tailwindcss
bun add -D prettier prettier-plugin-tailwindcss @ttskch/prettier-plugin-tailwindcss-anywherePrettier formateará tu código automáticamente, incluyendo ordenar las clases de Tailwind. Ese segundo plugin de Prettier es especial: permite que Prettier entienda templates de Twig, lo que Prettier normal no puede hacer.
Crea un archivo .prettierrc:
{
"plugins": ["prettier-plugin-tailwindcss", "@ttskch/prettier-plugin-tailwindcss-anywhere"],
"overrides": [
{
"files": ["*.twig", "*.html.twig"],
"options": {
"parser": "anywhere",
"regex": "(?:class(?:Name)?|addClass)\\\\s*(?:[=:]|\\\\()\\\\s*[\"']([^\"'{}~]*)"
}
}
],
"printWidth": 100,
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5"
}Ahora configura Vite. Crea vite.config.js en la raíz de tu tema:
import { defineConfig } from 'vite';
import tailwindcss from '@tailwindcss/vite';
import { resolve } from 'path';
export default defineConfig({
plugins: [tailwindcss()],
build: {
outDir: 'dist',
manifest: true,
emptyOutDir: true,
rollupOptions: {
input: {
main: resolve(__dirname, 'src/main.js'),
},
output: {
entryFileNames: '[name]-[hash].js',
chunkFileNames: '[name]-[hash].js',
assetFileNames: '[name]-[hash].[ext]',
},
},
},
server: {
host: '0.0.0.0',
port: 5173,
strictPort: true,
cors: true,
},
base: process.env.NODE_ENV === 'production' ? '/themes/custom/blog_sdc/dist/' : '/',
});Esta configuración maneja la compilación de tus recursos con hashes para caché y configura un servidor de desarrollo con reemplazo de módulos en caliente.
Crea un directorio fuente para tus archivos principales de JavaScript y CSS:
mkdir -p srcCrea src/main.css con directivas de Tailwind:
@import 'tailwindcss';
@layer base {
body {
@apply text-gray-900;
}
}
@layer components {
/* Your custom component styles */
}
@layer utilities {
/* Your custom utilities */
}Aquí es donde todo se vuelve inteligente. Crea src/main.js con descubrimiento automático de componentes:
/**
* @file
* Main JavaScript entry point for blog_sdc theme.
*/
// Import main CSS (includes Tailwind)
import './main.css';
// Auto-discover component JavaScript files
const componentModules = import.meta.glob('../components/**/*.js', { eager: false });
// Initialize components
async function initializeComponents() {
const componentPaths = Object.keys(componentModules);
console.log(`[blog_sdc] Found ${componentPaths.length} component JS files`);
for (const path of componentPaths) {
const componentName = path.match(/components\/([^/]+)\//)?.[1];
const componentSelector = `[data-component-id="blog_sdc:${componentName}"]`;
if (document.querySelector(componentSelector)) {
console.log(`[blog_sdc] Loading component: ${componentName}`);
try {
const module = await componentModules[path]();
if (module.default && typeof module.default === 'function') {
module.default();
}
} catch (error) {
console.error(`[blog_sdc] Error loading component ${componentName}:`, error);
}
}
}
}
// Initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializeComponents);
} else {
initializeComponents();
}
export { initializeComponents };Este script encuentra y carga automáticamente JavaScript para cualquier componente que esté en la página. ¡Ya no necesitas importar manualmente cada archivo de componente!
Actualiza tu package.json con scripts útiles:
{
"name": "blog_sdc",
"type": "module",
"private": true,
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"format": "prettier --write \"src/**/*.{js,css}\" \"components/**/*.{js,twig}\" \"templates/**/*.twig\""
}
}Como ahora estamos usando Tailwind, ya no necesitamos ese archivo CSS:
rm components/card/card.cssAhora reconstruyamos el componente card con utilidades de Tailwind. Edita components/card/card.twig:
<article {{ attributes.addClass('bg-white rounded-lg shadow-md hover:shadow-lg transition-shadow duration-300 overflow-hidden mb-6') }}>
{% if image %}
<div class="aspect-video overflow-hidden">
{{ image }}
</div>
{% endif %}
{% if title %}
<div class="p-6">
<h2 class="text-xl font-semibold text-gray-900 mb-3">
{% if url %}
<a href="{{ url }}" class="text-blue-600 hover:text-blue-800 transition-colors">
{{ title }}
</a>
{% else %}
{{ title }}
{% endif %}
</h2>
</div>
{% endif %}
{% if content %}
<div class="px-6 pb-4">
<div class="text-gray-600 leading-relaxed">
{{ content }}
</div>
</div>
{% endif %}
{% if tags %}
<div class="px-6 pb-6 border-t border-gray-100 pt-4">
<div class="flex flex-wrap gap-2">
{{ tags }}
</div>
</div>
{% endif %}
</article>Mira eso: no se necesita archivo CSS. Todos los estilos están ahí mismo en el template usando clases utilitarias de Tailwind.
Agreguemos algo de interactividad. Crea components/card/card.js:
/**
* @file
* Card component JavaScript.
*/
export default function initCard() {
const cards = document.querySelectorAll('[data-component-id="blog_sdc:card"]');
console.log(`[Card] Initializing ${cards.length} card(s)`);
cards.forEach((card) => {
card.addEventListener('mouseenter', () => {
card.classList.add('card--hover');
});
card.addEventListener('mouseleave', () => {
card.classList.remove('card--hover');
});
const title = card.querySelector('.card__title');
if (title) {
console.log(`[Card] Initialized: ${title.textContent.trim()}`);
}
});
}Ahora compila todo:
bun run buildPresta atención a la salida. Verás algo como:
dist/main-BuEGDbdA.css
dist/main-BicIW-eB.jsEsos valores de hash (BuEGDbdA y BicIW-eB) son importantes. Cópialos, los necesitarás en un segundo.
Edita blog_sdc.libraries.yml y agrega tus recursos compilados con los valores de hash reales:
# Vite-built assets with Tailwind CSS
vite-main:
css:
theme:
dist/main-BuEGDbdA.css: {} # Replace with your actual hash
js:
dist/main-BicIW-eB.js: {} # Replace with your actual hash
dependencies:
- core/drupal
# Legacy theme library (if it exists)
global:
css:
theme:
css/style.css: {}Importante: Esos valores de hash cambian cada vez que compilas. Si modificas tus archivos fuente y recompilas, necesitarás actualizar estos hashes en tu archivo de bibliotecas. Es molesto, pero así funciona el cache-busting.
Ahora dile a tu tema que cargue esta biblioteca. Edita blog_sdc.info.yml:
name: Blog SDC
type: theme
description: 'A custom theme for testing SDC components'
package: Custom
core_version_requirement: ^10 || ^11
base theme: stable9
libraries:
- blog_sdc/vite-mainLimpia el caché una vez más:
ddev drush cr¿Quieres que tu código se vea bonito? Ejecuta el formateador:
bun run formatAgregando Storybook para desarrollo de componentes
Aquí es donde el flujo de trabajo realmente brilla. Storybook te permite desarrollar componentes de forma aislada, sin necesidad de navegar por la interfaz de administración de Drupal o crear contenido de prueba. Inicialízalo:
npx storybook@latest init --builder vite --type html --no-dev --yesLimpia las stories de ejemplo que incluyen:
rm -rf storiesInstala el accessibility addon (esto es muy útil):
bun add -D @storybook/addon-a11y@10.0.7Elimina las configuraciones de TypeScript (lo mantendremos simple):
rm .storybook/main.ts .storybook/preview.tsCrea .storybook/main.js:
import { join } from 'node:path';
import { cwd } from 'node:process';
const config = {
stories: ['../components/**/*.stories.js'],
addons: [
'@storybook/addon-a11y',
'@storybook/addon-docs',
],
framework: {
name: '@storybook/html-vite',
options: {
builder: {
viteConfigPath: './.storybook/vite.config.js',
},
},
},
core: {
disableTelemetry: true,
},
};
export default config;Crea .storybook/preview.js para configurar cómo se muestran las stories:
import '../src/main.css';
export const parameters = {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
backgrounds: {
default: 'light',
values: [
{ name: 'light', value: '#ffffff' },
{ name: 'dark', value: '#1a1a1a' },
{ name: 'gray', value: '#f3f4f6' },
],
},
layout: 'padded',
a11y: {
config: {
rules: [{ id: 'color-contrast', enabled: true }],
},
},
};
export const decorators = [
(Story) => {
const wrapper = document.createElement('div');
wrapper.style.padding = '1rem';
wrapper.appendChild(Story());
return wrapper;
},
];Crea .storybook/vite.config.js (separado de tu configuración principal de Vite):
import { defineConfig } from 'vite';
import tailwindcss from '@tailwindcss/vite';
export default defineConfig({
plugins: [tailwindcss()],
optimizeDeps: {
include: [],
},
});Ahora crea tu primera story. Crea components/card/card.stories.js:
export default {
title: 'Components/Card',
tags: ['autodocs'],
parameters: { layout: 'padded' },
argTypes: {
title: { control: 'text', description: 'The title displayed in the card header' },
content: { control: 'text', description: 'The main content of the card' },
image: { control: 'text', description: 'Optional image HTML for the card' },
tags: { control: 'text', description: 'Optional tags HTML for the card' },
url: { control: 'text', description: 'Optional URL for the title link' },
},
};
const renderCard = (args) => {
const article = document.createElement('article');
article.className = 'mb-6 overflow-hidden rounded-lg bg-white shadow-md transition-shadow duration-300 hover:shadow-lg';
article.setAttribute('data-component-id', 'blog_sdc:card');
if (args.image) {
const imageDiv = document.createElement('div');
imageDiv.className = 'aspect-video overflow-hidden';
imageDiv.innerHTML = args.image;
article.appendChild(imageDiv);
}
if (args.title) {
const titleDiv = document.createElement('div');
titleDiv.className = 'p-6';
const h2 = document.createElement('h2');
h2.className = 'mb-3 text-xl font-semibold text-gray-900';
if (args.url) {
const link = document.createElement('a');
link.href = args.url;
link.className = 'text-blue-600 transition-colors hover:text-blue-800';
link.textContent = args.title;
h2.appendChild(link);
} else {
h2.textContent = args.title;
}
titleDiv.appendChild(h2);
article.appendChild(titleDiv);
}
if (args.content) {
const contentDiv = document.createElement('div');
contentDiv.className = 'px-6 pb-4';
const innerDiv = document.createElement('div');
innerDiv.className = 'leading-relaxed text-gray-600';
innerDiv.innerHTML = args.content;
contentDiv.appendChild(innerDiv);
article.appendChild(contentDiv);
}
if (args.tags) {
const tagsDiv = document.createElement('div');
tagsDiv.className = 'border-t border-gray-100 px-6 pt-4 pb-6';
const innerDiv = document.createElement('div');
innerDiv.className = 'flex flex-wrap gap-2';
innerDiv.innerHTML = args.tags;
tagsDiv.appendChild(innerDiv);
article.appendChild(tagsDiv);
}
return article;
};
export const Basic = {
args: { title: 'Basic Card', content: 'This is a basic card with minimal styling.' },
render: renderCard,
};
export const WithImage = {
args: {
title: 'Card with Image',
content: 'This card includes a featured image at the top.',
image: '<img src="https://placehold.co/600x400" alt="Placeholder" class="w-full h-auto" />',
url: '/example',
},
render: renderCard,
};
export const Complete = {
args: {
title: 'Complete Card',
content: 'This card showcases all available features.',
image: '<img src="https://placehold.co/600x400" alt="Complete" class="w-full h-auto" />',
tags: '<span class="px-2 py-1 bg-blue-100 text-blue-800 rounded text-sm">Featured</span>',
url: '/complete',
},
parameters: { backgrounds: { default: 'gray' } },
render: renderCard,
};Actualiza tu package.json una vez más para agregar scripts de Storybook:
{
"name": "blog_sdc",
"type": "module",
"private": true,
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"format": "prettier --write \"src/**/*.{js,css}\" \"components/**/*.{js,twig}\" \"templates/**/*.twig\"",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
}
}Inicia Storybook:
bun run storybookAbre http://localhost:6006 en tu navegador. Deberías ver tu componente card con tres variantes diferentes con las que puedes interactuar y modificar en tiempo real.
¿Quieres compilar una versión estática para despliegue o compartir con tu equipo?
bun run build-storybookTu flujo de trabajo diario de desarrollo
Ahora que todo está configurado, así es como trabajarás día a día.
Creando nuevos componentes
Veamos cómo agregar un nuevo componente desde cero. Digamos que quieres agregar un componente Button:
Genera el componente:
ddev drush generate sdc
# Theme: blog_sdc, Component: Button, machine: buttonElimina el archivo CSS (estamos usando Tailwind, ¿recuerdas?):
rm components/button/button.cssEstilízalo con Tailwind en components/button/button.twig:
<button {{ attributes.addClass('px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors') }}>
{{ label }}
</button>Agrega JavaScript si es necesario en components/button/button.js:
export default function initButton() {
console.log('Button component initialized');
// Your button logic here
}components/button/button.stories.js con diferentes variantesPruébalo en Storybook:
bun run storybookCompila tus recursos:
bun run buildblog_sdc.libraries.yml con los nuevos valores de la salida de compilaciónLimpia el caché de Drupal:
ddev drush crFormatea tu código:
bun run formatComandos diarios comunes
Estos son los comandos que usarás constantemente:
# Iniciar el servidor de desarrollo de Vite (recarga en caliente, mucho más rápido que recompilar)
bun run dev
# Iniciar Storybook (usualmente en una terminal separada)
bun run storybook
# Generar contenido de prueba cuando necesites más artículos para trabajar
ddev drush devel-generate:content 5 --bundles=article
# Encontrar IDs de nodos para visitar contenido específico
ddev drush sqlq "SELECT nid, title FROM node_field_data WHERE type='article' LIMIT 5"
# Limpiar caché (en serio, haz esto todo el tiempo)
ddev drush cr
# Verificar qué temas están instalados
ddev drush pm:list --type=theme
# Desinstalar un tema si necesitas empezar de nuevo
ddev drush theme:uninstall blog_sdcSolución de problemas comunes
Seamos honestos: las cosas saldrán mal. Aquí está cómo solucionar los problemas más comunes.
Error "Unmet Dependencies" al instalar el tema
Esto usualmente significa que tu blog_sdc.info.yml tiene un problema. Verifica que base theme: esté configurado con un nombre de tema válido como stable9. El generador de Drush a veces se confunde y mezcla el orden de las entradas durante la generación.
El componente no aparece
Esto es casi siempre un problema de caché. Ejecuta ddev drush cr e intenta de nuevo. Si eso no funciona:
- Verifica que los archivos del componente realmente existan en
web/themes/custom/blog_sdc/components/card/ - Verifica que tu tema esté instalado:
ddev drush pm:list --type=theme - Confirma que tu tema esté configurado como predeterminado:
ddev drush config-get system.theme default
Los estilos de Tailwind no se cargan
Algunas cosas para verificar:
- ¿Ejecutaste
bun run build? - ¿Actualizaste los valores de hash en
blog_sdc.libraries.yml? - ¿La biblioteca realmente se está cargando? Verifica
blog_sdc.info.ymlpara la secciónlibraries: - Abre las herramientas de desarrollo de tu navegador, ve a la pestaña Network y busca tu archivo CSS
- Limpia el caché de Drupal:
ddev drush cr
La compilación de Vite falla
Usualmente esto significa un problema de dependencias:
- Ejecuta
bun installpara reinstalar todo - Verifica que
src/main.jsysrc/main.cssexistan - Verifica que
vite.config.jsesté en el directorio raíz de tu tema - Mira el mensaje de error: Vite usualmente te dice exactamente qué está mal
Storybook no inicia
- Verifica tu versión de Node.js (se recomienda 18 o superior)
- Elimina
node_modulesy ejecutabun installde nuevo - Asegúrate de que nada más esté usando el puerto 6006
- Verifica que
.storybook/main.jsy.storybook/preview.jsexistan
Los cambios no aparecen
El problema clásico. Prueba esto en orden:
- Limpia el caché de Drupal:
ddev drush cr - Recarga forzada de tu navegador:
Cmd+Shift+Ren Mac oCtrl+Shift+Ren Windows/Linux - Verifica la consola de tu navegador para errores de JavaScript
- Asegúrate de estar editando archivos en el directorio de tema correcto
Error del módulo Kint
Si obtienes un error al instalar Kint, usa el nombre exacto del paquete: drupal/kint-kint (sí, "kint" aparece dos veces). Esto es debido a un conflicto de nombres con otro paquete. Es confuso, pero así es.
Referencia rápida de comandos
Para cuando solo necesites copiar y pegar algo rápidamente:
# Limpiar caché (usa esto constantemente)
ddev drush cr
# Generar un nuevo componente SDC
ddev drush generate sdc
# Compilar recursos para producción
bun run build
# Iniciar servidor de desarrollo con recarga en caliente
bun run dev
# Iniciar Storybook
bun run storybook
# Formatear tu código
bun run format
# Generar contenido de prueba
ddev drush devel-generate:content 5 --bundles=article
# Encontrar IDs de nodos
ddev drush sqlq "SELECT nid, title FROM node_field_data WHERE type='article' LIMIT 5"
# Verificar temas instalados
ddev drush pm:list --type=theme
# Obtener el tema predeterminado actual
ddev drush config-get system.theme default
# Compilar Storybook estático para despliegue
bun run build-storybookMejores prácticas
Desarrollo de temas
- Usa un tema base como
stable9para una base sólida - Limpia el caché frecuentemente con
drush cr(en serio, haz esto a menudo) - Mantén los componentes SDC enfocados y reutilizables
- Sigue las convenciones de nomenclatura: minúsculas con guiones bajos
- Prueba antes de desplegar a producción
Desarrollo de componentes
- Elimina los archivos CSS generados cuando uses Tailwind
- Almacena el JavaScript de componentes junto a los templates Twig
- Documenta props en los metadatos de
component.yml - Usa versionado semántico para dependencias
- Mantén los componentes organizados y enfocados
Proceso de compilación
- Siempre usa
bun run buildpara despliegues de producción - Actualiza los hashes de biblioteca después de cada compilación
- Aprovecha HMR con
bun run devdurante el desarrollo - Formatea el código antes de hacer commit con
bun run format - Compila para producción antes de enviar cambios
Mejores prácticas de Storybook
- Un archivo de story por componente
- Usa nombres descriptivos para las stories
- Prueba casos extremos (estados vacíos, errores, carga)
- Habilita autodocs con
tags: ['autodocs'] - Siempre verifica el panel a11y antes de hacer commit
- Prueba componentes en diferentes colores de fondo
- Mantén las stories simples y enfocadas
Lo que has construido
Esto no es solo un tema: es un entorno de desarrollo moderno completo:
- Tema personalizado de Drupal con soporte SDC
- Tailwind CSS v4 con estilización utility-first
- Sistema de compilación Vite con recarga en caliente
- Descubrimiento automático de JavaScript de componentes
- Storybook para desarrollo aislado de componentes
- Prettier para formateo de código
- Pruebas de accesibilidad integradas
- Flujo de trabajo de desarrollo completo
Cronograma de desarrollo
Basado en este flujo de trabajo, esto es lo que puedes esperar:
Configuración inicial
- Generación de tema: ~5 minutos
- Configuración de Tailwind + Vite: ~10 minutos
- Integración de Storybook: ~10 minutos
- Total: ~25-30 minutos
Por componente
- Generar componente: ~30 segundos
- Diseñar con Tailwind: ~5-10 minutos
- Agregar JavaScript: ~5-10 minutos (si es necesario)
- Crear stories: ~5-10 minutos
- Compilar y probar: ~2-3 minutos
- Total: ~15-30 minutos por componente
Esto es significativamente más rápido que los flujos de trabajo tradicionales de desarrollo de temas de Drupal.
Conclusiones clave
- El desarrollo moderno de temas de Drupal combina SDC para componentes reutilizables, Tailwind para estilización utility-first, Vite para compilaciones rápidas y Storybook para desarrollo aislado
- La configuración completa toma 25-30 minutos, con nuevos componentes listos en 15-30 minutos
- Este flujo de trabajo rivaliza con los frameworks modernos de JavaScript mientras permanece dentro del ecosistema de Drupal
- El descubrimiento automático de componentes escala a medida que agregas más componentes
- La integración de Prettier mantiene tu código limpio y consistente
- No se necesitan archivos CSS separados: las utilidades de Tailwind reemplazan el CSS personalizado
- La integración manual te da control total sobre la carga de recursos
- Las pruebas de accesibilidad están integradas desde el principio
- La experiencia de desarrollo es rápida, moderna y agradable
Conclusión
Ahora tienes un flujo de trabajo moderno de desarrollo de temas listo para producción que lleva a Drupal a la misma liga que los frameworks modernos de JavaScript. La combinación de SDC, Tailwind, Vite y Storybook te da aislamiento de componentes, iteración rápida y una experiencia de desarrollo que realmente se siente bien.
El verdadero poder aquí no son solo las herramientas, es cómo trabajan juntas. Puedes prototipar en Storybook, estilizar con clases utilitarias y desplegar en Drupal sin cambiar de contexto. Tus componentes son portables, tus compilaciones son rápidas, y tu flujo de trabajo escala a medida que tu proyecto crece.
Comienza pequeño. Construye algunos componentes, familiarízate con el flujo de trabajo, luego expande tu biblioteca de componentes. Pronto te preguntarás cómo alguna vez construiste temas de Drupal de otra manera.
¡Feliz desarrollo de temas! 🎨