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/viteEl plugin de Vite
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 Livemarkfrontmatter: el frontmatter YAML del archivotoc: 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ón | Por defecto | Qué hace |
|---|---|---|
include | /\.mdx?$/ | Los ids de módulo a compilar, como globs o expresiones regulares |
exclude | Los ids de módulo que se dejan intactos | |
codeThemeLight | catppuccin-latte | El tema de sintaxis en modo claro |
codeThemeDark | catppuccin-mocha | El tema de sintaxis en modo oscuro |
plugins | Plugins { md, html }, como en un sitio | |
allowDangerousHtml | true | false elimina todo el HTML y falla con cualquier archivo que sería MDX |
root, base | Resuelve las URLs relativas de imágenes desde root y las sirve en base | |
collections | Colecciones 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:
/// <reference types="@livemark/vite/env" />Renderizado
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 }} />.
MessagesProviderproporciona 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 demessagesde un idioma.IconsProviderde@livemark/vite/reactproporciona los iconos que renderizan:icony:::card{icon}, por su clave de Lucide: pasaiconsde 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:
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>:
import { lazy } from "react"
import { entries, load } from "virtual:livemark/collection/blog"
const posts = entries.map(entry => ({ ...entry, Body: lazy(() => load(entry.id)) }))entrieslista cada archivo, ordenado por ruta, como{ id, file, frontmatter, readingTime }.readingTimeestá 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ón | Por defecto | Qué hace |
|---|---|---|
name | El nombre del módulo: letras, dígitos, _ y - | |
include | Los archivos .md y .mdx, como globs relativos a root | |
exclude | Los archivos excluidos, como globs relativos a root | |
root | el directorio de trabajo | El directorio desde el que cuentan los globs y file |
schema | Valida el frontmatter de cada archivo: un esquema de zod o cualquier { parse } | |
id | file sin su extensión | El 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:
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:
@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-foregroundprimary,secondary,muted,accent, cada uno con su-foregroundborder,input,ringpositive,warningydestructive, cada uno con-tinty-tint-foreground
Sus esquinas usan la escala rounded-card y rounded-control que define la hoja de estilos.
Hecho con ❤ y Livemark