Registre de blocs

Enregistrement des blocs personnalisés et rendu serveur du contenu BlockEditor pour le site public.

blocks-registry porte deux responsabilités : tenir le registre des blocs personnalisés du projet hôte, et rendre un tableau de blocs côté serveur pour le site public.

Tous les modules CMS partagent ce rendu. Il n'y a pas de renderer par module.

Points d'entrée

Sous-cheminFichierRuntime
@lrvo/core/blocks-registryindex.server.jsserveur
@lrvo/core/blocks-registry/renderrender.server.jsserveur
@lrvo/core/blocks-registry/walkwalk.jsneutre
@lrvo/core/blocks-registry/columns-layoutcolumns-layout.jsneutre
@lrvo/core/blocks-registry/schemafields-schema.jsneutre
@lrvo/core/blocks-registry/clientclient.client.jsclient
@lrvo/core/blocks-registry/fields-uifields-ui.client.jsclient

Les helpers purs de l'éditeur, eux, sont sous @lrvo/core/shared/components/block-editor.

Registre serveur

Pendant l'édition, un bloc personnalisé est rendu côté client par son composant Template. Pendant le rendu public, c'est son composant Render qui est appelé depuis un composant serveur. Ce registre porte le second.

Le manifeste auto-généré app/.lrvo/blocks.server.js, produit par lrvo-modules sync, appelle registerCustomBlock au boot. L'appel est déclenché par instrumentation.js.

registerCustomBlock({ type, RenderComponent, meta })

ChampTypeRôle
typestringType du bloc, par exemple custom:hero.
RenderComponentFunction | objectComposant React. Une fonction pour un composant serveur, un objet pour une référence client quand le render.js du bloc porte 'use client'.
metaobjectContenu du meta de fields.js.

Les deux formes de RenderComponent sont acceptées parce qu'un module client importé depuis un manifeste serveur se résout en objet, pas en fonction.

Lecture

FonctionRetour
getCustomBlockRender(type)Composant de rendu, ou null.
getCustomBlockEntry(type)Entrée complète { type, RenderComponent, meta }, ou null.
listCustomBlockMeta()Métadonnées de tous les blocs enregistrés.
isCustomBlockType(type)true si le type est un bloc personnalisé.

Registre client

@lrvo/core/blocks-registry/client porte le registre des types de blocs de l'éditeur, celui que le menu de commandes lit. Le manifeste app/.lrvo/blocks.client.js y appelle registerBlock pour chaque bloc du dossier components/lrvo/blocks/.

ExportRôle
registerBlock(def)Enregistre un type de bloc. type et create sont requis, sinon l'appel lève. Un type déjà connu est remplacé.
getBlockDef(type)Définition du type, ou null.
listBlocks()Toutes les définitions enregistrées, intégrées comprises. Base d'un menu d'insertion maison.
isBlockText(type)true si le type porte un contentEditable, donc si son contenu est un InlineNode[].
DEFAULT_BLOCK_TYPE'paragraph'. Type créé par défaut, notamment sur Entrée en fin de document.
newBlockId()Identifiant de bloc.
LrvoBlocksClientComposant inerte à monter dans le layout. Son rendu force l'exécution du manifeste côté navigateur.

Le champ type d'un bloc du projet hôte est préfixé custom: à la génération. Les types intégrés n'utilisent jamais :, il n'y a donc pas de collision possible.

Rendu serveur

@lrvo/core/blocks-registry/render expose trois niveaux d'API.

1. BlockListRenderer({ blocks })

Export par défaut. Rendu complet d'une liste avec l'apparence par défaut du core. Sert de repli pour les templates par défaut des modules et pour un projet sans content.js.

import BlockListRenderer from '@lrvo/core/blocks-registry/render';

export default function Page({ page }) {
  return <BlockListRenderer blocks={page.blocks} />;
}

2. Helpers composables

La voie recommandée pour personnaliser le rendu. Le projet hôte écrit son propre <Content> dans components/lrvo/templates/content.js et y assemble ces helpers avec sa JSX.

