Blocs de code façon GitHub : barres de titre, bouton Copier, numéros de ligne et lignes surlignées

Cet article vous a aidé ?

Vos documents vont bien jusqu’au moment où quelqu’un essaie de coller une commande et qu’elle contient silencieusement un numéro de ligne, un symbole d’invite et un espace final. Ensuite vous recevez un ticket : « vos instructions ont cassé la prod ».

Les blocs de code de GitHub semblent simples : barre de titre avec nom de fichier, bouton copier qui copie vraiment le bon contenu, numéros de ligne qui n’encombrent pas le presse-papiers, et lignes surlignées qui pointent la ligne importante. Reproduire cela sur votre site est tout à fait faisable—si vous arrêtez de le traiter comme « juste du CSS » et commencez à le considérer comme un composant avec des exigences de fiabilité.

À quoi ressemble le « bon » dans la documentation de production

Les blocs de code à la manière de GitHub ne visent pas l’esthétique. Ils visent à réduire le risque opérationnel. Quand quelqu’un suit un runbook à 03:00, le bloc de code est l’interface. S’il ment, embrouille ou copie les mauvais octets, votre « site de docs » devient alors un contributeur d’incident.

Voici la barre que j’utilise :

  • Le copier est exact. Il ne copie que le code. Pas d’invites, pas de numéros de ligne, pas de confettis Unicode invisibles.
  • Les numéros de ligne sont purement présentiels. Ils aident à référer « ligne 17 » sans contaminer le presse‑papier.
  • Les lignes surlignées sont pilotées par les données. L’auteur peut préciser quelles lignes importent (contexte de diff, « modifier ceci », « ne pas exécuter »).
  • La barre de titre porte des métadonnées utiles. Nom de fichier, langage, peut-être « shell », peut-être « k8s », peut-être « output ». Pas de décoration inutile.
  • Accessible par défaut. Le bouton Copier fonctionne au clavier, annonce l’état, et ne vole pas le focus comme un enfant avec un nouveau tambour.
  • Rapide. Afficher 30 blocs de code ne devrait pas transformer une page de doc en radiateur.
  • Fonctionne hors ligne et sous CSP. Les systèmes de production ont souvent des politiques. Vos docs doivent survivre à cela.

Idée paraphrasée (de John Ousterhout) : la complexité rend les systèmes difficiles à changer et à comprendre ; si vous pouvez la supprimer, faites-le.

Et oui, je vais traiter un « widget de bloc de code » comme un mini-système de production. Parce que c’en est un. C’est aussi un système distribué : auteur, moteur de rendu, navigateur, API du presse‑papier et patience de l’utilisateur—aucun de ces éléments n’est entièrement sous votre contrôle.

Faits et courte histoire (pourquoi GitHub a gagné)

Un peu de contexte aide à prendre de meilleures décisions. Ce sont de petits faits, mais ils expliquent pourquoi l’écosystème ressemble à ce qu’il est.

  1. Les premiers exemples de code sur le web étaient de simples <pre>. La « coloration syntaxique » a commencé comme des bidouilles côté serveur avec des regex dans les années 1990, bien avant que les navigateurs aient de bonnes fontes ou moteurs de mise en page.
  2. Pygments (milieu des années 2000) a rendu le surlignage courant. Il a popularisé le modèle « tokenizer et spans de style » que la plupart des surligneurs utilisent encore.
  3. GitHub a popularisé les blocs de code délimités en Markdown. La convention des triples backticks est devenue le modèle mental par défaut pour le code dans la doc.
  4. Les API du presse‑papier ont évolué tardivement. Pendant des années, « copier » signifiait sélectionner du texte en espérant que le DOM n’inclue pas de saletés ; l’API moderne Clipboard a rendu les boutons Copier fiables.
  5. Les numéros de ligne ont toujours été controversés. Les IDE en ont besoin ; les docs souvent pas. Le débat existe parce que les numéros sont utiles mais faciles à implémenter incorrectement.
  6. Le surlignage côté client a été une réaction à l’hébergement statique. Quand tout le monde a commencé à déployer la doc sur des CDN, livrer des surligneurs JS semblait plus simple que le rendu serveur—jusqu’à la facture de performance.
  7. Les patterns UI de GitHub sont devenus un standard de facto. Barre de titre + bouton Copier est familier, donc les utilisateurs lui font confiance et l’utilisent sans réfléchir.
  8. Les surligneurs se concurrencent maintenant sur la précision « sémantique ». Tree-sitter et des parseurs similaires ont relevé la barre ; les tokenizers regex sont plus rapides à construire, mais moins précis pour les langages complexes.

Deux conclusions : premièrement, la plupart des fonctionnalités « bloc de code » sont greffées sur de vieux primitifs. Deuxièmement, ce qui semble être une UI simple est généralement trois systèmes séparés collés ensemble : rendu, interaction, et édition.

Architecture : un composant, trois chemins de données

Un composant de bloc de code à la manière de GitHub a trois chemins qui doivent concorder :

1) Chemin d’affichage : ce que l’utilisateur voit (surlignage, numéros de ligne, barre de titre).

2) Chemin du presse‑papier : ce qui est copié (doit être le code brut, normalisé de façon saine).

