# Mermaid.js Buceo profundo: Cómo funciona realmente y cómo extenderlo

<!--category-- Mermaid, JavaScript, SVG, Diagrams -->
<datetime class="hidden">2025-11-09T16:00</datetime>

# Introducción

> NOTA: Esto es parte de mis experimentos con IA / una manera de gastar $1000 créditos de Código Calude Web. He alimentado esto un montón de documentos, mi comprensión, preguntas que tuve que generar este artículo. Es divertido y llena un vacío que no he visto llenado en ningún otro lugar.

> **Este post se basa en artículos anteriores:** Si aún no lo has hecho, echa un vistazo. [Añadir sirena.js con htmx](/blog/mermaidandhtmx), [Cambiar temas por Sirena](/blog/switchingthemesformermaid), y [Mejorando los diagramas de sirena con Pan/Zoom y Exportación](/blog/enhancingmermaiddiagramswithpanzoomandexport). Esta inmersión profunda explica los aspectos internos detrás de esas implementaciones.

Mermaid.js es realmente brillante. Escribir texto simple, obtener diagramas hermosos. No más balbucear con Visio o draw.io, perder archivos de origen, o mantener archivos de imagen separados. Todo vive en Markdown, versión controlada junto a su código.

Pero quería saber *¿Cómo?* En realidad funciona bajo el capó. ¿Cómo hace esto?

```mermaid
graph LR
    A[Text] --> B[Magic?]
    B --> C[Beautiful Diagram]
```

