---
title: "Le surlignage de code peut maintenant vivre hors du DOM"
locale: "fr"
url: "https://irz.fr/fr/articles/microlighter-outside-dom-fr"
markdown_url: "https://irz.fr/fr/articles/microlighter-outside-dom-fr.md"
category: "tech"
tags: ["Microlighter", "CSS", "JavaScript", "accessibilité", "performance"]
published_at: "2026-08-20T12:38:00.000Z"
author: "Manon Girard"
translation: "https://irz.fr/en/articles/microlighter-outside-dom-en.md"
---

# Le surlignage de code peut maintenant vivre hors du DOM

Microlighter colore du code avec CSS Custom Highlight au lieu d’entourer chaque token de spans. Nous avons mesuré le vrai payload, le DOM, 37 grammaires et les dix thèmes.

Pendant des années, colorer trois mots dans un bloc de code signifiait souvent fabriquer trente éléments HTML.

Un tokenizer reconnaît `const`, une chaîne, un nombre ou un commentaire, puis le résultat devient une forêt de `<span class="token …">`. La méthode fonctionne très bien, au point que Prism et Highlight.js ont coloré une bonne partie du Web technique de cette manière.

**Microlighter 2.1.0 choisit désormais un autre endroit pour ranger les tokens : hors du DOM.**