3) Chemin de référence : ce à quoi auteurs et lecteurs se réfèrent (numéros de ligne, lignes surlignées, ancres).

Si ces chemins divergent, vous obtenez des modes de défaillance qui ressemblent à des erreurs utilisateur mais ne le sont pas :

  • Le bouton Copier copie des invites ou des numéros → la commande collée échoue → l’utilisateur se méfie des docs.
  • Les lignes surlignées ne correspondent pas au code réel à cause de retours à la ligne ou de spans cachés → l’utilisateur modifie la mauvaise ligne.
  • Les numéros de ligne se décalent entre SSR et hydratation → les gens parlent de « ligne 14 » et ne font pas référence au même contenu.

Choisissez votre stratégie de rendu : SSR, build-time ou côté client

Vous avez trois options réalistes :

Stratégie Avantages Inconvénients Quand choisir
Coloration au moment du build (ex. Shiki) Pages rapides, pas de JS runtime pour la coloration, sortie cohérente Builds plus lents ; les changements de thème exigent un rebuild Sites de documentation, blogs, runbooks, tout ce qui est plutôt statique
Rendu côté serveur Cohérent, peut gérer thèmes par requête, pas de JS client lourd Plus d’infra ; le cache devient important Docs intégrées au produit, docs authentifiées
Coloration côté client (Prism/Highlight.js) Intégration facile ; contenu dynamique Poids JS, pics CPU, bizarreries d’hydratation Éditeurs interactifs, contenu généré par les utilisateurs, dernier recours

Je suis catégorique : pour la plupart des documentations et runbooks opérationnels, faites la coloration au build ou côté serveur. La coloration côté client est une taxe que vous payez indéfiniment.

Définissez un modèle explicite de bloc de code

Arrêtez de laisser le parsing Markdown « décider » de ce qu’est votre bloc de code. Modelez‑le. Au minimum :

  • language (bash, yaml, json, …)
  • title (nom de fichier, ou label comme « nginx.conf »)
  • code (contenu brut, fins de ligne normalisées)
  • highlight (plages de lignes : 3,5-8)
  • showLineNumbers (booléen)
  • copyTextOverride (optionnel ; ex. supprimer les invites)
  • kind (source, terminal, output, diff)

Une fois ce modèle en place, votre moteur de rendu peut être déterministe, testable et ennuyeux. Être ennuyeux, c’est bien. L’ennuyeux est déployable.

Bouton Copier : exactitude avant ingéniosité

Un bouton Copier qui copie parfois le mauvais contenu vaut mieux que pas de bouton du tout. Il crée de la confiance, puis la trahit.

Quoi copier (et quoi éviter)

Règles qui fonctionnent en environnements réels :

  • Ne copiez jamais les numéros de ligne. Ce sont des éléments d’interface. Gardez-les en dehors du nœud de texte copié.
  • Ne copiez pas les invites par défaut. Les invites sont utiles visuellement (« c’est une commande »), mais elles empoisonnent le collage dans un shell non interactif.
  • Normalisez les fins de ligne en \n lors du copier. Le contenu du presse‑papier doit être cohérent entre OS ; le terminal s’adaptera.
  • Supprimez exactement une nouvelle ligne finale (optionnel). GitHub copie typiquement sans ajouter d’espaces étranges ; correspondez à cette attente.
  • Ne transformez pas les tabulations en espaces. Les gens copient des Makefiles, YAML et Python. N’êtes pas « aidant ».

Blague #1 : Les boutons Copier sont comme les sauvegardes—tout le monde suppose qu’ils fonctionnent jusqu’au jour où ils ne fonctionnent plus.

API du presse‑papier et solutions de repli

Les navigateurs modernes supportent navigator.clipboard.writeText(), mais vous devez prévoir :

  • Contraintes de permission (certains contextes restreignent l’accès au presse‑papier).
  • HTTP vs HTTPS (l’API presse‑papier veut généralement un contexte sécurisé).
  • Content Security Policy (les scripts inline et handlers d’événements peuvent être bloqués).

Conseils d’implémentation :

  • Privilégiez un élément button avec type="button".
  • Définissez aria-label="Copy code".
  • Utilisez une région aria-live pour le retour « Copié », pas des popups d’alerte.
  • Copiez depuis une chaîne stockée (le copyTextOverride du modèle ou le code brut), pas depuis le innerText du DOM affiché, qui peut inclure numéros et spans cachés.

Invites : les afficher, ne pas les copier

Les extraits de type invite sont appréciés car ils communiquent rapidement le contexte. Mais ils cassent aussi le copier‑coller. Le compromis sensé :

  • Rendez les invites visuellement (par ex. avec un span séparé).
  • Stockez une charge utile de copie sans invites.
  • Proposez une option « copier avec invites » pour les docs de formation, si nécessaire.

Numéros de ligne : friandise UX avec bords tranchants

Les numéros de ligne améliorent la collaboration : « modifiez la ligne 42 » est une instruction claire. Mais ils ont des pièges.

