En bref
Pretext.js est une bibliothèque TypeScript sans dépendance qui mesure et positionne le texte multiligne via des calculs purement arithmétiques au lieu d'opérations DOM. Elle élimine les reflows synchrones forcés, offre une mesure de texte environ 500 fois plus rapide que getBoundingClientRect(), et prend en charge tous les principaux systèmes d'écriture de la planète. Si vous construisez des défilements virtuels, des interfaces de chat ou des grilles de données, cette bibliothèque résout un problème que les navigateurs ont ignoré pendant 30 ans.
Introduction
Chaque fois que votre JavaScript appelle getBoundingClientRect() ou lit offsetHeight, le navigateur arrête tout. Il vide les changements de style en attente, recalcule la mise en page et force un rendu complet. C'est ce qu'on appelle le reflow synchrone forcé, et c'est l'opération la plus coûteuse qu'un navigateur puisse effectuer.
Multipliez cela par 1 000 bulles de chat dans une liste virtuelle. Ou 10 000 lignes dans une grille de données. Le résultat ? Des images perdues, des saccades et des utilisateurs qui pensent que votre application est en panne.
Cheng Lou, le développeur derrière react-motion (plus de 21 700 étoiles sur GitHub) et contributeur principal à React et ReasonML chez Meta, a créé Pretext.js pour résoudre ce problème. La bibliothèque a été lancée en mars 2026, a atteint plus de 14 000 étoiles sur GitHub en quelques jours, et a suscité l'un des plus grands débats sur Hacker News de l'année.
Cet article explique ce que fait Pretext.js, comment il fonctionne en coulisses, quand vous devriez l'utiliser et où il est insuffisant. Vous saurez si cette bibliothèque a sa place dans votre stack.
Qu'est-ce que Pretext.js ?
Pretext.js est un moteur de mise en page de texte pur JavaScript/TypeScript. Il mesure et positionne le texte multiligne entièrement par calcul arithmétique ; pas de getBoundingClientRect(), pas de offsetHeight, pas de reflow, pas de surcharge.

