Skip to content

@fc/vitepress-plugin-mermaid ​

Plugin VitePress para renderização de diagramas Mermaid com modal de zoom/pan ao clicar.

Funcionalidades ​

  • Converte blocos ```mermaid ``` em elementos renderizados client-side
  • Valida a sintaxe durante o build, com arquivo e linha no erro
  • Renderiza sempre com securityLevel: 'strict'
  • Exibe fallback legível quando o renderer do navegador fica temporariamente indisponível
  • Suporte a tema claro/escuro sincronizado com o VitePress
  • Modal de visualização com zoom in/out, restaurar e arrastar (via panzoom)
  • Badge "⤢ ampliar" e anel de foco nos diagramas ao passar o mouse ou navegar por teclado
  • Tooltips nos botões do modal
  • Abre com clique, Enter ou Espaço; contém e restaura o foco; fecha com ESC ou clique fora

Instalação ​

Nas apps que consumirem o plugin, adicionar ao package.json:

json
"@fc/vitepress-plugin-mermaid": "workspace:*",
"panzoom": "catalog:docs"

Uso ​

1. config.ts — registrar o plugin markdown-it ​

ts
import { markdownPluginMermaid } from '@fc/vitepress-plugin-mermaid'
import { defineConfig } from 'vitepress'

export default defineConfig({
  markdown: {
    config: (md) => md.use(markdownPluginMermaid),
  },
})

O script de build do site deve usar scripts/vitepress-com-ambiente.mjs, que chama validarDiagramasMermaidEmDiretorio antes de iniciar o VitePress e bloqueia a publicação em caso de erro:

json
"build": "node ../../scripts/vitepress-com-ambiente.mjs build site"

Atenção: config.ts é carregado pelo Node.js puro, sem Vite. Por isso, importe apenas de @fc/vitepress-plugin-mermaid (sem /theme). O subpath /theme contém Vue e .vue e só pode ser importado em arquivos processados pelo Vite.

2. theme/index.ts — ativar renderização e modal ​

ts
import { useMermaidTheme } from '@fc/vitepress-plugin-mermaid/theme'
import '@fc/vitepress-plugin-mermaid/mermaid.css'
import DefaultTheme from 'vitepress/theme'
import MyLayout from './MyLayout.vue'

export default {
  extends: DefaultTheme,
  Layout: MyLayout,
  setup() {
    useMermaidTheme()
  },
}

3. theme/MyLayout.vue — montar o modal no layout ​

vue
<script setup>
import MermaidModal from '@fc/vitepress-plugin-mermaid/modal'
import DefaultTheme from 'vitepress/theme'
const { Layout } = DefaultTheme
</script>

<template>
  <Layout />
  <MermaidModal />
</template>

Exports ​

SubpathExportContexto
@fc/vitepress-plugin-mermaidmarkdownPluginMermaidNode.js / configuração
@fc/vitepress-plugin-mermaid/validacaovalidarDiagramasMermaidEmDiretorioNode.js / build
@fc/vitepress-plugin-mermaid/themeuseMermaidThemetheme/ (Vite / client-side)
@fc/vitepress-plugin-mermaid/modalexport default de MermaidModalMyLayout.vue
@fc/vitepress-plugin-mermaid/mermaid.cssestilos de hover dos diagramastheme/index.ts

Arquitetura interna ​

src/
  index.ts         ← plugin markdown-it (puro TS, importável no config.ts)
  validacao.ts     ← varredura e parser cacheado executados antes do build
  theme.ts         ← useMermaidTheme
  MermaidModal.vue ← componente modal com panzoom
  mermaidState.ts  ← ref<string|null> compartilhada entre theme.ts e MermaidModal
  mermaid.css      ← estilos .mermaid-clicavel (hover ring + badge "⤢ ampliar")

Decisões técnicas ​

  • Imports diretos por ambiente: o loader do VitePress carrega config.ts via jiti (Node.js puro), antes do Vite inicializar. Importar arquivos .vue nesse contexto causa ERR_UNKNOWN_FILE_EXTENSION. Por isso, plugin Markdown, validação Node, tema Vue e modal têm subpaths próprios, sem reexports que escondam a origem.
  • Dimensionamento pelo viewBox: removeAttribute('width/height') num SVG dentro de um div inline-block resulta em tamanho zero. O modal lê viewBox.baseVal para obter as dimensões naturais e escala proporcionalmente para caber em 88% do modal.
  • Reset sem reset(): panzoom v9 não expõe reset(). A restauração é feita com moveTo(0, 0) + zoomAbs(0, 0, 1).
  • Tooltips CSS: ::after { content: attr(title) } em vez do tooltip nativo do browser (delay de ~1s).
  • clip-path em vez de overflow: hidden: o container do modal usa clip-path: inset(0 round 12px) para preservar os cantos arredondados sem criar um contexto de clipping que cortaria os tooltips CSS da toolbar.
  • Fonte idempotente: a DSL original fica preservada no elemento e cada geração tem uma época; uma troca de rota ou tema mais nova invalida resultados assíncronos antigos.
  • Sintaxe bloqueada no build: o wrapper comum usa os tokens do markdown-it para encontrar somente cercas Mermaid reais, percorre páginas e includes, usa o parser oficial do Mermaid em ambiente DOM isolado do processo do VitePress, serializado e cacheado por fonte, e falha com arquivo e linha. Esse portão valida sintaxe; links e callbacks são neutralizados pela configuração explícita securityLevel: 'strict' no build e no navegador. O fallback do cliente cobre apenas indisponibilidade transitória do renderer.
  • Diálogo acessível: o wrapper do diagrama é um botão semântico nomeado por accTitle quando disponível; o modal recebe foco inicial, contém Tab/Shift+Tab e devolve o foco ao acionador ao fechar.