Comment les numéros de ligne foirent

  • Ils se font copier. Si vous les implémentez en insérant des nœuds de texte réels au début des lignes, ils fuiteront dans la sélection et le presse‑papier.
  • Ils dérivent. Si le wrapping change ce que les utilisateurs perçoivent comme « une ligne », ils référeront la mauvaise chose.
  • Ils cassent la recherche. Certaines implémentations modifient le DOM de sorte que la recherche dans la page ne trouve plus proprement les segments de code attendus.
  • Ils ralentissent le rendu. Fragmenter en milliers d’éléments ligne par ligne peut provoquer une explosion du DOM.

Patrons d’implémentation qui tiennent la charge

Deux patrons tiennent généralement :

  1. Compteurs CSS pour les numéros, sans insérer les nombres dans le texte. C’est rapide et la sélection peut rester propre.
  2. Colonne de gutter séparée avec les numéros de ligne comme éléments propres, tandis que le texte du code reste un bloc sélectionnable séparé.

Si vous surlignez en enveloppant chaque ligne dans un élément, vous séparez déjà les lignes. C’est acceptable pour de petits blocs, mais imposez un seuil. Au‑delà d’une certaine taille, basculez vers « pas de DOM par ligne ».

Règle opérationnelle : si un bloc de code dépasse quelques milliers de lignes, ne rendez pas des spans par ligne dans le navigateur. Rendez un simple <pre> ou proposez un téléchargement.

Lignes surlignées : le moyen le plus rapide de réduire les erreurs

Surligner des lignes n’est pas de la décoration. C’est une garde‑fou. Bien utilisé, cela réduit la charge cognitive de « quelle partie dois‑je modifier ? »

Bonnes utilisations

  • Signaler des modifications dans des fichiers de config : affichez le fichier complet, surlignez seulement les lignes qui diffèrent.
  • Pointer des commandes dangereuses : surlignez la ligne destructive dans un extrait multi‑étapes.
  • Enseignement de type diff : surlignez les lignes qui correspondent à une demande de changement d’une revue.

Mauvaises utilisations

  • Surligner la moitié du bloc. Ce n’est pas de l’emphase ; c’est une crise de surligneur.
  • Utiliser une couleur de surlignage à faible contraste en mode sombre. Les gens le manqueront.
  • Surligner basé sur des « lignes visuelles renvoyées à la ligne ». C’est un cauchemar. N’utilisez que des lignes logiques.

Format d’écriture : restez ennuyeux

N’inventez pas un mini‑langage pour les plages de lignes. Utilisez le format établi « 1,3-5,8 ». Parsez‑le de façon déterministe et échouez bruyamment.

Si l’auteur demande des lignes en dehors de la longueur du bloc, vous avez deux choix sensés :

  • Faire échouer la build (ma préférence pour les runbooks), ou
  • Avertir et ignorer (acceptable pour les blogs).

Barres de titre : noms de fichiers, étiquettes de langage et métadonnées

Une barre de titre est utile quand elle donne de l’orientation. « Voici /etc/nginx/nginx.conf » est exploitable. « Code » ne l’est pas.

Que doit‑on inclure

  • Nom de fichier ou étiquette (par ex. values.yaml, docker-compose.yml).
  • Langage (une petite étiquette aide : bash, yaml, json).
  • Bouton Copier avec affordance claire.
  • Option « voir brut » pour les très gros blocs (servir comme fichier, pas comme DOM de 10k lignes).

Ne pas surcharger

Les barres de titre ne sont pas des tableaux de bord. Si vous y caser des hashes de commit, timestamps et noms d’environnement, vous avez construit un accordéon de distractions. Gardez‑la minimale, cohérente et stable sur tout le site.

Performance et contraintes opérationnelles (oui, vraiment)

Les blocs de code deviennent un problème de performance dans trois scénarios prévisibles :

  • Beaucoup de blocs sur une page (les runbooks sont souvent denses).
  • Blocs immenses (configs générés, logs, manifests Kubernetes).
  • Coloration côté client (pics CPU, tâches longues, saccades).

Ce qu’il faut budgéter

Pensez en budgets comme pour une API :

  • CPU : évitez de tokenizer de gros contenus côté client.
  • Nœuds DOM : évitez les wrappers par ligne au‑delà d’un seuil.
  • Bytes JS : ne chargez pas 40 langages si vous n’en avez besoin que de 6.
  • Fonts : une police monospace de repli suffit ; ne bloquez pas le rendu pour des fontes fancy.

Le cache compte (même pour le surlignage)

Si vous faites la coloration côté serveur ou au build, mettez en cache la sortie par une clé stable : hash(code + language + theme + highlighter-version). Sinon, vous re-surlignez les mêmes extraits à chaque build ou requête, et votre CI commencera à ressembler à une ferme de minage.

Sécurité : traitez les blocs de code comme du texte non fiable

Si votre système rend du code généré par les utilisateurs, supposez que le contenu est hostile. La coloration injecte souvent des spans HTML ; si vous ne nettoyez pas correctement, vous pouvez créer des XSS via le « code ».

La méthode la plus sûre est de rendre les tokens en HTML avec l’échappement centralisé, et ne jamais permettre de passage d’HTML brut dans les blocs de code.

Instrumentation et surveillance pour les blocs de code

Si vous déployez un bouton Copier et que vous ne le mesurez jamais, vous apprendrez les défaillances via des humains en colère. Instrumentez‑le.

