Utiliser des composants customs avec Nuxt-mdc pour construire un système de thèmes
Je crée une plateforme de blog (Writizzy), alternative à Ghost ou Substack. Sous le capot j'utilise Nuxt mais je n'utilise pas nuxt-content pour avoir plus de souplesse donc j'utilise directement le module Nuxt-Mdc. Ce module permet de surcharger le markdown pour inclure des composants customs, par exemple des galeries d'images, la capacité d'insérer un lecteur YouTube etc...
Sauf que Writizzy propose des thèmes et on souhaite facilement pouvoir personnaliser les composants pour chaque thème.
Dans cet article je souhaite vous montrer comment utiliser Nuxt-Mdc pour ce cas de figure relativement commun.
Nuxt-mdc
Le module nuxt-mdc est utilisé par nuxt content mais il est possible de l'utiliser directement pour interpréter du contenu markdown et l'afficher en HTML. C'est une brique fondamentale de Nuxt-Content mais heureusement exploitable en standalone.
Parmi les fonctionnalités de Nuxt-mdc, on peut utiliser des MDC components. Un MDC component est globalement un balisage markdown qui fera ensuite appel à un composant Vue pour l'affichage.
Par exemple avec ce markup :
::card
The content of the card
::
On va demander à Nuxt-mdc d'utiliser le composant Card pour rendre le bloc précédent.
C'est aussi ce qu'utilise Nuxt-mdc pour rendre tous les Prose components, qui correspondent aux composants customs : titres, liens, blockquote etc... créés suite au parsing markdown. En interne en effet, chaque balise standard de Markdown est transformée en MDC components, par exemple :
## un titre
est transformé en
::h2
un titre
::
(C'est une simplification. En réalité, c'est plutôt une substitution au niveau du renderer. Le renderer lit l'arbre AST qui contient un noeud h2 et décide d'utiliser le composant h2 pour le rendre. Mais on peut vulgariser comme ça)
Cette fonctionnalité permet à l'utilisateur de Nuxt-Mdc de customiser l'affichage de chaque MDC component, donc les Prose components aussi.
Pour surcharger un composant Prose ou pour proposer un composant custom, il suffit de les placer dans le répertoire app/components/mdc sur une application Nuxt.
Cependant, et si on souhaite que ces composants changent en fonction du thème sélectionné ? C'est exactement la question que je me suis posée pour Writizzy.
Un système de thème pour composants
Sur Writizzy je développe un système de thème. On peut choisir l'apparence de son blog parmi ceux listés ici : https://writizzy.com/docs/your-blog/themes. J'ai repris partiellement ce que j'avais déjà fait pour Bloggrify (un projet opensource pour créer des blogs statiques).
Mais avec Writizzy j'étais plutôt insatisfait de cette solution notamment pour les composants customs. C'est évident que l'apparence doit changer en fonction du thème.
Et il se trouve qu'il existe une fonctionnalité non documentée dans Nuxt-Mdc qui permet exactement de faire cela.
Par défaut, la documentation conseille d'utiliser le composant MDC pour afficher du markdown, par exemple :
<script setup lang="ts">
const md = `
::alert
Hello MDC
::
`
</script>
<template>
<MDC :value="md" tag="article" />
</template>
Cependant MDC utilise lui-même MDCRenderer en arrière-plan. Et en regardant le code source de MDCRenderer, on note une propriété intéressante : components
Cette propriété permet de passer la liste des composants à utiliser.
Par défaut, MDCRenderer va rechercher dans cette liste, et sinon dans components/mdc donc on peut surcharger les composants qu'on souhaite modifier :
<script setup lang='ts'>
const mdcComponents = {
callout: TerminalCallout,
blockquote: TerminalBlockquote,
pre: TerminalCode
}
</script>
<template>
<MDCRenderer
:body="ast.body"
:data="ast.data"
:components="mdcComponents"
/>
</template>
Et voilà !
Désormais on peut personnaliser chaque composant en fonction du thème.
Cette approche fonctionne bien pour Writizzy et je compte l'appliquer aussi à Bloggrify. Si vous utilisez Nuxt-MDC avec un système de thèmes, c'est à mon avis la solution la plus propre — même si elle mériterait d'être documentée officiellement.
J'ai d'ailleurs ouvert une issue pour clarifier son statut. En attendant, ça fonctionne en production sur Writizzy sans souci.

