cover

Usando Markdoc con Remix: la pareja perfecta

author photo

Héctorbliss

@hectorbliss


robot logo saying hello

No te quedes atrás: Actualízate

Suscríbete para recibir información sobre nuevos frameworks, updates, eventos, tips, hacks y más.

Markdoc es un nuevo “parser” de Markdown hecho por Stripe.

Stripe lo ha publicado como un paquete open source y presume de su simplicidad, misma que le da vida a su plataforma de documentación, que por si no la has visitado: es una de las mejores de toda la web.

Nosotros también queremos usar Markdoc leer nuestro markdown desde los posts de nuestra base de datos y parsear desde el servidor, aligerando con esto la velocidad de entrega de cada uno de nuestros post de blog.

Así que vamos a instalarlo, configurarlo y usarlo e incluso agregaremos “Prismjs” para que el código en nuestros post se vea muy bien.

Instalando

Solo necesitamos instalar markdoc desde su mono repo:

npm i @markdoc/markdoc

Recuerda que puedes usar el gestor de paquetes de tu preferencia (pnpm, yarn etc.).

Configurando del lado del servidor

Comencemos creando nuestra función (”utility”) que tomará el markdown desde el atributo .body de nuestros posts que ya tenemos en la base de datos, lo leerá y lo convertirá (parsing) a un árbol de nodos que se pueden renderizar.

👀 Este árbol de nodos renderizable, primero es un “ast”(abstract syntax tree) que es una representación de un texto, regularmente código en un lenguaje específico que se representa como un diagrama de árbol, normalmente con formato JSON. Después es convertido a un arbol renderizable de nodos(RenderableTreeNodes).

Este es el proceso tal cual, así de simple, pues el trabajo de convertir ese “RenderableTreeNodes” en JSX será del componente del cliente.

Escribámoslo pues:

// app/utils/markdoc.server.ts import { parse, transform, type RenderableTreeNodes } from "@markdoc/markdoc"; export function markdownParser(markdown: string): RenderableTreeNodes { return transform(parse(markdown)); }

Observa que nuestro archivo lleva el extra .server en la extensión. Con esto nos aseguramos de que este código jamás salga del servidor, ni por accidente.

¡Muy bien! esto lo usaremos en nuestro loader que se encarga de conseguir el post.

Pero ahora, vamos a construir el componente que transformará este RenderableTreeNodes.

Configurando el componente del cliente

Recuerda que lo que nuestro parser devuelve es un “Arbol de nodos renderizables” (RenderableTreeNodes) que nuestro loader entregará a el cliente. Es entonces tarea del cliente convertir estos nodos, de formato JSON a componentes JSX.

// app/components/markdoc.tsx import { renderers, type RenderableTreeNodes } from "@markdoc/markdoc"; import * as React from "react"; export function ContentToJSX({ content }: { content: RenderableTreeNodes }) { return <>{renderers.react(content, React)}</>; }

Este componente recibe como prop content que debe venir de nuestro loader, para devolver JSX válido para React. De esta forma, no tenemos que lidiar con HTML en ninguna etapa de esta transformación, el texto en formato markdown pasa de nuestra base de datos a convertirse en un árbol de nodos (JSON) en el servidor, para luego convertirse en JSX en el cliente.

Remix junto con React hacen el trabajo de siempre editándonos el "dangerouslySetInnerHTML" .

Solo nos falta una cosa más: Syntax Highlighting para nuestro código. 🤓 Porque, qué sería de un blog de programación sin poder mostrar nuestro código bonito. 👩🏻‍💻

Agregando Prism.js

Prism es igualmente usado por Stripe para destacar los bloques de código en su documentación, además de que Markdoc es 100% compatible con Prism, sin mencionar que Prism es muy fácil de usar, pues solo se trata de una hoja de estilos y un archivo js en el cliente.