Que mesurer

  • Taux de succès du copier (promise résolue vs rejetée).
  • Temps jusqu’à interaction sur les pages avec beaucoup de blocs de code.
  • Tâches longues après le chargement de la page (la coloration côté client est souvent coupable).
  • Nombre de nœuds DOM sur les pages lourdes (proxy pour « on a wrapper chaque ligne »).
  • Durée du surlignage au build (si ça spike, vous avez changé quelque chose).

Journalisation sans être indiscret

Ne journalisez pas le contenu du code lors des événements de copie. Vous vous retrouverez avec des secrets, tokens et clés API dans l’analytics. Journalisez seulement des métadonnées : id de page, langage, longueur du bloc, présence d’invites, succès/échec.

Blague #2 : La seule chose plus sensible que des secrets de production est la réaction du juridique quand vous les journalisez.

Mode opératoire de diagnostic rapide

Quand les utilisateurs se plaignent que « les blocs de code sont lents » ou « le copier ne marche pas », ne débattez pas esthétique. Faites le triage comme un incident.

Première étape : confirmez le mode de défaillance en 60 secondes

  • Exactitude du copier : cliquez copier, collez dans un éditeur de texte simple, vérifiez la présence de numéros/prompts/espaces bizarres.
  • Console du navigateur : cherchez des erreurs de permission du presse‑papier ou des violations CSP.
  • Performance de la page : ouvrez devtools performance, rechargez, cherchez des tâches longues autour du surlignage/hydratation.

Deuxième étape : localisez le goulot

  • Bound CPU : beaucoup de ms en exécution JS → probablement coloration côté client, DOM par ligne, ou sélecteurs coûteux.
  • Bound DOM : layout/recalc style domine → trop de nœuds, CSS lourd, logique de wrapping.
  • Bound réseau : bundles JS ou fontes volumineux → highlighter ou packs de langages expédiés inutilement.

Troisième étape : appliquez un correctif chirurgical, pas une réécriture

  • Déplacez la coloration au build/SSR.
  • Réduisez les langages embarqués.
  • Arrêtez d’envelopper les lignes au‑delà d’un seuil.
  • Copiez depuis la chaîne source, pas depuis le DOM.
  • Ajoutez les invites visuellement via pseudo‑éléments CSS ou spans séparés exclus du payload de copie.

Heuristique : si la page lente a 10+ blocs de code et que le pic CPU corrèle avec les fonctions de « highlight », la correction est architecturale, pas des micro‑optimisations.

Tâches pratiques avec commandes, sorties et décisions

Voici les vérifications à exécuter quand les blocs de code se comportent mal. Chaque tâche inclut une commande, ce que signifie la sortie, et la décision à prendre. Les commandes supposent que vous travaillez sur un hôte Linux où tourne le site de docs ou la build.

Tâche 1 : Vérifier les versions de Node et du gestionnaire de paquets (reproductibilité)

cr0x@server:~$ node --version
v20.11.1

Signification : vous êtes sur Node 20 ; les polyfills du presse‑papier et les toolchains se comportent différemment selon les versions majeures.

Décision : épinglez Node dans la CI (et localement via les outils) si vous observez des sorties de coloration incohérentes entre environnements.

Tâche 2 : Mesurer le coût du surlignage au build (est‑ce le goulot ?)

cr0x@server:~$ /usr/bin/time -v npm run build
...
User time (seconds): 58.23
System time (seconds): 6.12
Percent of CPU this job got: 342%
Elapsed (wall clock) time: 0:18.74
Maximum resident set size (kbytes): 912344

Signification : beaucoup de CPU, ~900MB RSS. Des surligneurs comme Shiki peuvent être gourmands en mémoire avec de nombreuses pages/langages.

Décision : mettez en cache la sortie surlignée et limitez les langages supportés ; si le RSS menace les conteneurs CI, segmentez les builds ou précalculez.

Tâche 3 : Trouver les pages les plus lourdes par nombre de blocs (points chauds)

cr0x@server:~$ rg -n "```" -S docs/ | cut -d: -f1 | sort | uniq -c | sort -nr | head
  84 docs/runbooks/storage/zfs-replace-disk.md
  62 docs/runbooks/kubernetes/etcd-restore.md
  51 docs/platform/nginx/hardening.md

Signification : ces fichiers ont le plus de blocs délimités.

Décision : testez la charge de ces pages en priorité ; optimisez les plus gros coupables avant de chercher des gains marginaux ailleurs.

Tâche 4 : Confirmer que votre HTML rendu ne copie pas les numéros de ligne (vérification rapide)

cr0x@server:~$ rg -n "data-line-number|class=\"line-number\"" -S dist/ | head
dist/runbooks/storage/zfs-replace-disk/index.html:412: 1
dist/runbooks/storage/zfs-replace-disk/index.html:413: 2

Signification : les numéros de ligne sont de vrais nœuds de texte/spans, qui peuvent finir dans la sélection/le presse‑papier.

Décision : passez aux compteurs CSS ou à un gutter séparé exclu de la sélection/copie, ou assurez‑vous que le chemin de copie utilise le code brut, pas le texte du DOM.