... convertirse en un SVG real? Y lo más importante, ¿cómo se puede conectar en él para añadir características como el pan/zoom, conmutación de temas, y la funcionalidad de exportación que construí para este sitio (ahora disponible como [@mostlylucid/mermaid-enhancements](https://www.npmjs.com/package/@mostlylucid/mermaid-enhancements))?

Después de investigar mucho a través del código fuente de Mermaid y construir extensiones reales, aquí está todo lo que aprendí acerca de cómo trabaja Mermaid internamente y cómo extenderlo correctamente.

[TOC]

# ¿Qué es Mermaid.js?

Sirena convierte las definiciones de texto en diagramas. Piensa en "Marcado para diagramas".

**A la vieja manera:**

1. Abrir herramienta de diagramación
2. Crear diagrama
3. Exportar como PNG
4. Incrustado en documentos
5. ¿Necesita actualizar? Encuentre el archivo fuente, edite, reexporte, reemplace la imagen...

**El camino de la sirena:**

1. Escribir diagrama en texto
2. Hecho

Actualízalo? Sólo edita el texto. Control de la versión? Es sólo texto! Funciona en marcado? Sí!

## Tipos de diagramas

Sirena soporta un número ridículo de tipos de diagramas:

```mermaid
graph LR
    A[Flowcharts] --> B[Sequence Diagrams]
    B --> C[Class Diagrams]
    C --> D[State Diagrams]
    D --> E[ER Diagrams]
    E --> F[Gantt Charts]
    F --> G[Pie Charts]
    G --> H[Git Graphs]
    H --> I[User Journeys]
    I --> J[And many more...]
```

Ver [los documentos de la sirena](https://mermaid.js.org/intro/) para la lista completa.

# Cómo funciona realmente la sirena: El oleoducto

Esto es lo que sucede cuando Sirena representa un diagrama:

```mermaid
graph TD
    A[Text Definition] --> B[Lexer/Tokenizer]
    B --> C[Parser]
    C --> D[AST Built]
    D --> E[Diagram Type Detected]
    E --> F[Type-Specific Renderer]
    F --> G[SVG Generated]
    G --> H[Inserted into DOM]
    H --> I[Your Enhancements Run]

    style A stroke:#059669,stroke-width:3px,color:#10b981
    style D stroke:#2563eb,stroke-width:3px,color:#3b82f6
    style G stroke:#7c3aed,stroke-width:3px,color:#8b5cf6
    style I stroke:#d97706,stroke-width:3px,color:#f59e0b
```

Vamos a romper cada paso.

## Paso 1: Definición de texto

Todo comienza con texto. Usted escribe diagramas en el DSL de Sirena (idioma específico del dominio):

```javascript
// Flowchart
const diagram = `
graph TD
    A[Start] --> B{Is it working?}
    B -->|Yes| C[Great!]
    B -->|No| D[Debug time]
`;
```

## Paso 2: Análisis léxico

El lexer rompe texto en tokens. Por ejemplo, esta línea:

```
A[Start] --> B{Decision}
```

Se convierte en fichas como:

```javascript
[
    { type: 'NODE_ID', value: 'A' },
    { type: 'NODE_TEXT', value: 'Start' },
    { type: 'ARROW', value: '-->' },
    { type: 'NODE_ID', value: 'B' },
    { type: 'NODE_TEXT', value: 'Decision' },
    { type: 'NODE_SHAPE', value: 'diamond' }  // from { }
]
```

## Paso 3: Análisis y generación AST

El analizador consume tokens y construye un Abstract Syntax Tree (AST):

```javascript
// Simplified AST structure
{
    type: 'flowchart',
    direction: 'TD',
    nodes: [
        { id: 'A', text: 'Start', shape: 'rect' },
        { id: 'B', text: 'Decision', shape: 'diamond' }
    ],
    edges: [
        { from: 'A', to: 'B', type: 'arrow' }
    ]
}
```

Sirena utiliza diferentes analizadores para cada tipo de diagrama. Estos se generan a menudo a partir de archivos gramaticales usando [Jison](https://github.com/zaach/jison) (como Yacc/Bison para JavaScript).

## Paso 4: Detección de diagramas

Sirena detecta el tipo de diagrama desde la primera línea:

```javascript
// Simplified detection logic
if (text.match(/^\s*graph/)) return 'flowchart';
if (text.match(/^\s*sequenceDiagram/)) return 'sequence';
if (text.match(/^\s*classDiagram/)) return 'class';
// ... etc
```

## Paso 5: Renderización específica de tipo

Cada tipo de diagrama tiene su propio renderizador. El renderizador toma el AST y genera elementos SVG.

Para diagramas de flujo, Sirena utiliza el [Dagre](https://github.com/dagrejs/dagre) biblioteca para el diseño de gráficos. Para otros, utiliza algoritmos personalizados o bibliotecas como Cytoscape.

```javascript
// Simplified flowchart renderer
export const draw = function (text, id, version, diagObj) {
    const graph = diagObj.db;  // The AST
    const svg = d3.select(`#${id}`);

    // Render nodes
    graph.getVertices().forEach(vertex => {
        drawNode(svg, vertex);
    });

    // Render edges
    graph.getEdges().forEach(edge => {
        drawEdge(svg, edge);
    });

    // Apply layout algorithm
    dagre.layout(graph);
};
```

## Paso 6: Generación SVG

El renderizador produce el marcado SVG:

```xml
<svg xmlns="http://www.w3.org/2000/svg">
    <g class="node">
        <rect x="0" y="0" width="100" height="50"/>
        <text x="50" y="25">Start</text>
    </g>
    <g class="edge">
        <path d="M 100 25 L 200 25" stroke="#333"/>
    </g>
</svg>
```

## Paso 7: Inserción de DOM

Sirena encuentra todo `.mermaid` elementos y los reemplaza con SVG renderizado:

```javascript
// From mermaid.ts
export const init = async function (config, nodes) {
    const nodesToProcess = nodes || document.querySelectorAll('.mermaid');

    for (const node of nodesToProcess) {
        const id = `mermaid-${Date.now()}-${Math.random()}`;
        const txt = node.textContent;

        const { svg } = await render(id, txt);
        node.innerHTML = svg;
    }
};
```

## Paso 8: Post-Procesamiento (donde usted entra)

Después de Sirena inserta el SVG, se puede mejorar. Aquí es donde todas mis mejoras gancho en:

- Funcionalidad de pan/zoom
- Botones de control
- Capacidades de exportación
- Cambio de tema

Más sobre esto abajo.

# Extender Sirena: Los puntos de extensión

Ahora que sabemos cómo trabaja Sirena, exploremos cómo extenderla.

## 1. Configuración

La extensión más básica es la configuración:

```javascript
import mermaid from 'mermaid';

mermaid.initialize({
    startOnLoad: true,
    theme: 'dark',
    securityLevel: 'loose',
    flowchart: {
        curve: 'basis',
        padding: 15
    }
});
```

## 2. Personalización del tema

He cubierto esto extensamente en [Cambiar temas por Sirena](/blog/switchingthemesformermaid), pero aquí está la implementación clave:

### El problema con el cambio de tema

Sirena necesita ser inicializada con un tema, y no puede cambiarlo después. PERO si desea volver a renderizar diagramas con un tema nuevo, necesita la fuente original del diagrama, que Sirena *no almacena en el DOM*.

### La solución

Almacene el contenido original antes de renderizar, luego restaure y vuelva a renderizar cuando cambie de tema:

```javascript
// From my theme-switcher implementation
const originalData = new Map();

// Save original content before first render
const saveOriginalData = async () => {
    const elements = document.querySelectorAll('.mermaid');
    elements.forEach(element => {
        const id = element.id || `mermaid-${Date.now()}`;
        element.id = id;

        // Store the original diagram source
        if (!originalData.has(id)) {
            originalData.set(id, element.textContent?.trim());
        }
    });
};

// When theme changes, restore and re-render
const loadMermaid = async (theme) => {
    mermaid.initialize({
        startOnLoad: false,
        theme: theme
    });

    const elements = document.querySelectorAll('.mermaid');
    for (const element of elements) {
        const source = originalData.get(element.id);
        if (source) {
            element.innerHTML = '';  // Clear
            element.removeAttribute('data-processed');

            const { svg } = await mermaid.render(
                `mermaid-svg-${element.id}`,
                source
            );
            element.innerHTML = svg;
        }
    }
};
```

**Métodos de detección de múltiples temas** (sitios manejan temas de manera diferente):

```javascript
function detectTheme() {
    // Check various sources
    if (typeof window.__themeState !== 'undefined') {
        return window.__themeState;
    }
    if (localStorage.theme) {
        return localStorage.theme;
    }
    if (document.documentElement.classList.contains('dark')) {
        return 'dark';
    }
    if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
        return 'dark';
    }
    return 'light';
}
```

Ver [el código completo del conmutador del tema](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/src/js/memmaid_theme_switch.js) para más detalles.

## 3. Mejoras posteriores a los resultados

Aquí es donde ocurre la magia real. Después de los renders de Sirena, puedes añadir funciones interactivas.

He cubierto esto extensamente en [Mejorando los diagramas de sirena con Pan/Zoom y Exportación](/blog/enhancingmermaiddiagramswithpanzoomandexport), así que voy a destacar las técnicas clave aquí.

### Diagramas de envoltorio

Crear un contenedor de envoltura para los controles:

```javascript
function wrapDiagram(element) {
    if (element.closest('.mermaid-wrapper')) {
        return element.closest('.mermaid-wrapper');
    }

    const wrapper = document.createElement('div');
    wrapper.className = 'mermaid-wrapper';
    wrapper.id = `wrapper-${element.id}`;

    element.parentNode.insertBefore(wrapper, element);
    wrapper.appendChild(element);

    return wrapper;
}
```

### Añadiendo Pan/Zoom

Uso [svg-pan-zoom](https://github.com/bumbu/svg-pan-zoom):

```javascript
import svgPanZoom from 'svg-pan-zoom';

const panZoomInstances = new Map();

function initPanZoom(svgElement, diagramId) {
    // Clean up existing instance
    if (panZoomInstances.has(diagramId)) {
        panZoomInstances.get(diagramId).destroy();
        panZoomInstances.delete(diagramId);
    }

    const instance = svgPanZoom(svgElement, {
        zoomEnabled: true,
        controlIconsEnabled: false,
        fit: true,
        center: true,
        minZoom: 0.1,
        maxZoom: 10
    });

    panZoomInstances.set(diagramId, instance);
    return instance;
}
```

### Botones de control

Crear panel de control flotante:

```javascript
function createControlButtons(container, diagramId) {
    const controlsDiv = document.createElement('div');
    controlsDiv.className = 'mermaid-controls';

    const buttons = [
        { icon: 'bx-fullscreen', title: 'Fullscreen', action: 'fullscreen' },
        { icon: 'bx-zoom-in', title: 'Zoom In', action: 'zoomIn' },
        { icon: 'bx-zoom-out', title: 'Zoom Out', action: 'zoomOut' },
        { icon: 'bx-reset', title: 'Reset', action: 'reset' },
        { icon: 'bx-move', title: 'Pan', action: 'pan' },
        { icon: 'bx-image', title: 'Export PNG', action: 'exportPng' },
        { icon: 'bx-code-alt', title: 'Export SVG', action: 'exportSvg' }
    ];

    buttons.forEach(btn => {
        const button = document.createElement('button');
        button.className = `mermaid-control-btn bx ${btn.icon}`;
        button.setAttribute('data-action', btn.action);
        button.setAttribute('data-diagram-id', diagramId);
        controlsDiv.appendChild(button);
    });

    container.appendChild(controlsDiv);
}
```

### Delegación de eventos (¡Performance!)

No adjunte oyentes a cada botón. Utilice la delegación de eventos:

```javascript
document.addEventListener('click', (e) => {
    const target = e.target;
    if (!target.classList.contains('mermaid-control-btn')) return;

    const action = target.getAttribute('data-action');
    const diagramId = target.getAttribute('data-diagram-id');
    const panZoom = panZoomInstances.get(diagramId);

    switch (action) {
        case 'zoomIn': panZoom?.zoomIn(); break;
        case 'zoomOut': panZoom?.zoomOut(); break;
        case 'reset': panZoom?.reset(); break;
        // ... etc
    }
});
```

### Funcionalidad de exportación

**El desafío:** Los elementos SVG tienen dimensionamiento dinámico, transforma pan/zoom y estilos heredados. Para exportar correctamente, necesita:

1. Clonar el SVG
2. Preservar las dimensiones
3. Eliminar transforms
4. Convertir en PNG o SVG

Uso [html-to-image](https://github.com/bubkoo/html-to-image):

```javascript
import { toPng, toSvg } from 'html-to-image';

async function exportDiagram(container, format, diagramId) {
    const svgElement = container.querySelector('svg');
    if (!svgElement) return;

    // Clone to avoid modifying original
    const clonedSvg = svgElement.cloneNode(true);

    // Get or calculate viewBox
    let viewBox = clonedSvg.getAttribute('viewBox');
    if (!viewBox) {
        const bbox = svgElement.getBBox();
        viewBox = `${bbox.x} ${bbox.y} ${bbox.width} ${bbox.height}`;
        clonedSvg.setAttribute('viewBox', viewBox);
    }

    // Set explicit dimensions
    const [, , width, height] = viewBox.split(' ').map(Number);
    clonedSvg.setAttribute('width', width);
    clonedSvg.setAttribute('height', height);

    // Remove pan-zoom transforms
    clonedSvg.removeAttribute('style');

    // Create off-screen container
    const temp = document.createElement('div');
    temp.style.position = 'absolute';
    temp.style.left = '-9999px';
    temp.appendChild(clonedSvg);
    document.body.appendChild(temp);

    // Export
    const dataUrl = format === 'png'
        ? await toPng(clonedSvg, { pixelRatio: 2 })
        : await toSvg(clonedSvg);

    downloadFile(dataUrl, `diagram-${Date.now()}.${format}`);

    // Cleanup
    document.body.removeChild(temp);
}
```

**Detalles críticos:**

- **Preservar vistaBox** - Captura todo el diagrama, no sólo la porción visible
- **Renderizado fuera de pantalla** - Evite afectar el diagrama mostrado
- **pixelRatio: 2** - High-DPI para las exportaciones de PNG crujientes

Ver [el código de exportación completo](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/src/js/mermaid_enhancements.js#L148) para más detalles.

# El paquete completo de mejora

He empaquetado todas estas mejoras como [@mostlylucid/mermaid-enhancements](https://www.npmjs.com/package/@mostlylucid/mermaid-enhancements). Véase [Publicación de mejoras de sirena como un paquete npm](/blog/publishingmermaidenhancementsnpm) para más detalles.

Así es como todo encaja:

```mermaid
graph TB
    A[User Initializes] --> B[init Function]
    B --> C[initMermaid]
    B --> D[enhanceMermaidDiagrams]

    C --> E[Theme Detection]
    C --> F[Event Listeners]
    C --> G[Mermaid Rendering]

    E --> E1[Global State]
    E --> E2[LocalStorage]
    E --> E3[DOM Class]
    E --> E4[OS Preference]

    F --> F1[Custom Events]
    F --> F2[Media Query]

    G --> H[Apply Enhancements]
    D --> H

    H --> I[Wrap Diagrams]
    H --> J[Init Pan/Zoom]
    H --> K[Add Controls]

    I --> L[Interactive Diagram]
    J --> L
    K --> L

    L --> M[User Interactions]
    M --> M1[Zoom In/Out]
    M --> M2[Pan]
    M --> M3[Fullscreen]
    M --> M4[Export PNG/SVG]

    style A stroke:#059669,stroke-width:3px,color:#10b981
    style L stroke:#2563eb,stroke-width:3px,color:#3b82f6
    style M stroke:#7c3aed,stroke-width:3px,color:#8b5cf6
```

## Uso

```bash
npm install @mostlylucid/mermaid-enhancements
```

```typescript
import { init } from '@mostlylucid/mermaid-enhancements';
import '@mostlylucid/mermaid-enhancements/styles.css';

await init();
```

Tus diagramas de Sirena ahora tienen:

- • Pan/zoom interactivo
- • Caja de luz de pantalla completa
- • Exportación de PNG/SVG
- • Conmutación automática del tema
- Diseño responsivo

# Integración con HTMX

Como cubrí en [Añadir sirena.js con htmx](/blog/mermaidandhtmx), es necesario volver a iniciar Mermaid después de los intercambios de contenido HTMX:

```javascript
// On page load
document.addEventListener('DOMContentLoaded', function () {
    mermaid.initialize({ startOnLoad: true });
});

// After HTMX swaps content
document.body.addEventListener('htmx:afterSwap', function(evt) {
    mermaid.run();
});
```

Con el paquete de mejoras:

```javascript
import { init, enhanceMermaidDiagrams } from '@mostlylucid/mermaid-enhancements';

// Initial load
await init();

// After HTMX swap
document.body.addEventListener('htmx:afterSwap', async function() {
    await init();  // Re-init Mermaid with current theme
    enhanceMermaidDiagrams();  // Re-apply enhancements
});
```

# Mejores prácticas que aprendí

Después de construir este material y depurar casos de bordes extraños, esto es lo que funciona:

## 1. Almacenar siempre el contenido original

La sirena no conserva la fuente original del diagrama después de la renderización.

```javascript
const originalData = new Map();

// Before first render
element.setAttribute('data-original-code', element.textContent);
originalData.set(element.id, element.textContent);

// When re-rendering
element.innerHTML = originalData.get(element.id);
```

## 2. Limpiar las instancias

Las fugas de memoria son reales. Destruya instancias antes de crear nuevas:

```javascript
if (panZoomInstances.has(id)) {
    try {
        panZoomInstances.get(id).destroy();
    } catch (e) {
        console.warn('Failed to destroy:', e);
    }
    panZoomInstances.delete(id);
}
```

## 3. Usar delegación de eventos

No adjunte los oyentes a botones individuales:

```javascript
// ❌ Don't do this
buttons.forEach(btn => {
    btn.addEventListener('click', handler);
});

// ✅ Do this
document.addEventListener('click', (e) => {
    if (e.target.matches('.mermaid-control-btn')) {
        handleClick(e.target);
    }
});
```

## 4. Manipule el cargador de cohetes Cloudflare

Rocket Loader retrasa la ejecución de JavaScript.

```javascript
function waitForDependencies(maxAttempts = 50) {
    return new Promise((resolve) => {
        let attempts = 0;

        const check = () => {
            if (window.mermaid && window.htmx && window.Alpine) {
                resolve();
            } else if (attempts >= maxAttempts) {
                resolve();  // Give up
            } else {
                attempts++;
                setTimeout(check, Math.min(50 * Math.pow(1.2, attempts), 500));
            }
        };

        check();
    });
}
```

Y excluya su guión principal de Rocket Loader:

```html
<script src="main.js" data-cfasync="false"></script>
```

## 5. El tiempo es todo

Uso `requestAnimationFrame` para un mejor momento que arbitrario `setTimeout`:

```javascript
// After Mermaid renders
await mermaid.run();

// Wait for paint before enhancing
await new Promise(resolve => {
    requestAnimationFrame(() => {
        requestAnimationFrame(() => {
            enhanceMermaidDiagrams();
            resolve();
        });
    });
});
```

## 6. Manipulación defensiva de SVG

Los SVG pueden ser raros.

```javascript
const svgElement = container.querySelector('svg');
if (!svgElement) {
    console.warn('No SVG found');
    return;
}

// Clone before modifying
const cloned = svgElement.cloneNode(true);

// Ensure viewBox exists
let viewBox = cloned.getAttribute('viewBox');
if (!viewBox) {
    const bbox = svgElement.getBBox();
    viewBox = `${bbox.x} ${bbox.y} ${bbox.width} ${bbox.height}`;
}
```

# Consejos de depuración

## Salida de la consola

Con la inicialización adecuada, usted debe ver:

```
Saving original data
Loading mermaid with theme: dark
Mermaid initialized
Enhanced 3 diagrams
```

## Lista de comprobación de pruebas

Después de implementar mejoras:

- [ ] Diagramas renderizados en la carga de la página
- [ ] Controles de pan/zoom funcionan
- [ ] Pantalla completa se abre/se cierra (X, haga clic fuera, ESC)
- [ ] Exportación de PNG captura diagrama completo
- [ Exportación SVG preserva vectores
- [ ] Funciona después del intercambio de contenido HTMX
- [ ] El tema de conmutación re-renders correctamente
- [ # Móvil sensible #
- [ Estilo de modo oscuro
- [ ] Accesibilidad del teclado

## Cuestiones comunes

**Diagrama no renderizado:**

- Comprobar si hay errores en la consola del navegador
- Verificar sirena cargada (`window.mermaid`)
- Comprobar la sintaxis del diagrama

**Sin trabajar en pan ni en zoom:**

- Verificar inicializado svg-pan-zoom
- Comprobar si CSS está en conflicto (`pointer-events: none`)
- Inspeccionar mapa de instancia

**Exportar capturas sólo esquina:**

- Falta la preservación de la vistaBox
- Transformar no eliminado
- Comprobar la lógica de clonación

**Tema que no cambia:**

- Datos originales no guardados
- `data-processed` no restablecer
- Los oyentes de eventos no están registrados

# Consideraciones sobre el desempeño

## Inicialización perezosa

No cargue mejoras hasta que sea necesario:

```javascript
let enhancementsLoaded = false;

async function loadEnhancements() {
    if (enhancementsLoaded) return;

    const { enhanceMermaidDiagrams } = await import('./enhancements.js');
    enhanceMermaidDiagrams();
    enhancementsLoaded = true;
}

// Load on first interaction
document.addEventListener('click', (e) => {
    if (e.target.closest('.mermaid')) {
        loadEnhancements();
    }
}, { once: true });
```

## Observador de Intersección

Diagramas de renderización sólo cuando se pueden ver:

```javascript
const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
        if (entry.isIntersecting) {
            renderDiagram(entry.target);
            observer.unobserve(entry.target);
        }
    });
}, { rootMargin: '100px' });