👀 No vamos a usar el archivo JS del cliente de Prism. Únicamente nos interesa la hoja de estilos, pero, si quieres saber más sobre el uso avanzado de Prism. Aquí te dejo su documentación oficial.

Desde el sitio web oficial: prismjs.com, vamos a descargar nuestra hoja de estilos en versión “minified”. Lo mínimo está seleccionado (JavaScript) pero no olvides seleccionar los lenguajes de tu preferencia y tu tema favorito; yo seleccionaré “coy” en esta ocasión. Ahora, descargamos el archivo .css para instalarlo en Remix.

prism.png

Creando el detalle del blog

Vamos a crear una ruta dinámica en Remix, que nos permita mostrar el detalle de nuestro blog, es decir, aquí es donde nuestro lector disfrutará de nuestro contenido. 📖

Para ello creamos la siguiente ruta y agregamos los estilos de prism que hemos descargado en la función links:

import type { LinksFunction } from "@remix-run/cloudflare"; import prismStyles from "~/styles/prismjs.css"; export const links: LinksFunction = () => [ { rel: "stylesheet", href: prismStyles }, ]; export default function BlogDetail() { return ( <> // ... </> ); }

Bien. Ahora vamos a escribir el loader que se encargará de conseguir nuestro post, convertirlo en un “ast” y entregarlo al cliente.

export const loader: LoaderFunction = async ({ params }) => { const post = await db.post.findUnique({ where: { slug: params.slug }, select: { title: true, createdAt: true, body: true, }, }); const content = await markdownParser(post.body); return json({ title: post.title, date: post.createdAt, content }); };

Perfecto. Toma nota del return del loader.

Ahora vamos a usar nuestro componente para renderizar correctamente nuestro árbol de nodos.

export default function BlogDetail() { const { content, title, date } = useLoaderData<typeof loader>(); return ( <> <h2>{title}</h2> <span>{new Date(date).toLocaleDateString()}</span> <ContentToJSX content={content} /> </> ); }

¡Y ya está, tu blog ha cobrado vida! 🎉 Y no fue tan difícil ¿o si?

Este es el código completo de la ruta de nuestro blog:

import { db } from "~/utils/db.server"; import { type LinksFunction, type LoaderFunction, json, } from "@remix-run/cloudflare"; import { useLoaderData } from "@remix-run/react"; import prismStyles from "~/styles/prismjs.css"; import { markdownParser } from "~/utils/markdoc.server"; import { ContentToJSX } from "~/components/AstToJSX"; export const links: LinksFunction = () => [ { rel: "stylesheet", href: prismStyles }, ]; export const loader: LoaderFunction = async ({ params }) => { const post = await db.post.findUnique({ where: { slug: params.slug }, select: { title: true, createdAt: true, body: true, }, }); const content = await markdownParser(post.body); return json({ title: post.title, date: post.createdAt, content }); }; export default function BlogDetail() { const { content, title, date } = useLoaderData<typeof loader>(); return ( <> <h2>{title}</h2> <span>{new Date(date).toLocaleDateString()}</span> <ContentToJSX content={content} /> </> ); }

Qué bonito, qué minimalista, qué limpio. 🧴🧽🧼

Ha sido muy fácil trabajar con Markdoc, tanto, que estoy a punto de migrar el blog de FixterGeek.com y así hacerlo mucho más rápido.

Ya te contaré cómo me fue, mientras tanto… ¿Qué esperas para publicar tu propio blog?

Abrazo. Bliss. 🤓

Enlaces relacionados:

Qué es un ast (Abstract syntax tree)

Docs oficiales de Prismjs

banner

¿Quieres mantenerte al día sobre los próximos cursos y eventos?

Suscríbete a nuestro newsletter

Jamás te enviaremos spam, nunca compartiremos tus datos y puedes cancelar tu suscripción en cualquier momento 😉

robot logo saying hello
facebook icontwitter iconlinkedin iconinstagram iconyoutube icon

© 2016 - 2023 Fixtergeek