Tâche 5 : Détecter des Unicode suspects dans les extraits (exactitude du presse‑papier)

cr0x@server:~$ python3 -c 'import sys,unicodedata; s=open("docs/runbooks/kubernetes/etcd-restore.md","r",encoding="utf-8").read(); bad=[c for c in s if unicodedata.category(c) in ("Cf",)]; print(len(bad), sorted(set(hex(ord(c)) for c in bad))[:10])'
3 ['0x200b', '0x2060']

Signification : caractères de format (zero-width space, word joiner) présents. Ils peuvent casser des commandes collées.

Décision : ajoutez un hook pre-commit ou un lint CI qui rejette ces caractères dans la doc, ou au moins les signale.

Tâche 6 : Confirmer que le bundle JS n’embarque pas 40 langages (maîtrise du poids)

cr0x@server:~$ ls -lh dist/assets | sort -k5 -h | tail
-rw-r--r-- 1 cr0x cr0x  84K app.css
-rw-r--r-- 1 cr0x cr0x 312K app.js
-rw-r--r-- 1 cr0x cr0x 1.8M highlight.bundle.js

Signification : le bundle du surligneur domine votre payload JS.

Décision : passez au surlignage au build ou éliminez les langages inutiles ; n’acceptez pas une taxe de 1.8MB pour de jolies couleurs.

Tâche 7 : Vérifier les violations CSP affectant le presse‑papier

cr0x@server:~$ rg -n "Content-Security-Policy" -S nginx/conf.d/docs.conf
12:add_header Content-Security-Policy "default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self';" always;

Signification : les scripts inline sont bloqués ; si votre bouton Copier dépend d’handlers inline, il échouera silencieusement.

Décision : déplacez la logique de copie dans un JS externe, évitez les attributs d’événements inline, ou ajoutez une politique basée sur des nonces si nécessaire.

Tâche 8 : Valider que la sortie build a des ancres stables pour les surlignages

cr0x@server:~$ rg -n "data-highlight|data-line" -S dist/runbooks/storage/zfs-replace-disk/index.html | head
615: 

Signification : les métadonnées de surlignage sont présentes dans le HTML ; votre client peut les styliser sans re-tokenizer.

Décision : conservez les plages de surlignage comme attributs data ; évitez de recomputer les maps de lignes dans le navigateur.

Tâche 9 : Identifier les blocs surdimensionnés qui devraient être « voir brut »

cr0x@server:~$ awk 'BEGIN{in=0; n=0} /^```/{in=!in; if(!in){print n; n=0}} {if(in) n++}' docs/runbooks/storage/zfs-replace-disk.md | sort -nr | head
412
188
141

Signification : il y a un extrait de 412 lignes ; pas énorme, mais candidat à des problèmes de performance si vous wrapper chaque ligne.

Décision : définissez un seuil (ex. 200–500 lignes) où le DOM par ligne est désactivé ou basculé en mode léger.

Tâche 10 : Confirmer que gzip/brotli est activé (goulot réseau)

cr0x@server:~$ nginx -T 2>/dev/null | rg -n "gzip|brotli" | head
gzip on;
gzip_types text/plain text/css application/javascript application/json image/svg+xml;

Signification : gzip est activé ; si votre JS est encore lourd, la compression aide mais ne résout pas le coût CPU.

Décision : gardez la compression, mais concentrez‑vous sur la réduction du JS et du DOM plutôt que de célébrer des transferts plus petits.

Tâche 11 : Contrôle ponctuel pour explosions DOM par ligne (proxy nombre de nœuds)

cr0x@server:~$ rg -n "class=\"line\"" -S dist/runbooks/kubernetes/etcd-restore/index.html | wc -l
6220

Signification : des milliers d’éléments par ligne ont été émis.

Décision : arrêtez d’émettre des wrappers par ligne pour les gros blocs ; utilisez des compteurs CSS ou un bloc tokenisé unique.

Tâche 12 : Vérifier Lighthouse CLI pour les régressions de page (automatisable)

cr0x@server:~$ lighthouse dist/runbooks/kubernetes/etcd-restore/index.html --quiet --chrome-flags="--headless" --only-categories=performance
Performance: 62

Signification : score de performance médiocre ; les blocs de code sont souvent les coupables à cause de JS lourd ou d’un DOM dense.

Décision : profilez la page ; si des tâches longues corrèlent avec le surlignage, déplacez le travail au build et réduisez la complexité du DOM.

Tâche 13 : Vérifier que les invites ne sont pas incluses dans les payloads de copie

cr0x@server:~$ rg -n "cr0x@server:~\\$" -S dist/ | head
dist/runbooks/storage/zfs-replace-disk/index.html:618: cr0x@server:~$ zpool status

Signification : des chaînes d’invite apparaissent dans le HTML rendu. C’est acceptable visuellement, risqué si votre logique de copie gratte le texte du DOM.

Décision : stockez une chaîne de commande brute séparée pour la copie, ou marquez les spans d’invite avec data-no-copy et appliquez‑le dans la logique de copie.

Tâche 14 : Confirmer qu’aucun secret n’est intégré dans les blocs de code (oui, ça arrive)

