Vista previa técnicaDanos tu opinión

Plugin de Vite para Markdown

Actualizado el 12 nov 2026

El pipeline de Markdown y los componentes de artículo de Livemark se publican por separado como @livemark/vite. Cualquier app de React basada en Vite puede importar archivos .md y .mdx y renderizarlos con los mismos callouts, pestañas, bloques de código y tarjetas que un artículo de Livemark, sin instalar Livemark.

npm install @livemark/vite

El plugin de Vite

vite.config.ts
import markdown from "@livemark/vite"
import { defineConfig } from "vite"

export default defineConfig({
  plugins: [markdown()],
})

Cada archivo .md o .mdx importado se convierte en un módulo que exporta:

  • default: el contenido como componente de React, renderizado con los componentes de Livemark
  • frontmatter: el frontmatter YAML del archivo
  • toc: sus encabezados h2 a h4 como { url, title, depth }

Un archivo se compila como Livemark compila un artículo: .md como Markdown, .mdx como MDX, y un format: md o format: mdx en su frontmatter prevalece sobre la extensión.

OpciónPor defectoQué hace
include/\.mdx?$/Los ids de módulo a compilar, como globs o expresiones regulares
excludeLos ids de módulo que se dejan intactos
codeThemeLightcatppuccin-latteEl tema de sintaxis en modo claro
codeThemeDarkcatppuccin-mochaEl tema de sintaxis en modo oscuro
pluginsPlugins { md, html }, como en un sitio
allowDangerousHtmltruefalse elimina todo el HTML y falla con cualquier archivo que sería MDX
root, baseResuelve las URLs relativas de imágenes desde root y las sirve en base
collectionsColecciones servidas como módulos virtuales

Usa allowDangerousHtml: false para contenido en el que no confías del todo: Markdown no puede ejecutar código, MDX sí. React bloquea los enlaces con una URL javascript: al renderizar el contenido. El plugin también avisa, a través de Vite, de lo que se renderiza mal en silencio, como una directiva desconocida.

Para TypeScript, referencia los tipos de los módulos una vez:

env.d.ts
/// <reference types="@livemark/vite/env" />

Renderizado

routes/blog/post.tsx
import { MessagesProvider } from "@livemark/vite/react"
import Post from "./post.md"

export function PostPage() {
  return (
    <MessagesProvider messages={{ "callout.note": "Nota" }}>
      <article className="prose dark:prose-invert">
        <Post />
      </article>
    </MessagesProvider>
  )
}

Pasa components para reemplazar cualquiera de los componentes de Livemark en un renderizado, como <Post components={{ a: RouterLink }} />.

  • MessagesProvider proporciona los textos de interfaz que muestran los componentes, como los títulos de los callouts y la etiqueta del botón de copiar. Una clave que omitas queda en inglés. Las claves son las de messages de un idioma.
  • IconsProvider de @livemark/vite/react proporciona los iconos que renderizan :icon y :::card{icon}, por su clave de Lucide: pasa icons de lucide-react o un subconjunto. Sin él, no se renderiza ningún icono.

Colecciones

El índice de un blog, una página de etiqueta o un sitemap necesitan el frontmatter de cada artículo sin cargar todos los artículos. Una colección lista un conjunto de archivos sin su contenido y carga cada contenido cuando hace falta:

vite.config.ts
import markdown, { defineCollection } from "@livemark/vite"
import { defineConfig } from "vite"
import { Post } from "./post.ts"

export const blog = defineCollection({
  name: "blog",
  include: "content/blog/**/*.md",
  schema: Post,
})

export default defineConfig({
  plugins: [markdown({ collections: [blog] })],
})

La aplicación la importa como virtual:livemark/collection/<name>:

routes/blog/post.tsx
import { lazy } from "react"
import { entries, load } from "virtual:livemark/collection/blog"

const posts = entries.map(entry => ({ ...entry, Body: lazy(() => load(entry.id)) }))
  • entries lista cada archivo, ordenado por ruta, como { id, file, frontmatter, readingTime }. readingTime está en minutos, a 200 palabras por minuto y sin contar los bloques de código. No incluye contenidos, así que un índice sigue siendo ligero.
  • load(id) importa el módulo compilado de una entrada, el mismo que da importar el archivo. Cada archivo es su propio chunk, y un id desconocido rechaza la promesa.
OpciónPor defectoQué hace
nameEl nombre del módulo: letras, dígitos, _ y -
includeLos archivos .md y .mdx, como globs relativos a root
excludeLos archivos excluidos, como globs relativos a root
rootel directorio de trabajoEl directorio desde el que cuentan los globs y file
schemaValida el frontmatter de cada archivo: un esquema de zod o cualquier { parse }
idfile sin su extensiónEl id de una entrada a partir de su file, p. ej. file => file.split("/")[2]

Un frontmatter que no supera schema hace fallar el build y nombra el archivo, igual que un id que comparten dos archivos. entries se serializa como JSON, así que un esquema que convierte una fecha en Date da un string en la aplicación.

blog.list() devuelve las mismas entradas desde Node, sin Vite: una configuración puede enumerar con ella las páginas que prerenderizar. Los archivos de una colección se compilan aunque el include o el exclude del plugin los dejen fuera. En el servidor de desarrollo, una edición que cambia entries recarga la página, mientras que una edición del contenido se actualiza como siempre.

Para tipar el frontmatter, declara el módulo una vez, junto a la referencia de tipos:

env.d.ts
declare module "virtual:livemark/collection/blog" {
  import type { CollectionEntry, MarkdownModule } from "@livemark/vite"
  import type { Post } from "./post.ts"
  export const entries: CollectionEntry<Post>[]
  export function load(id: string): Promise<MarkdownModule>
}

Estilos

Los componentes se distribuyen como código fuente, así que tu build de Tailwind CSS v4 compila sus clases. Una hoja de estilos carga los estilos de Markdown y apunta Tailwind a los componentes:

app.css
@import "tailwindcss";
@plugin "@tailwindcss/typography";
@import "@livemark/vite/styles.css";
@custom-variant dark (&:is(.dark *));

Los componentes usan estos tokens de tema, que tu CSS define como --color-* bajo @theme (consulta los valores de Livemark):

  • background, foreground, card, card-foreground, popover, popover-foreground
  • primary, secondary, muted, accent, cada uno con su -foreground
  • border, input, ring
  • positive, warning y destructive, cada uno con -tint y -tint-foreground

Sus esquinas usan la escala rounded-card y rounded-control que define la hoja de estilos.

Hecho con ❤ y Livemark