document.querySelectorAll('.mermaid').forEach(el => {
    observer.observe(el);
});
```

## Debounce Re-renders

En el cambio de tamaño o tema:

```javascript
let timeout;
window.addEventListener('resize', () => {
    clearTimeout(timeout);
    timeout = setTimeout(() => {
        panZoomInstances.forEach(instance => {
            instance.resize();
            instance.fit();
        });
    }, 250);
});
```

# Conclusión

Mermaid.js es fantástico fuera de la caja, pero entender cómo funciona internamente le permite construir algunas mejoras realmente geniales.

1. **Pipeline de renderización**: Texto → Lexer → Parser → AST → Renderer → SVG
2. **Puntos de extensión**: Config, temas, mejoras post-render
3. **Almacenar datos originales**La sirena no lo hace por ti.
4. **Limpiar los recursos**: Las fugas de memoria son reales
5. **Delegación del evento**: Mejor rendimiento
6. **Fuentes temáticas múltiples**: Los sitios manejan los temas de manera diferente
7. **Cuestiones relativas al calendario**: Usar `requestAnimationFrame`

Todas las técnicas que he cubierto aquí se utilizan en la producción en este sitio y empaquetado en [@mostlylucid/mermaid-enhancements](https://www.npmjs.com/package/@mostlylucid/mermaid-enhancements). La fuente completa está disponible en [En su mayoríalucidweb/en su mayoríalucid-mermaid](https://github.com/scottgal/mostlylucidweb/tree/main/mostlylucid-mermaid).

## Puestos relacionados

- [Añadir sirena.js con htmx](/blog/mermaidandhtmx)
- [Cambiar temas por Sirena](/blog/switchingthemesformermaid)
- [Mejorando los diagramas de sirena con Pan/Zoom y Exportación](/blog/enhancingmermaiddiagramswithpanzoomandexport)
- [Publicación de mejoras de sirena como un paquete npm](/blog/publishingmermaidenhancementsnpm)

## Recursos

- [Sirena.js Documentación](https://mermaid.js.org/)
- [Sirena GitHub](https://github.com/mermaid-js/mermaid)
- [svg-pan-zoom](https://github.com/bumbu/svg-pan-zoom)
- [html-to-image](https://github.com/bubkoo/html-to-image)
- [Mi paquete de mejoras de sirena](https://www.npmjs.com/package/@mostlylucid/mermaid-enhancements)

¡Prueba los controles en los diagramas de arriba!