cr0x@server:~$ rg -n "AKIA|BEGIN PRIVATE KEY|password\s*=" -S docs/ | head
docs/runbooks/app/deploy.md:203: password = "changeme"

Signification : motifs suspects présents ; parfois exemples, parfois vrais secrets.

Décision : appliquez des règles de redaction ; en production, utilisez des placeholders et un workflow de gestion de secrets, pas des identifiants en ligne.

Trois mini-histoires d’entreprise depuis le terrain

Mini-histoire 1 : L’incident causé par une mauvaise hypothèse

L’entreprise avait un « Engineering Handbook » interne que tout le monde utilisait. Il avait l’air moderne : belle typographie, blocs de code soignés et un bouton Copier. Une équipe a publié un guide de migration pour faire tourner les identifiants de base de données, avec une douzaine de commandes shell.

Quelqu’un a supposé que les invites étaient inoffensives. L’auteur a écrit des exemples comme dbadmin@bastion:~$ psql ... et le moteur a stocké exactement cela dans le nœud de texte du bloc. Le bouton Copier a copié ce qu’il voyait.

Ça fonctionnait pour ceux qui collent dans un shell interactif et suppriment manuellement l’invite. Ça a échoué pour l’automatisation. Quelques ingénieurs, en train d’exécuter la rotation sous pression, ont collé le tout dans un runner shell non interactif qui traite les tokens inconnus comme commandes. Le premier token était dbadmin@bastion:~$. Le runner a échoué rapidement, mais le workflow n’a pas. Il a interprété l’échec comme « passer à l’étape suivante ».

Le résultat n’a pas été catastrophique, mais bruyant : modifications partielles, logs confus, et un utilisateur de base de données verrouillé plus tôt que prévu. L’analyse post‑incident a été embarrassante car la cause racine n’était ni PostgreSQL ni IAM. C’était un widget UI de doc qui copiait les mauvais octets.

La correction a été simple : les invites sont devenues des spans affichés seulement, le copier utilisait une payload sans invite, et la build de doc a commencé à linter les motifs d’invite dans les blocs « copiables ». La partie intéressante a été culturelle : après cela, l’équipe docs a été invitée aux revues d’incident. Ils l’avaient mérité.

Mini-histoire 2 : L’optimisation qui s’est retournée contre eux

Une autre organisation a décidé que leur site de docs devait supporter le « changement de thème à chaud » entre clair et sombre sans recharger. Ils sont passés de la coloration au build à la coloration côté client pour que le navigateur puisse recolorer dynamiquement les tokens.

Sur le papier, cela semblait propre : expédier du code brut, lancer Prism dans le navigateur, appliquer des thèmes CSS. En pratique, le site avait des runbooks longs avec beaucoup de blocs, certains grands (manifests Kubernetes, timelines d’incident, extraits de logs). À chaque chargement, la tokenization s’exécutait sur le thread principal.

Ils ont constaté l’impact sur la performance et ont tenté d’optimiser. L’« optimisation » a été d’envelopper chaque ligne dans un span pour simplifier le surlignage et les numéros. Cela a massivement augmenté les nœuds DOM. Le navigateur passait plus de temps en recalcul de styles et layout que dans la coloration elle‑même.

Puis le vrai coup : sur des laptops bas de gamme et certains environnements VDI, le défilement est devenu haché. Les gens copiaient moins car l’UI semblait peu fiable. Le projet a obtenu le changement de thème mais perdu la confiance—un mauvais échange.

Le rollback a été pragmatique. Ils ont gardé le changement de thème pour la chrome de la page, mais les blocs de code sont redevenus colorés au build avec deux thèmes pré‑calculés. Le switch de thème changeait une classe et des variables CSS ; les blocs utilisaient des spans tokenisés pré‑rendus. Ce n’était pas « pur ». C’était rapide et stable. C’est ce qui compte.

Mini-histoire 3 : La pratique ennuyeuse et correcte qui a sauvé la mise

Une entreprise de services financiers maintenait des runbooks internes stricts. Les docs étaient statiques, construites en CI et publiées derrière authentification. Rien d’extravagant. Ce qu’ils avaient, c’était un pipeline de lint impitoyable.

Chaque pull request lançait une vérification de la doc qui validait les fences de code : les langages devaient être reconnus, les plages de surlignage valides, et les caractères interdits (zero-width spaces, espaces insécables dans des commandes) étaient bloqués. On vérifiait aussi que les blocs terminal utilisaient un format d’invite cohérent et fournissaient une payload de copie sans invite.

Un semaine, un fournisseur leur a envoyé un « script de correction » intégré dans un PDF. Un ingénieur l’a copié dans un runbook. Le script contenait un espace insécable entre un flag et son argument—visuellement indiscernable dans l’éditeur utilisé. Le linter l’a détecté immédiatement, a fait échouer la build et a imprimé le point de code Unicode.

L’ingénieur a râlé, a remplacé le caractère et a continué. Deux jours plus tard, le script a été exécuté pendant un incident en direct. Il a fonctionné. Personne n’a reparlé du linter, ce qui est le plus grand compliment pour la correction ennuyeuse.

Ce pipeline n’était pas glamour. Il n’a pas gagné de prix de design. Il a évité une classe d’échecs qui ne se montrent que sous stress. En ops, c’est une victoire.