HelperRôle
renderInline(content)Rend un tableau d'InlineNode en JSX, marks comprises.
groupListItems(blocks)Regroupe les blocs de liste consécutifs en <ul> / <ol>.
anchorProps(block)Retourne les props d'ancre d'un bloc, pour les liens internes de page.
isInternalHref(href)true pour une route relative ou une ancre pure ; false pour tout schéma absolu et pour une URL protocole-relative. Décide entre <Link> et <a>.
CustomBlockRender({ block })Rend un bloc personnalisé.
isCustomBlockType(type)Re-export du registre.

3. CustomBlockList({ blocks })

Rend uniquement les blocs personnalisés. Utile quand le dispatch entre blocs intégrés et personnalisés est fait en amont.

Pourquoi pas le BlockEditor en lecture seule

Réutiliser le BlockEditor client en mode désactivé pour l'affichage public coûterait deux choses. D'abord le poids : tout le code d'édition (menu slash, historique, glisser-déposer) partirait dans le bundle navigateur pour du contenu en lecture seule. Ensuite le rendu : le BlockEditor porte 'use client', ce qui poserait une frontière client sur tout le contenu de la page et priverait le SEO du rendu serveur.

Parcours de l'arbre

Le BlockEditor stocke un tableau plat, à une exception près : le bloc columns imbrique du contenu dans columns[].blocks. walk.js est la source unique de vérité de cette forme.

Tout code qui doit voir les blocs profonds (collecte d'images, normalisation média, enrichissement) passe par ces helpers plutôt que d'itérer le tableau plat, sinon le contenu des colonnes est silencieusement ignoré.

flattenBlocks(blocks)

Aplatit l'arbre en une liste incluant les blocs imbriqués. Le bloc conteneur apparaît avant ses enfants. Destiné aux collecteurs, par exemple pour rassembler tous les blocs image et faire un seul appel base de données.

mapBlocksDeep(blocks, fn)

Reconstruit l'arbre en appliquant fn à chaque bloc, en descendant dans les colonnes. fn est appliquée au conteneur après ses enfants, donc le bloc reçu porte déjà ses colonnes transformées.

Tout futur type de bloc conteneur s'ajoute ici, et nulle part ailleurs.

Mise en page des colonnes

columns-layout.js est la source de vérité de la disposition du bloc columns, partagée par l'éditeur, le rendu serveur du core, et le <Content> du projet hôte. Sans ce partage, une disposition réglée dans l'éditeur s'afficherait différemment sur le site.

ExportRôle
DEFAULT_COLUMN_GAP'md'.
COLUMN_GAP_CLASSClasses de gap par taille : sm, md, lg.
columnGapClass(gap)Classe de gap, avec repli sur le défaut.
columnWeight(col)Poids flex-grow de la colonne. Toute valeur non numérique ou nulle retombe sur 1.
columnsContainerClass(gap)Classe du conteneur : empilé sur mobile, en ligne dès md.
COLUMN_CLASSClasse d'une colonne.
columnStyle(col)Style inline portant la variable CSS de répartition.

La répartition n'est pas posée en style inline direct : un style inline gagnerait en spécificité et serait impossible à neutraliser sur mobile. Elle passe par une variable CSS --lrvo-col, consommée seulement à partir du point de rupture md. En dessous, les colonnes sont empilées en pleine largeur.

Schéma des champs

fields-schema.js valide la déclaration d'un bloc personnalisé.

FonctionRôle
validateBlockMeta(meta, sourcePath?)Valide le meta d'un fields.js. Lève avec le chemin fautif.
toCustomType(metaId)Construit le type complet à partir de l'identifiant, hero → custom:hero.
isCustomType(type)true si le type porte le préfixe custom:.

Champs d'édition

fields-ui.client.js fournit les champs de formulaire d'un bloc personnalisé : TextField, TextareaField, NumberField, SelectField, BooleanField, ColorField, IconField, ImageField, plus LrvoBlockShell pour l'enveloppe visuelle.

ImageField passe par le sélecteur du registre média et se masque quand @lrvo/module-medias est absent.

Notes

Le registre vit sur globalThis via Symbol.for pour survivre aux doubles instanciations Node natif et bundle Turbopack. Voir Registres et singletons.

Pour créer un bloc personnalisé de bout en bout, voir Blocs personnalisés.

Sur cette page