La bibliothèque de Dave Rupert crée des `Range` sur le texte existant, groupe ces plages dans des objets `Highlight`, les enregistre dans `CSS.highlights`, puis laisse `::highlight()` appliquer les couleurs.[1](https://daverupert.com/2026/08/microlighter/)[2](https://github.com/davatron5000/microlighter/tree/d7fec9e69c38166924294b26fac8e2fedd935013)[4](https://developer.mozilla.org/en-US/docs/Web/API/CSS_Custom_Highlight_API)

Le code reste du texte. La coloration devient une couche de peinture du navigateur.

C’est une idée plus importante que le chiffre « 2 Ko » affiché sur le projet.

## Un nœud

Le cœur de Microlighter ne remplace pas le contenu du `<code>` par du HTML tokenisé. Lorsqu’une règle TextMate correspond à une portion de texte, le code construit un `Range`, fixe son début et sa fin dans le nœud texte, puis ajoute cette plage à une catégorie comme `keyword`, `string` ou `comment`.[2](https://github.com/davatron5000/microlighter/tree/d7fec9e69c38166924294b26fac8e2fedd935013)

Le registre CSS reçoit ensuite les groupes de ranges. Un thème peut écrire :

```css
::highlight(keyword) {
  color: var(--syntax-keyword);
}
```

MDN décrit précisément cette propriété du CSS Custom Highlight API : cibler des plages arbitraires **sans affecter la structure DOM sous-jacente**.[4](https://developer.mozilla.org/en-US/docs/Web/API/CSS_Custom_Highlight_API)[5](https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Custom_highlight_API)

Restait à vérifier si cette promesse survivait à autre chose qu’un exemple de README.

Après build du dépôt au commit `d7fec9e`, les **15 tests Node passent**. Un Chromium Playwright temporaire a ensuite servi à lancer la suite navigateur en mode CI, où **26 tests sur 26 passent**, notamment les 37 grammaires, les thèmes, les syntaxes imbriquées Svelte/Vue/HEEx, le composant optionnel et l’édition en direct.[2](https://github.com/davatron5000/microlighter/tree/d7fec9e69c38166924294b26fac8e2fedd935013)

Un test est particulièrement révélateur : après modification d’un bloc `contenteditable`, il exige que celui-ci possède toujours **un seul enfant, un Text node**, tout en vérifiant que des ranges de highlight pointent bien dedans.[2](https://github.com/davatron5000/microlighter/tree/d7fec9e69c38166924294b26fac8e2fedd935013)

> **Même code, deux endroits pour stocker les tokens**
> Comparaison entre le markup à spans de Prism et Highlight.js et les Range CSS de Microlighter autour d’un même extrait JavaScript
> - Même code, deux endroits pour stocker les tokens
> - Extrait JavaScript IRZ de 185 caractères. Prism/Highlight.js mesurés via leur API Node ; Microlighter validé dans Chromium.
> - Prism / Highlight.js
> - Le résultat encode les tokens dans le markup.
> - <span class="token keyword">const</span>
> - message = <span class="token template-string">…
> - …
> - Prism : 46 spans · 1 998 caractères
> - Highlight.js : 18 spans · 858 caractères
> - Le texte source faisait 185 caractères.
> - Microlighter
> - Le DOM garde le texte. Les tokens vivent dans des Range.
> - const message = `Hello, ${name}!`;
> - ↑ Range(keyword)   ↑ Range(string)
> - CSS.highlights → ::highlight(keyword)
> - DOM : 1 nœud texte dans le test éditable
> - Chromium : 26/26 tests Playwright passent
> - La coloration reste une couche de peinture, pas de sémantique.
> - Le gain architectural est plus intéressant que le seul chiffre gzip : le code reste éditable sans reconstruire son DOM token par token.
> Dans notre extrait de 185 caractères, Prism produit 46 spans et 1 998 caractères de markup ; Highlight.js 18 spans et 858 caractères. Microlighter garde le texte dans le DOM et stocke la coloration dans CSS.highlights.

## Quarante-six spans

Le même extrait JavaScript de **185 caractères** a ensuite été envoyé aux API Node de Prism 1.30.0 et Highlight.js 11.12.0 afin de rendre la différence tangible.

Prism renvoie **1 998 caractères de HTML et 46 `<span>`**. Highlight.js renvoie **858 caractères et 18 spans**.

Ce ratio ne mesure ni le temps de rendu ni la mémoire. Il montre simplement où vit l’information de tokenisation : dans le markup pour ces deux sorties, dans le registre de highlights pour Microlighter.

La distinction devient particulièrement séduisante dans un éditeur : modifier du texte tokenisé traditionnellement oblige à gérer une structure susceptible d’être reconstruite au fil des frappes, alors que Microlighter peut conserver le même nœud texte et recalculer les ranges.[2](https://github.com/davatron5000/microlighter/tree/d7fec9e69c38166924294b26fac8e2fedd935013)

Le navigateur n’a pas supprimé le travail. Il a offert un **canal de rendu distinct du DOM**.

## Deux kilo-octets

Le deuxième argument de Microlighter est sa taille. Le build 2.1.0 impose même un budget : son bundle auto-runner doit rester sous **2,05 KiB gzip**. Notre build reproduit exactement ce chiffre : 4,41 KiB minifiés, **2,05 KiB gzip**, 1,88 KiB Brotli.[2](https://github.com/davatron5000/microlighter/tree/d7fec9e69c38166924294b26fac8e2fedd935013)

Mais télécharger le noyau ne colore encore aucun JavaScript. Les grammaires sont chargées à la demande et le thème est un fichier CSS séparé.

Les versions utilisées dans ce test, Microlighter 2.1.0, Prism 1.30.0 et Highlight.js 11.12.0, ont donc été empaquetées depuis npm avant d’additionner la taille gzip des ressources nécessaires à des scénarios comparables.[3](https://www.npmjs.com/package/microlighter/v/2.1.0)[6](https://www.npmjs.com/package/prismjs/v/1.30.0)[7](https://www.npmjs.com/package/highlight.js/v/11.12.0) Chaque format reçoit son thème, sans supposer de fusion par bundler, de cache CDN ni de compression conjointe.

> **« 2 Ko » n’est que le noyau**
> Barres comparant les tailles gzip de Microlighter, Prism et Highlight.js pour JavaScript puis HTML CSS JavaScript avec thème
> - « 2 Ko » n’est que le noyau
> - Somme des tailles gzip des ressources npm, thème inclus. Ce n’est pas une capture réseau CDN.
> - JavaScript
> - Microlighter 2.1.0
> - 4,08 Ko
> - Prism 1.30.0
> - 5,89 Ko
> - Highlight.js 11.12.0
> - 28,61 Ko
> - HTML + CSS + JavaScript
> - 5,77 Ko
> - 7,55 Ko
> - 35,82 Ko
> - Microlighter core seul : 2,05 Ko gzip · grammaire moyenne : 0,73 Ko · thème moyen : 0,56 Ko.
> Avec JavaScript et un thème, Microlighter totalise 4,08 Ko gzip, Prism 5,89 Ko et Highlight.js 28,61 Ko. Pour HTML+CSS+JS : 5,77 Ko, 7,55 Ko et 35,82 Ko.

Pour **JavaScript + thème** :

- Microlighter : **4 081 octets gzip** ;
- Prism : **5 890 octets** ;
- Highlight.js : **28 610 octets**.

Pour **HTML + CSS + JavaScript + thème** :

- Microlighter : **5 766 octets** ;
- Prism : **7 552 octets** ;
- Highlight.js : **35 816 octets**.

Sur ces configurations précises, Microlighter est donc environ **31 % plus petit que Prism** en JavaScript et **24 %** sur le trio Web. L’écart avec le core modulaire de Highlight.js est beaucoup plus grand.

Ces chiffres ne sont pas des octets réseau universels. Un CDN peut Brotli-compresser autrement, un bundler fusionner les ressources, le cache supprimer certains transferts et un projet réel charger d’autres langues. Ils donnent seulement une comparaison reproductible des artefacts npm que nous avons réellement ouverts.

## Le prix caché

Le cœur peut rester à 2 Ko parce que beaucoup de matière arrive plus tard.

Microlighter 2.1.0 distribue **37 fichiers de grammaire**. Notre build mesure une moyenne de **0,73 KiB gzip** par grammaire, de 0,24 KiB pour JSON jusqu’à 1,37 KiB pour JavaScript. Les dix thèmes tournent autour de **0,56 KiB gzip** chacun.[2](https://github.com/davatron5000/microlighter/tree/d7fec9e69c38166924294b26fac8e2fedd935013)

Certaines langues tirent aussi leurs dépendances. HTML déclare CSS, JSON et JavaScript. TypeScript dépend de JavaScript, tandis que TSX dépend de JavaScript et TypeScript.[2](https://github.com/davatron5000/microlighter/tree/d7fec9e69c38166924294b26fac8e2fedd935013)

Le lazy loading est donc la vraie stratégie : ne pas vendre 37 langues à une page qui n’en montre qu’une.

Un petit détail du dépôt illustre d’ailleurs la vitesse d’évolution du projet. Le haut du README annonce encore **« 35 languages »**, alors que la section Languages en énumère 37 et que le build produit effectivement 37 modules.[2](https://github.com/davatron5000/microlighter/tree/d7fec9e69c38166924294b26fac8e2fedd935013) Rien de grave, simplement un nombre marketing devenu périmé plus vite que le code.

À titre de contexte, les paquets inspectés contiennent 298 composants minifiés `prism-*` et 386 fichiers sous `highlight.js/lib/languages`. Ce ne sont **pas** des nombres directement comparables de langues supportées : aliases, dépendances et variantes font diverger fichiers et langages. Ils montrent néanmoins que Microlighter vise volontairement une surface plus petite.

Le README pose lui-même la limite : **la couverture et la précision des grammaires restent inférieures à celles d’une solution plus lourde comme Shiki**.[2](https://github.com/davatron5000/microlighter/tree/d7fec9e69c38166924294b26fac8e2fedd935013)[9](https://shiki.style/)

## Regex natives

Cette économie vient aussi du tokenizer.

Microlighter réutilise des grammaires inspirées de TextMate mais les exécute avec le `RegExp` natif du navigateur. Il n’embarque ni Oniguruma, ni WebAssembly, ni moteur de tokenisation massif.[2](https://github.com/davatron5000/microlighter/tree/d7fec9e69c38166924294b26fac8e2fedd935013)

Le code parcourt les règles, résout les `include`, compile les expressions au besoin, trouve le prochain match puis descend récursivement dans les paires `begin/end`.[2](https://github.com/davatron5000/microlighter/tree/d7fec9e69c38166924294b26fac8e2fedd935013)

Cela suffit pour les 37 grammaires fournies et leur suite de tests. Cela ne signifie pas que n’importe quelle grammaire TextMate complexe peut être déposée telle quelle et obtenir la fidélité d’un moteur qui implémente l’ensemble des constructions attendues.

Le projet choisit **un sous-ensemble utile contre un moteur général**.

Dans de petits articles techniques, une documentation ou un éditeur léger, ce compromis peut être excellent. En revanche, reproduire au pixel près la coloration d’un éditeur sur des dizaines de syntaxes exotiques risque de faire revenir le poids économisé sous forme de travail de grammaire.

## Navigateur récent

La technique dépend aussi d’une capacité Web relativement jeune.

MDN classe le CSS Custom Highlight API **Baseline 2025** et indique une disponibilité dans les navigateurs récents depuis **juin 2025**, tout en avertissant que les appareils ou navigateurs plus anciens peuvent ne pas la prendre en charge.[4](https://developer.mozilla.org/en-US/docs/Web/API/CSS_Custom_Highlight_API)

C’est très différent d’une solution à spans, dont le mécanisme de rendu repose sur du HTML et du CSS presque intemporels.

Sur un site qui exige de vieux navigateurs, la matrice de compatibilité devient une vraie décision produit. Dans un outil moderne qui contrôle son environnement, le navigateur peut enfin prendre en charge une partie que les bibliothèques devaient auparavant matérialiser elles-mêmes.

## Pas de gras

Le Custom Highlight API possède une autre restriction intéressante : `::highlight()` n’accepte qu’un **ensemble limité de propriétés CSS**.[5](https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Custom_highlight_API)

Dave Rupert le souligne directement : pas de changement arbitraire de graisse ou de famille de police pour reproduire certains thèmes riches.[1](https://daverupert.com/2026/08/microlighter/) Microlighter organise donc ses thèmes principalement autour de couleurs de catégories sémantiques.

Ce qui ressemble à une limitation stylistique est aussi une conséquence cohérente de l’architecture : le pseudo-élément de highlight peint une plage de texte, il ne devient pas un nouvel élément typographique complet.

## Accessibilité

« DOM propre » peut facilement devenir une formule magique, alors nous avons testé quelque chose de beaucoup plus banal : **le contraste des couleurs fournies**.

L’audit prend les dix thèmes de Microlighter 2.1.0 et calcule le ratio entre leurs 14 variables de couleur de tokens et le fond, en mode clair puis sombre. Le seuil retenu est **4,5:1**, celui de WCAG 2.2 pour le texte normal.[8](https://www.w3.org/TR/WCAG22/#contrast-minimum)

> **DOM propre ≠ contraste garanti**
> Tableau des dix thèmes Microlighter indiquant combien de leurs quatorze couleurs de tokens passent sous 4,5 contre le fond en modes clair et sombre
> - DOM propre ≠ contraste garanti
> - Nombre de couleurs de tokens sous 4,5:1 contre le fond, sur 14 couleurs par mode. Audit IRZ des thèmes Microlighter 2.1.0.
> - THÈME
> - CLAIR < 4,5
> - SOMBRE < 4,5
> - cobalt2
> - 0 / 14
> - 3 / 14
> - dracula
> - 1 / 14
> - github
> - min
> - monokai
> - 5 / 14
> - night-owl
> - 8 / 14
> - solarized-light
> - 14 / 14
> - tokyo-night
> - vesper
> - vscode-plus
> - 45 / 280 combinaisons thème × token × mode sont sous 4,5:1.
> - github, vesper et vscode-plus passent les 28 combinaisons de cette grille. Ce test de contraste ne remplace pas un audit d’accessibilité complet.
> 45 combinaisons sur 280 passent sous 4,5:1 dans cet audit. GitHub, Vesper et VSCode Plus n’en ont aucune ; d’autres thèmes privilégient parfois une couleur plus discrète, souvent celle des commentaires.

Résultat : **45 combinaisons thème × token × mode sur 280** sont sous 4,5:1.

Trois thèmes n’en ont aucune dans notre grille : `github`, `vesper` et `vscode-plus`. Le thème GitHub descend au minimum à **4,55:1** sur les commentaires en mode clair et **6,15:1** en sombre. À l’opposé, la palette claire de `solarized-light` place les quatorze couleurs de tokens sous 4,5 dans notre calcul.

Ce tableau ne vaut évidemment pas certificat de conformité : tailles réelles, couleurs adjacentes, modes forcés, contexte de page et besoins des utilisateurs exigent toujours un audit complet. Il montre seulement qu’**une architecture de rendu moderne ne garantit pas, à elle seule, des choix chromatiques accessibles**.

Le CSS Highlight API n’ajoute pas non plus une sémantique d’accessibilité aux mots-clés. `const` reste du texte `const`, seulement peint différemment. C’est plutôt une bonne propriété pour la robustesse du contenu, mais un lecteur d’écran ne reçoit pas soudain l’étiquette « keyword » parce qu’un `Highlight` porte ce nom.

## Éditer

C’est dans un bloc éditable que l’idée prend le plus de relief.

La démo Microlighter utilise `contenteditable="plaintext-only"` et recalcule le surlignage à chaque `input`.[2](https://github.com/davatron5000/microlighter/tree/d7fec9e69c38166924294b26fac8e2fedd935013) Le même parcours rejoué avec Playwright conserve le texte intact après la saisie, avec toujours un seul Text node dans le bloc et de nouveaux ranges correctement enregistrés.

L’API optionnelle `<micro-lighter>` ajoute copie et numéros de ligne. Son bundle complet fait **3,54 KiB gzip**, contre 2,05 pour l’auto-runner, avant grammaire et thème.[2](https://github.com/davatron5000/microlighter/tree/d7fec9e69c38166924294b26fac8e2fedd935013)

La frontière devient alors assez nette : **la coloration syntaxique** peut rester hors du DOM, tandis que contrôles, numéros, boutons et comportements d’éditeur, eux, sont de vrais composants et reviennent naturellement dans la structure.

Cette séparation évite surtout de traiter la peinture syntaxique et l’interface comme un seul problème.

## Le navigateur paye

Microlighter ne prouve donc pas seulement qu’un syntax highlighter peut être petit.

Il montre ce qui arrive quand une primitive autrefois absente du Web devient native. Une bibliothèque cesse alors de fabriquer des centaines d’éléments juste pour accrocher de la couleur et peut se concentrer sur le morceau qu’elle apporte réellement, **comprendre le code et produire des ranges**.

Le gain possède des contreparties claires : navigateur moderne, 37 grammaires plutôt que des centaines, interprétation TextMate simplifiée, propriétés de thème limitées, validation du contraste toujours nécessaire.

C’est précisément ce qui rend le projet intéressant.

Les 2 Ko ne sont pas une preuve de supériorité. Ils sont la conséquence visible d’une meilleure question architecturale : **est-ce encore à JavaScript de matérialiser dans le DOM quelque chose que le navigateur sait désormais peindre tout seul ?**

## References

1. [Dave Rupert, Microlighter, 19 août 2026](https://daverupert.com/2026/08/microlighter/)
2. [davatron5000/microlighter, version 2.1.0 auditée au commit d7fec9e](https://github.com/davatron5000/microlighter/tree/d7fec9e69c38166924294b26fac8e2fedd935013)
3. [npm, microlighter 2.1.0](https://www.npmjs.com/package/microlighter/v/2.1.0)
4. [MDN, CSS Custom Highlight API](https://developer.mozilla.org/en-US/docs/Web/API/CSS_Custom_Highlight_API)
5. [MDN, CSS custom highlight API guide](https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Custom_highlight_API)
6. [Prism 1.30.0](https://www.npmjs.com/package/prismjs/v/1.30.0)
7. [Highlight.js 11.12.0](https://www.npmjs.com/package/highlight.js/v/11.12.0)
8. [W3C, WCAG 2.2 — Contrast Minimum](https://www.w3.org/TR/WCAG22/#contrast-minimum)
9. [Shiki — syntax highlighting](https://shiki.style/)