Erreurs courantes : symptômes → cause → correctif

Cette section vous semblera familière avec du recul. Épargnez‑vous le recul.

1) « Copier » inclut les numéros de ligne

  • Symptômes : le code collé commence par 1, 2, ou des chiffres au début de chaque ligne ; les commandes échouent.
  • Cause : les numéros sont insérés comme nœuds de texte/spans réels dans la zone sélectionnable ; la logique de copie scrappe innerText.
  • Correctif : copiez depuis la chaîne brute du modèle ; rendez les numéros via compteurs CSS ou gutter séparé ; n’injectez jamais les nombres dans le texte du code.

2) Le bouton Copier ne fait rien en production mais marche localement

  • Symptômes : aucune erreur affichée ; les utilisateurs disent « le bouton Copier est mort ».
  • Cause : CSP bloque les scripts inline ou handlers ; l’API presse‑papier exige un contexte sécurisé ; les permissions diffèrent.
  • Correctif : déplacez la logique en JS externe ; assurez HTTPS ; ajoutez de la télémétrie pour les échecs de copie et proposez une alternative « sélectionner le code ».

3) Les lignes surlignées sont décalées d’un

  • Symptômes : l’auteur surligne la ligne 5, mais l’UI surligne la 4 ou la 6.
  • Cause : discordance sur la façon de compter les lignes (newline initiale, trimming, CRLF vs LF) ou parser qui compte depuis 0 alors que l’UI compte depuis 1.
  • Correctif : normalisez les fins de ligne à l’ingestion ; définissez la numérotation 1‑based ; ajoutez des tests pour les cas limites (newline initiale/finale).

4) Défilement et saisie saccadés sur des pages avec gros blocs

  • Symptômes : jank, scroll lent, CPU élevé, ventilateurs qui s’emballent.
  • Cause : coloration côté client et/ou wrappers par ligne créant des milliers de nœuds ; CSS trop coûteux.
  • Correctif : faites la coloration au build/SSR ; limitez le DOM par ligne ; simplifiez le CSS ; utilisez la virtualisation uniquement si vraiment nécessaire.

5) Les utilisateurs collent des guillemets typographiques ou des tirets incorrects

  • Symptômes : les flags semblent corrects mais le shell renvoie des erreurs ; le texte collé contient une ponctuation étrange.
  • Cause : transformations typographiques ou éditeurs riches qui remplacent des caractères (tiret long vs trait d’union, guillemets courbes).
  • Correctif : assurez que les blocs de code restent du texte brut ; verrouillez les éditeurs ; lint pour les Unicode suspects dans les fences.

6) La recherche dans la page ne trouve pas le code

  • Symptômes : la recherche du navigateur ne trouve pas une chaîne visible dans le bloc de code.
  • Cause : la tokenization insère des spans qui scindent le texte ; certaines implémentations de recherche échouent, ou le contenu est rendu via canvas/DOM virtuel bizarre.
  • Correctif : conservez le code comme nœuds de texte réels dans le DOM ; ne rendez pas le code via canvas ; évitez la restructuration DOM agressive.

7) Les numéros de ligne cassent le wrapping et l’overflow

  • Symptômes : le code chevauche le gutter ; le scroll horizontal est cassé ; les chiffres se désalignent.
  • Cause : largeur du gutter non réservée ; métriques de fonte différentes entre gutter et code ; line-height incohérent.
  • Correctif : utilisez un layout à deux colonnes avec largeur de gutter fixe ; imposez la même police et line-height ; testez sur plusieurs plateformes.

Listes de contrôle / plan étape par étape

Étape par étape : construire un composant de bloc de code style GitHub qui ne vous trahira pas

  1. Choisissez une stratégie de rendu. Préférez build‑time ou SSR pour la documentation ; évitez la tokenization côté client sauf si le contenu est vraiment dynamique.
  2. Définissez le modèle du bloc de code. language, title, code brut, plages de surlignage, showLineNumbers, kind, payload de copie.
  3. Normalisez l’entrée. Convertissez CRLF en LF, conservez les tabulations, préservez les espaces finaux importants, et rejetez les caractères de format en CI.
  4. Implémentez le chemin d’affichage. Rendez la barre de titre + le code ; gardez la structure DOM du code stable.
  5. Implémentez les numéros de ligne en sécurité. Compteurs CSS ou gutter séparé ; n’injectez jamais les nombres dans le texte du code.
  6. Implémentez les lignes surlignées de façon déterministe. Parsez « 1,3-5 » ; validez ; surlignez seulement des lignes logiques.
  7. Implémentez la copie depuis la payload stockée. Ne scrappez pas le DOM ; gérez les échecs du presse‑papier avec une solution de repli (sélection + copie manuelle).
  8. Ajoutez l’accessibilité. Focus clavier, labels aria, région live pour le retour, contraste suffisant pour les surlignages.
  9. Définissez des garde‑fous de performance. Caps sur le DOM par ligne ; mode « voir brut » pour les blocs énormes ; limitation des bundles de langages.
  10. Ajoutez de la télémétrie. Succès/échec du copier, erreurs de parsing de surlignage, marques de performance sur les pages lourdes.
  11. Écrivez des tests. Tests snapshot pour la structure HTML ; tests unitaires pour le parsing des plages ; e2e pour le payload de copie.
  12. Documentez les règles d’écriture. Comment spécifier titres, invites et surlignages ; ce qui est copié ; ce qui ne l’est pas.

