Saltar al contenido principal
Login

Desarrollo moderno de temas para Drupal: trabajando con SDC, Tailwind, Vite y Storybook

Denys Kravchenko profile picture
por Denys Kravchenko
tema moderno de Drupal con SDC y Storybook

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:

bash
mkdir -p web/themes/custom

Ahora 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:

bash
ddev drush generate theme

Esto 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:

bash
ddev drush theme:install blog_sdc -y
ddev drush config-set system.theme default blog_sdc -y
ddev drush config-get system.theme default

Ese ú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:

bash
ddev drush generate sdc

Responde 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:

bash
ddev drush cr

Haciendo 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:

bash
cp web/sites/default/default.services.yml web/sites/default/services.yml

Abre 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:

yaml
parameters:
  twig.config:
    debug: true
    auto_reload: true
    cache: false

Estas 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:

bash
ddev composer require drupal/devel drupal/twig_tweak 'drupal/kint-kint:^2.2'
ddev drush en devel devel_generate twig_tweak kint -y

Estos 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:

bash
ddev drush devel-generate:content 1 --bundles=article

Encuentra el ID de nodo de tu artículo (necesitarás esto):

bash
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:

bash
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.twig

Abre templates/node/node--article.html.twig y reemplaza todo con esta única declaración include:

plaintext
{% 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:

yaml
22 lines
'$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 URL

Limpia el caché nuevamente:

bash
ddev drush cr

Desarrollando el template del componente

Antes de agregar Tailwind, asegurémonos de que nuestro componente realmente muestre algo. Edita components/card/card.twig:

plaintext
<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):

css
33 lines
[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:

bash
ddev drush cr

Visita 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:

bash
cd web/themes/custom/blog_sdc
bun init -y

Instala las dependencias que necesitamos:

bash
bun add -D vite @tailwindcss/vite tailwindcss
bun add -D prettier prettier-plugin-tailwindcss @ttskch/prettier-plugin-tailwindcss-anywhere

Prettier 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:

json
{
  "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:

jsx
29 lines
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:

bash
mkdir -p src

Crea src/main.css con directivas de Tailwind:

css
@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:

jsx
44 lines
/**
 * @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:

json
{
  "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:

bash
rm components/card/card.css

Ahora reconstruyamos el componente card con utilidades de Tailwind. Edita components/card/card.twig:

plaintext
37 lines
<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:

jsx
25 lines
/**
 * @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:

bash
bun run build

Presta atención a la salida. Verás algo como:

plaintext
dist/main-BuEGDbdA.css
dist/main-BicIW-eB.js

Esos 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:

yaml
# 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:

yaml
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-main

Limpia el caché una vez más:

bash
ddev drush cr

¿Quieres que tu código se vea bonito? Ejecuta el formateador:

bash
bun run format

Agregando 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:

bash
npx storybook@latest init --builder vite --type html --no-dev --yes

Limpia las stories de ejemplo que incluyen:

bash
rm -rf stories

Instala el accessibility addon (esto es muy útil):

bash
bun add -D @storybook/addon-a11y@10.0.7

Elimina las configuraciones de TypeScript (lo mantendremos simple):

bash
rm .storybook/main.ts .storybook/preview.ts

Crea .storybook/main.js:

jsx
23 lines
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:

jsx
34 lines
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):

jsx
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:

jsx
92 lines
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:

json
{
  "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:

bash
bun run storybook

Abre 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.

Storybook 10 running in browser

¿Quieres compilar una versión estática para despliegue o compartir con tu equipo?

bash
bun run build-storybook

Tu 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:

  1. Genera el componente:

bash
ddev drush generate sdc
# Theme: blog_sdc, Component: Button, machine: button
  • Elimina el archivo CSS (estamos usando Tailwind, ¿recuerdas?):

  • bash
    rm components/button/button.css
  • Estilízalo con Tailwind en components/button/button.twig:

  • plaintext
    <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:

  • jsx
    export default function initButton() {
      console.log('Button component initialized');
      // Your button logic here
    }
  • Crea una story en components/button/button.stories.js con diferentes variantes
  • Pruébalo en Storybook:

  • bash
    bun run storybook
  • Compila tus recursos:

  • bash
    bun run build
  • Actualiza los hashes de la biblioteca en blog_sdc.libraries.yml con los nuevos valores de la salida de compilación
  • Limpia el caché de Drupal:

  • bash
    ddev drush cr
  • Formatea tu código:

  • bash
    bun run format

    Comandos diarios comunes

    Estos son los comandos que usarás constantemente:

    bash
    # 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_sdc

    Solució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.yml para la sección libraries:
    • 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 install para reinstalar todo
    • Verifica que src/main.js y src/main.css existan
    • Verifica que vite.config.js esté 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_modules y ejecuta bun install de nuevo
    • Asegúrate de que nada más esté usando el puerto 6006
    • Verifica que .storybook/main.js y .storybook/preview.js existan

    Los cambios no aparecen

    El problema clásico. Prueba esto en orden:

    1. Limpia el caché de Drupal: ddev drush cr
    2. Recarga forzada de tu navegador: Cmd+Shift+R en Mac o Ctrl+Shift+R en Windows/Linux
    3. Verifica la consola de tu navegador para errores de JavaScript
    4. 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:

    bash
    32 lines
    # 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-storybook

    Mejores prácticas

    Desarrollo de temas

    1. Usa un tema base como stable9 para una base sólida
    2. Limpia el caché frecuentemente con drush cr (en serio, haz esto a menudo)
    3. Mantén los componentes SDC enfocados y reutilizables
    4. Sigue las convenciones de nomenclatura: minúsculas con guiones bajos
    5. Prueba antes de desplegar a producción

    Desarrollo de componentes

    1. Elimina los archivos CSS generados cuando uses Tailwind
    2. Almacena el JavaScript de componentes junto a los templates Twig
    3. Documenta props en los metadatos de component.yml
    4. Usa versionado semántico para dependencias
    5. Mantén los componentes organizados y enfocados

    Proceso de compilación

    1. Siempre usa bun run build para despliegues de producción
    2. Actualiza los hashes de biblioteca después de cada compilación
    3. Aprovecha HMR con bun run dev durante el desarrollo
    4. Formatea el código antes de hacer commit con bun run format
    5. Compila para producción antes de enviar cambios

    Mejores prácticas de Storybook

    1. Un archivo de story por componente
    2. Usa nombres descriptivos para las stories
    3. Prueba casos extremos (estados vacíos, errores, carga)
    4. Habilita autodocs con tags: ['autodocs']
    5. Siempre verifica el panel a11y antes de hacer commit
    6. Prueba componentes en diferentes colores de fondo
    7. 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! 🎨