Butecho

PaposCauso

Um JSON vestido de Markdown, com tema claro e escuro

O json-markdown-viewer é um projeto de dois dias que transforma um objeto em bloco de código colorido, e a parte mais chata foi o tema.

João · · 3 min de leitura

Nem todo projeto é grande. O json-markdown-viewer tem cinco commits, feitos entre 9 e 10 de maio de 2024. Mas tem duas ideias pequenas que valem a conversa.

O que ele faz

Pega um objeto JavaScript e mostra como um bloco de código JSON colorido, com botão pra alternar entre tema claro e escuro. A página de exemplo renderiza um mini perfil:

const personalInfo = {
  location: "Brazil",
  age: 24,
  experience: "4+ years",
  stack: ["React", "Node", "Next", "TypeScript"],
};

É Next.js 14 com Tailwind, react-markdown, rehype-highlight e next-themes.

Ideia 1: não escrever um syntax highlighter

O caminho óbvio seria percorrer o objeto e gerar <span> colorido pra cada chave e valor. O projeto faz o contrário. Ele transforma o objeto num texto Markdown com uma cerca de código (aqui sem o try/catch que envolve a linha):

export function getMarkdownStringFromObj(content: Record<string, unknown>): string {
  return "```json\n" + JSON.stringify(content, null, 2) + "\n```";
}

A partir daí, quem trabalha é o ecossistema. O react-markdown renderiza o bloco e o rehype-highlight faz o highlight de JSON. Zero lógica de tokenização escrita à mão.

Ideia 2: prefixo nas classes

O rehype-highlight gera classes como hljs-string e hljs-number. Qualquer tema do highlight.js na mesma página pinta essas classes também.

A opção prefix troca o prefixo. O componente recebe ele por prop:

<ReactMarkdown
  rehypePlugins={[() => rehypeHighlight({ prefix: classPrefix })]}
  children={content}
/>

Com classPrefix="json-md-", as classes viram json-md-string, json-md-attr e assim por diante. O CSS é só do componente, e cada cor tem a versão clara e a escura:

.json-md-string { @apply text-green-600; }
.dark .json-md-string { @apply text-blue-400; }

Componente que carrega o próprio namespace de CSS é um hábito que eu recomendo pra qualquer coisa que vai ser embutida em outro lugar.

A parte chata: hidratação

O último commit se chama “Fix hydration error”. Quem já usou tema com SSR conhece esse.

O next-themes decide o tema no cliente, olhando o localStorage e a preferência do sistema. No servidor, ele não sabe qual é. Então o HTML que o servidor manda e o que o React monta no cliente divergem, e o React reclama.

A correção teve duas partes. Na raiz, suppressHydrationWarning no <html>, porque o next-themes mexe na classe dele antes do React hidratar:

<html lang="en" suppressHydrationWarning>

E no botão de tema, que mostra o tema atual, o componente só renderiza o conteúdo de verdade depois de montar no cliente:

const [isClient, setIsClient] = useState(false);
useEffect(() => { setIsClient(true); }, []);
if (!isClient) return <div>Loading Component</div>;

É o padrão de “espere montar” que a própria documentação do next-themes sugere. Tem um custo: o botão pisca um placeholder no primeiro paint. Pra um botão, aceitável.

O que ficou faltando

O README ainda é o do create-next-app. Não tem entrada de dados: o objeto está fixo no código da página. E o try/catch em volta do JSON.stringify loga e devolve string vazia, o que esconde o caso de objeto com referência circular.

É um projeto de dois dias e está tudo bem ser isso. O código está no GitHub.

A saideira

Sai pelo Substack. Dá pra cancelar quando quiser.

Mais papo na mesa

balcãoCtrl K
$ echo "puxa uma cadeira"
puxa uma cadeira
Terminal de verdade: digita help ou usa um atalho.