L'idée principale est simple. Au lieu de demander au navigateur "quelle est la hauteur de ce texte ?" (ce qui force le navigateur à le rendre d'abord), Pretext.js calcule la réponse mathématiquement en utilisant les métriques de police de l'API Canvas.
Voici toute la surface de l'API :
import { prepare, layout } from '@chenglou/pretext';
// Étape 1 : Préparer le texte (une seule fois, cachable)
const handle = prepare('Hello, pretext.js', '16px "Inter"');
// Étape 2 : Mettre en page à n'importe quelle largeur (arithmétique pure, microsecondes)
const { height, lineCount } = layout(handle, 400, 24);
C'est tout. Deux fonctions. L'une mesure les segments de texte et les met en cache. L'autre effectue des calculs arithmétiques pour déterminer la mise en page. L'appel à prepare() est la seule opération qui touche le navigateur (via measureText() de Canvas). Après cela, layout() est purement mathématique.
Pourquoi cela est important pour les applications gourmandes en API
Si vous développez des applications qui consomment des réponses API en streaming, comme des assistants IA, des tableaux de bord en temps réel ou des éditeurs collaboratifs, vous devez connaître la hauteur du texte entrant avant de le rendre. Sans cela, votre défilement virtuel saccade, votre interface de chat saute et vos utilisateurs le remarquent.
Pretext.js vous donne cette hauteur en microsecondes au lieu de millisecondes. La différence s'accumule rapidement.
Le problème résolu par Pretext.js
Pour comprendre pourquoi cette bibliothèque existe, vous devez comprendre ce qui se passe lorsque JavaScript lit les propriétés de mise en page.
Explication du reflow synchrone forcé
Lorsque vous écrivez ce code :
const elements = document.querySelectorAll('.text-block');
elements.forEach(el => {
const height = el.getBoundingClientRect().height; // REFLOW!
// use height for positioning...
});
Chaque appel à getBoundingClientRect() force le navigateur à :
- Mettre en pause l'exécution de JavaScript
- Vider toutes les modifications de style en attente
- Recalculer la mise en page pour l'ensemble du document (ou du sous-arbre)
- Renvoyer la valeur calculée
C'est ce qu'on appelle le "layout thrashing". Dans une boucle mesurant 1 000 éléments, le navigateur effectue 1 000 recalculs de mise en page complets. Le coût ? Environ 94 millisecondes, ce qui signifie 6 images perdues à 60 ips.
Le problème du défilement virtuel
Les bibliothèques de défilement virtuel (comme react-window ou tanstack-virtual) ont besoin de connaître la hauteur de chaque élément pour calculer les positions de défilement. Pour les éléments à hauteur fixe, c'est trivial. Pour le contenu textuel à hauteur variable, c'est un cauchemar.
La solution de contournement standard consiste à rendre les éléments hors écran, à les mesurer, puis à les positionner. Cela fonctionne mais va à l'encontre du but du défilement virtuel ; vous rendez des nœuds DOM que vous essayez d'éviter de rendre.
Certaines bibliothèques estiment les hauteurs et les corrigent après le rendu, ce qui provoque des sauts visibles. D'autres obligent les développeurs à spécifier des hauteurs fixes, limitant ce que vous pouvez afficher.
Pretext.js élimine toute cette catégorie de solutions de contournement. Vous calculez la hauteur exacte du texte avant qu'aucun nœud DOM n'existe.
Chiffres réels
Pretext.js a publié les résultats de ses benchmarks sur son site :
| Approche | 1 000 blocs de texte | 500 blocs de texte |
|---|---|---|
DOM (getBoundingClientRect) |
~94ms (6 images perdues) | ~47ms |
Pretext.js (layout()) |
~2ms | ~0.09ms |
| Différence de vitesse | ~47x plus rapide | ~500x plus rapide |
L'amélioration de la vitesse est plus spectaculaire avec des lots plus petits car la surcharge par appel de la mesure DOM reste constante tandis que le coût arithmétique de Pretext scale linéairement.
Comment Pretext.js fonctionne en coulisses
La bibliothèque opère en trois phases distinctes. Comprendre celles-ci vous aide à optimiser son utilisation.
Phase 1 : Segmentation du texte
Lorsque vous appelez prepare(), Pretext.js normalise d'abord votre texte d'entrée. Il gère les espaces blancs, applique les règles de saut de ligne Unicode (UAX #14) et segmente le texte en unités sécables.
C'est là qu'intervient le support multilingue. Le moteur de segmentation gère correctement :
- Les caractères CJK (chinois, japonais, coréen) : chaque caractère est un point de rupture valide
- L'arabe et l'hébreu : texte de droite à gauche avec des marqueurs bidirectionnels
- Le thaï : pas d'espaces entre les mots, nécessite une segmentation basée sur un dictionnaire
- L'hindi/devanagari : consonnes conjonctives et ligatures complexes
- Les emojis : gère correctement les séquences d'emojis multi-codepoints (drapeaux, tons de peau, séquences ZWJ)
- Les traits d'union conditionnels : respecte les opportunités de coupure
­
Phase 2 : Mesure Canvas
Après la segmentation, Pretext.js alimente chaque segment via l'API measureText() de Canvas. C'est le seul appel de navigateur que la bibliothèque effectue, et il est rapide car la mesure de texte Canvas ne déclenche pas de reflow de mise en page.
// Interne : comment Pretext mesure le texte
const ctx = offscreenCanvas.getContext('2d');
ctx.font = '16px "Inter"';
const metrics = ctx.measureText('Hello'); // Pas de reflow !
const width = metrics.width; // Largeur d'avance du glyphe
Les mesures sont mises en cache par segment et par combinaison de polices. Si vous appelez prepare() avec le même texte et la même police deux fois, le deuxième appel réutilise les données mises en cache.
Phase 3 : Mise en page arithmétique pure
La fonction layout() prend les largeurs de segments mises en cache et une largeur de conteneur, puis calcule les sauts de ligne à l'aide d'un algorithme glouton :
- Additionne les largeurs de segments jusqu'à ce que le total dépasse la largeur du conteneur
- Passe à une nouvelle ligne
- Répète jusqu'à ce que tous les segments soient placés
- Multiplie le nombre de lignes par la hauteur de ligne pour obtenir la hauteur totale
Pas de DOM. Pas de Canvas. Pure addition et comparaison.
C'est pourquoi layout() est si rapide ; il fait le même calcul que vous feriez sur papier avec une règle et une calculatrice.
Le modèle de poignée réutilisable
L'une des meilleures décisions de conception dans Pretext.js est que prepare() renvoie une poignée réutilisable. Un seul appel prepare() fonctionne sur toutes les largeurs de conteneur :
const handle = prepare(longArticleText, '16px "Inter"');
// Calcule la hauteur pour mobile, tablette et bureau en microsecondes
const mobile = layout(handle, 375, 24); // { height: 2400, lineCount: 100 }
const tablet = layout(handle, 768, 24); // { height: 1200, lineCount: 50 }
const desktop = layout(handle, 1200, 24); // { height: 720, lineCount: 30 }
Ce modèle est parfait pour les calculs de conception réactive. Vous mesurez une fois et disposez instantanément à n'importe quelle largeur.
Cas d'utilisation pratiques
1. Défilement virtuel avec texte de hauteur variable
C'est le cas d'utilisation principal. Voici comment vous intégreriez Pretext.js avec un défileur virtuel :
import { prepare, layout } from '@chenglou/pretext';
interface TextItem {
id: string;
content: string;
}
function computeHeights(items: TextItem[], containerWidth: number) {
return items.map(item => {
const handle = prepare(item.content, '14px "Inter"');
const { height } = layout(handle, containerWidth, 20);
return { id: item.id, height: height + 32 }; // +32 pour le rembourrage
});
}
// 10 000 éléments mesurés en ~4ms
const heights = computeHeights(chatMessages, 600);
Pas de rendu hors écran. Pas d'estimation de hauteur. Pas de sauts visibles lorsque les éléments défilent à l'écran.
2. Interfaces de chat IA
Les assistants IA diffusent les réponses jeton par jeton. Chaque nouveau jeton peut modifier le nombre de lignes, décalant tout ce qui se trouve en dessous. Avec la mesure DOM traditionnelle, chaque mise à jour de jeton déclenche un reflow.
Avec Pretext.js, vous recalculez la hauteur après l'arrivée de chaque fragment sans toucher le DOM :
let streamedText = '';
const font = '15px "SF Pro"';
socket.on('token', (token: string) => {
streamedText += token;
const handle = prepare(streamedText, font);
const { height } = layout(handle, bubbleWidth, 22);
// Met à jour la position du défileur virtuel sans mesure DOM
scroller.updateItemHeight(messageId, height + padding);
});
3. Grilles de données avec colonnes de texte
Les applications de type tableur nécessitent un dimensionnement automatique des colonnes. Mesurer des milliers de valeurs de cellule via le DOM est coûteux. Pretext.js le rend rapide :
function computeColumnWidth(values: string[], font: string, padding: number) {
let maxWidth = 0;
for (const value of values) {
const handle = prepare(value, font);
// Disposition avec largeur infinie = une seule ligne = largeur naturelle du texte
const { height } = layout(handle, Infinity, 20);
// Utilise le suivi de largeur interne de la poignée pour le dimensionnement des colonnes
maxWidth = Math.max(maxWidth, /* largeur calculée */);
}
return maxWidth + padding;
}
4. Flux de contenu multilingues
Les fils d'actualité des médias sociaux avec du contenu de scripts mixtes (publications chinoises suivies de réponses arabes suivies de commentaires coréens) sont notoirement difficiles à virtualiser car chaque script a des règles de saut de ligne différentes.
Pretext.js les gère tous avec la même API :
const posts = [
{ text: 'This library changed everything', lang: 'en' },
{ text: 'RTL text with correct bidirectional layout', lang: 'ar' },
{ text: 'CJK text gets proper character-level breaks', lang: 'zh' },
];
// Même API, résultats corrects pour chaque script
posts.forEach(post => {
const handle = prepare(post.text, '16px system-ui');
const { height } = layout(handle, 400, 24);
});
Tester votre mise en page de texte avec Apidog
Lorsque vous construisez des interfaces utilisateur riches en texte et alimentées par des API, obtenir la bonne mise en page n'est que la moitié de la bataille. Vous devez également vérifier que les réponses API qui alimentent vos composants texte fournissent les bonnes données, dans le bon format et à la bonne vitesse.

Apidog rend cela simple. Vous pouvez simuler des réponses API en streaming pour tester comment votre intégration Pretext.js gère le chargement progressif du texte. Configurez des scénarios de test avec différentes longueurs de texte, langues et cas limites Unicode, puis vérifiez que votre défileur virtuel se comporte correctement avant le déploiement.
Pour les équipes qui développent des produits de chat IA, l'environnement de test API d'Apidog vous permet de :
- Simuler des réponses en streaming avec du texte chunké pour simuler une sortie LLM réelle
- Tester avec des payloads multilingues pour détecter les bugs de mise en page avant les utilisateurs
- Valider les schémas de réponse pour confirmer que les champs de texte contiennent le format attendu
- Exécuter des suites de tests automatisées qui couvrent vos cas limites de rendu de texte
Cela est important car une bibliothèque de mise en page de texte n'est aussi bonne que les données qui y sont introduites. Des réponses API de mauvaise qualité produisent des mises en page de mauvaise qualité, quelle que soit la vitesse de votre moteur de mesure.
Limitations et critiques connues
Pretext.js n'est pas parfait. Le fil de discussion Hacker News a soulevé plusieurs préoccupations valides qu'il est utile de connaître avant de l'adopter.
Cas limites de précision de rendu
Plusieurs utilisateurs ont signalé que le texte dépassait les cadres dans les démonstrations Safari et Chrome. Les calculs arithmétiques de la bibliothèque peuvent diverger de la mise en page native du navigateur dans des scénarios spécifiques :
- Polices avec des paires de crénage inhabituelles
- Texte avec des tailles de police mixtes au sein d'un même bloc
- Différences de rendu sous-pixel entre Canvas et DOM
- Particularités spécifiques au navigateur en matière de façonnage de texte
Ces cas limites importent moins pour le défilement virtuel (où quelques pixels d'erreur sont invisibles) et davantage pour la composition typographique au pixel près.
La mesure Canvas n'est pas gratuite
L'appel prepare() sollicite toujours le moteur de texte Canvas du navigateur. Pour les applications qui créent des milliers de poignées prepare() uniques par image, cela peut devenir un goulot d'étranglement. La solution est la mise en cache et le traitement par lots, mais la bibliothèque n'impose ni l'un ni l'autre.
Aucune prise en charge des propriétés CSS
Pretext.js mesure le texte brut avec une spécification de police. Il ne tient pas compte des propriétés CSS qui affectent la mise en page :
letter-spacing(espacement des lettres)word-spacing(espacement des mots)text-indent(retrait de texte)text-transform(transformation de texte)font-feature-settings(réglages des fonctionnalités de police)font-variant(variante de police)
Si votre style de texte repose sur ces propriétés CSS, la hauteur calculée ne correspondra pas à ce que le navigateur rend. Vous devrez en tenir compte manuellement ou accepter la divergence.
Il ne remplace pas le rendu DOM
Pretext.js vous indique la hauteur du texte. Il ne rend pas le texte pour vous. Vous avez toujours besoin de nœuds DOM (ou d'un rendu Canvas/SVG) pour afficher le texte. La valeur de la bibliothèque réside dans la phase de mesure, pas dans la phase de rendu.
Pretext.js vs. les approches traditionnelles
| Caractéristique | Pretext.js | Mesure DOM | Hauteurs estimées |
|---|---|---|---|
| Vitesse (1K éléments) | ~2ms | ~94ms | ~0ms (pas de mesure) |
| Précision | Élevée (basée sur Canvas) | Parfaite (vérité absolue) | Faible (heuristique) |
| Dépendance DOM | Aucune après prepare() |
Complète | Aucune |
| Déclencheurs de reflow | Zéro | Un par mesure | Zéro |
| Multilingue | Prise en charge complète d'Unicode | Complète (native au navigateur) | Faible (ratios codés en dur) |
| Prise en charge des propriétés CSS | Limitée (police uniquement) | Complète | Aucune |
| Surcharge mémoire | Segments mis en cache | Nœuds DOM | Minimale |
| Mises en page réactives | Un prepare(), plusieurs layout() |
Re-mesure par largeur | Ré-estimation par largeur |
Le bon choix dépend de vos contraintes. Si vous avez besoin d'une précision au pixel près et d'une prise en charge des propriétés CSS, la mesure DOM reste la vérité absolue. Si vous avez besoin de vitesse sur des milliers d'éléments et que vous pouvez tolérer de légères différences sous-pixel, Pretext.js l'emporte haut la main.
Démarrer
Installation
npm install @chenglou/pretext
# ou
pnpm add @chenglou/pretext
# ou
bun add @chenglou/pretext
Utilisation de base
import { prepare, layout } from '@chenglou/pretext';
// Mesurer un paragraphe
const handle = prepare(
'Pretext.js computes text layout without touching the DOM.',
'16px "Inter"'
);
// Obtenir la hauteur à une largeur de conteneur spécifique
const result = layout(handle, 600, 24);
console.log(result.height); // par ex., 48 (2 lignes x 24px)
console.log(result.lineCount); // par ex., 2
Intégration avec React
import { prepare, layout } from '@chenglou/pretext';
import { useVirtualizer } from '@tanstack/react-virtual';
import { useMemo, useRef } from 'react';
function VirtualChat({ messages }: { messages: string[] }) {
const parentRef = useRef<HTMLDivElement>(null);
const containerWidth = 600;
const font = '14px "Inter"';
const lineHeight = 20;
const heights = useMemo(() => {
return messages.map(msg => {
const handle = prepare(msg, font);
const { height } = layout(handle, containerWidth, lineHeight);
return height + 24; // rembourrage
});
}, [messages]);
const virtualizer = useVirtualizer({
count: messages.length,
getScrollElement: () => parentRef.current,
estimateSize: (index) => heights[index],
});
return (
<div ref={parentRef} style={{ height: '100vh', overflow: 'auto' }}>
<div style={{ height: virtualizer.getTotalSize(), position: 'relative' }}>
{virtualizer.getVirtualItems().map(virtualRow => (
<div
key={virtualRow.key}
style={{
position: 'absolute',
top: virtualRow.start,
width: containerWidth,
}}
>
{messages[virtualRow.index]}
</div>
))}
</div>
</div>
);
}
Cela vous donne un chat virtuel avec des hauteurs d'éléments précises calculées avant que tout message ne soit rendu dans le DOM. Pas d'estimation, pas de sauts de correction, pas de reflow.
Aire de jeux interactive
Le site web de Pretext.js inclut une aire de jeux interactive à l'adresse pretextjs.dev/playground où vous pouvez coller du texte, choisir des polices, ajuster la largeur du conteneur et voir le calcul de la mise en page en temps réel. C'est le moyen le plus rapide de vérifier le comportement avant l'intégration.
Quand vous ne devriez PAS utiliser Pretext.js
Pretext.js n'est pas l'outil idéal pour tous les problèmes de mesure de texte :
- Pages statiques avec un contenu connu : Si votre texte ne change pas et que vous n'effectuez pas de virtualisation, CSS gère très bien la mise en page. Aucune bibliothèque n'est nécessaire.
- Mises en page d'impression au pixel près : Les différences sous-pixel entre la mesure Canvas et le rendu DOM sont importantes à la résolution d'impression. Utilisez le DOM comme vérité fondamentale.
- Style de texte CSS lourd : Si vous comptez sur
letter-spacing,text-indentoufont-feature-settings, les calculs de hauteur divergeront du rendu final. - Rendu côté serveur : Pretext.js dépend de l'API Canvas, qui n'est pas disponible dans Node.js sans polyfills comme
node-canvas. La prise en charge côté serveur est prévue mais n'a pas encore été livrée. - Petites listes statiques : Si vous avez 50 éléments dans une liste, la mesure DOM prend moins de 5 ms. L'optimisation ne vaut pas la dépendance.
FAQ
Pretext.js est-il prêt pour la production ?
La bibliothèque a été lancée en mars 2026 et a gagné plus de 14 000 étoiles sur GitHub en quelques jours. Cheng Lou, le créateur, dirige le frontend de Midjourney ; un système de production servant des millions d'utilisateurs. La suite de tests de la bibliothèque couvre des dizaines de langues et de cas limites. Cela dit, c'est une nouvelle version. Verrouillez votre version et testez-la avec vos polices et votre contenu spécifiques.
Pretext.js fonctionne-t-il avec React, Vue et Svelte ?
Oui. Pretext.js est indépendant du framework. C'est une bibliothèque TypeScript pure avec deux fonctions. Vous appelez prepare() et layout() partout où vous avez besoin de mesures de texte ; dans les hooks React, les composables Vue, les stores Svelte ou le JavaScript pur.
Comment Pretext.js gère-t-il les polices web ?
La fonction prepare() mesure le texte en utilisant la police que le navigateur a chargée au moment de l'appel. Si votre police web n'a pas encore été chargée, la mesure utilisera la police de remplacement et produira des résultats incorrects. Assurez-vous que vos polices sont chargées avant d'appeler prepare(). Utilisez l'API Font Loading (document.fonts.ready) pour vérifier.
Puis-je utiliser Pretext.js pour le rendu Canvas ou SVG ?
Oui. La bibliothèque calcule la mise en page du texte de manière agnostique vis-à-vis de la cible de rendu. Vous pouvez utiliser les hauteurs et les sauts de ligne calculés pour positionner le texte dans Canvas 2D, WebGL, SVG ou le DOM. Le site web de Pretext.js montre des exemples de toutes ces cibles de rendu.
Prend-il en charge les langues RTL (de droite à gauche) ?
Oui. Pretext.js gère l'arabe, l'hébreu et d'autres scripts RTL avec une prise en charge bidirectionnelle correcte du texte. Il gère également correctement le texte de direction mixte (par exemple, du texte arabe avec des mots anglais intégrés).
Quelle est la taille du bundle ?
15 Ko minifié sans dépendances. Aucun polyfill n'est requis. La bibliothèque utilise uniquement les API de navigateur standard (Canvas measureText() et Intl.Segmenter lorsqu'elles sont disponibles).
Quelle est sa précision par rapport à la mesure DOM ?
Pour la plupart des contenus textuels, Pretext.js correspond à la mise en page DOM à 1 ou 2 pixels près. La précision dépend de la police et des propriétés CSS que vous utilisez. Les propriétés comme letter-spacing et word-spacing ne sont pas prises en compte, donc si vous les utilisez, attendez-vous à des différences plus importantes. Pour le défilement virtuel, où quelques pixels d'erreur sont invisibles, la précision est plus que suffisante.
Pretext.js peut-il mesurer du texte stylisé (gras, italique, tailles mixtes) ?
Chaque appel prepare() prend une seule spécification de police. Pour le texte avec des styles mixtes (mots en gras dans du texte normal), vous devrez segmenter le texte vous-même et créer des poignées distinctes pour chaque série de styles. C'est une limitation connue qui pourrait être abordée dans les futures versions.
Conclusion
Pretext.js résout un problème que les navigateurs ont ignoré pendant trois décennies : la mesure rapide et précise du texte sans reflow DOM. Pour les développeurs qui construisent des défilements virtuels, des interfaces de chat, des grilles de données ou toute interface qui doit mesurer des milliers de blocs de texte, cette bibliothèque remplace toute une catégorie de solutions de contournement par deux appels de fonction.
La bibliothèque n'est pas une solution miracle. Elle ne prend pas en charge les propriétés de texte CSS au-delà de la spécification de police, présente des différences mineures de précision sous-pixel et ne fonctionne pas encore côté serveur. Mais pour son cas d'utilisation cible ; le pré-calcul des hauteurs de texte pour les listes virtualisées ; rien d'autre ne s'en approche.
Prêt à construire des interfaces utilisateur textuelles plus rapides ? Commencez par tester vos points de terminaison API avec Apidog pour vous assurer que votre couche de données est solide, puis intégrez Pretext.js dans votre pipeline de rendu.