Checklist pré‑merge pour les auteurs de docs (couche humaine)

  • Avez‑vous marqué les invites terminal comme affichage‑seulement ?
  • Y a‑t‑il de la « ponctuation intelligente » dans les fences de code ?
  • Les lignes surlignées sont‑elles dans la longueur du bloc ?
  • Envoyez‑vous des secrets, tokens, ou hostnames réels qui devraient être des placeholders ?
  • Le bloc de code est‑il trop grand pour une page ? Devrait‑il être un téléchargement de fichier ?
  • Avez‑vous essayé le bouton Copier et collé dans un éditeur de texte brut ?

Checklist Ops : lors du déploiement de changements de rendu de blocs de code

  • Pouvez‑vous rollback le renderer indépendamment du contenu ?
  • Les caches sont‑ils clefés par version du surligneur et thème ?
  • Avez‑vous une page canari avec les pires blocs pour tester la performance ?
  • Le CSP est‑il appliqué en staging exactement comme en production ?
  • Alertez‑vous sur les erreurs JS affectant les interactions de copie ?

FAQ

1) Dois‑je toujours ajouter des numéros de ligne ?

Non. Ajoutez‑les quand l’extrait est référencé par ligne dans le texte environnant, ou quand il est suffisamment long pour en tirer profit. Pour des commandes de 5 lignes, les numéros sont du bruit.

2) Comment empêcher que les numéros de ligne soient copiés ?

Ne les rendez pas comme partie du texte du code. Utilisez des compteurs CSS ou un gutter séparé. Et copiez depuis une chaîne brute, pas depuis innerText.

3) Les invites doivent‑elles être incluses dans les fences de code ?

Visuellement, oui—les invites communiquent « c’est une commande ». Dans la payload de copie, généralement non. Si vous devez supporter les deux, proposez deux modes de copie.

4) Pourquoi ne pas utiliser Prism côté client partout ?

Parce que cela pousse les coûts CPU et JS sur chaque lecteur, à chaque vue de page. Pour la doc, c’est une taxe à long terme que vous n’avez pas besoin de payer si vous pouvez pré‑rendre.

5) Quelle est la façon la plus propre de supporter une barre de titre en Markdown ?

Utilisez une syntaxe metadata conventionnelle que votre parser peut lire (comme une extension info string) et mappez‑la vers le modèle de bloc de code. Ne parsez pas les barres de titre depuis des commentaires dans le code.

6) Comment les lignes surlignées interagissent‑elles avec les lignes renvoyées à la ligne ?

Elles ne devraient pas. Surlignez uniquement des lignes logiques. Le wrapping est un détail de présentation et varie selon la viewport, la fonte et les réglages utilisateur.

7) Comment gérer les blocs énormes (logs, fichiers générés) ?

Ne les rendez pas comme un DOM entièrement tokenisé par ligne. Fournissez un aperçu tronqué et un bouton « voir brut » pour le téléchargement. Gardez la page rapide.

8) Et l’accessibilité—les blocs de code ont‑ils besoin d’ARIA ?

Le bloc de code lui‑même doit rester du HTML standard (<pre><code>). Le bouton Copier a besoin d’un label, du focus clavier, et d’un retour non intrusif via une région aria‑live.

9) Pourquoi mes lignes surlignées se décalent entre environnements ?

Souvent la normalisation des fins de ligne (CRLF vs LF) ou des différences de trimming. Normalisez à l’ingestion et testez avec des fixtures qui incluent des fins de ligne Windows.

10) Puis‑je instrumenter en toute sécurité les événements de copie ?

Oui—logguez uniquement des métadonnées. Ne journalisez jamais le contenu copié. Supposez que les extraits peuvent contenir des secrets même lorsqu’ils « ne devraient pas ».

Prochaines étapes qui livrent vraiment

Si vous voulez des blocs de code façon GitHub sans transformer votre plateforme de docs en projet scientifique, faites ceci dans l’ordre :

  1. Définissez le contrat du composant (champs du modèle, règles de payload de copie, règles de plages de surlignage).
  2. Déplacez la coloration hors du client sauf si votre contenu est vraiment dynamique.
  3. Implémentez la copie depuis la source, pas depuis le texte rendu du DOM.
  4. Mettez des garde‑fous de performance (max lignes pour rendu par ligne, max langages embarqués).
  5. Lintiez la doc pour les hazards Unicode, les plages de surlignage invalides et l’usage d’invites.
  6. Instrumentez les échecs de copie et la performance des pages sur vos pires runbooks.

Puis déployez. Surveillez les métriques. Si vous ne voyez pas moins de pings « la doc a cassé ma commande », votre chemin de copie ment encore à quelqu’un.

← Précédent
Faux positifs Rspamd : régler le scoring anti-spam sans laisser passer les indésirables
Suivant →
Proxmox « Impossible de supprimer le nœud » : suppression sûre d’un nœud d’un cluster

Laisser un commentaire