DEV Community

Cover image for Pretext.js: Biblioteca de 15KB para Layout de Texto 500x Mais Rápido
Lucas
Lucas

Posted on • Originally published at apidog.com

Pretext.js: Biblioteca de 15KB para Layout de Texto 500x Mais Rápido

Em resumo

Pretext.js é uma biblioteca TypeScript de dependência zero para medir e posicionar texto de várias linhas usando apenas aritmética, sem operações de DOM. Ela elimina reflows síncronos forçados, mede texto ~500x mais rápido que getBoundingClientRect() e suporta todos os principais sistemas de escrita. Para quem constrói scrollers virtuais, UIs de chat ou grids de dados, a biblioteca resolve um gargalo de layout ignorado pelos navegadores há 30 anos.

Experimente o Apidog hoje

Introdução

Sempre que seu JavaScript chama getBoundingClientRect() ou lê offsetHeight, o navegador pausa tudo, limpa estilos pendentes, recalcula o layout e faz uma renderização completa. Esse reflow síncrono forçado é a operação mais cara do navegador.

Agora multiplique isso por 1.000 balões de chat em uma lista virtual, ou 10.000 linhas em uma grid — o resultado é perda de quadros, travamentos e usuários frustrados.

💡 As equipes da Apidog que constroem frontends orientados por API conhecem bem essa dor; transmitir dados de resposta para UIs dinâmicas enquanto mantém tudo fluido é uma batalha constante quando seu motor de layout te combate a cada passo.

Cheng Lou, criador do react-motion e colaborador central do React e ReasonML na Meta, desenvolveu o Pretext.js para resolver isso. Lançada em março de 2026, a biblioteca rapidamente atingiu 14.000+ estrelas no GitHub e gerou um dos maiores tópicos do Hacker News do ano.

Este artigo detalha o que o Pretext.js faz, como funciona, quando usá-lo e suas limitações, para que você decida se ela faz sentido na sua stack.

O que é o Pretext.js?

Pretext.js é um motor de layout de texto JavaScript/TypeScript puro. Mede e posiciona texto de várias linhas só com cálculos matemáticos: sem getBoundingClientRect(), sem offsetHeight, sem reflow, sem travamentos.

Exemplo de uso do Pretext.js

A ideia central: em vez de perguntar ao navegador “qual a altura deste texto?” (forçando renderização), o Pretext.js calcula matematicamente usando métricas de fonte via Canvas.

API mínima

import { prepare, layout } from '@chenglou/pretext';

// 1. Prepare o texto (uma vez, cacheável)
const handle = prepare('Hello, pretext.js', '16px "Inter"');

// 2. Calcule o layout para qualquer largura (aritmética pura)
const { height, lineCount } = layout(handle, 400, 24);
Enter fullscreen mode Exit fullscreen mode

São só duas funções: prepare() mede o texto e armazena em cache; layout() faz a aritmética do layout. Apenas prepare() acessa o navegador (Canvas measureText()); depois disso, layout() é pura matemática.

Por que importa para apps orientados por API

Se você consome respostas de API streaming (assistentes de IA, dashboards em tempo real, editores colaborativos), precisa saber a altura do texto antes de renderizar. Sem isso, o scroll virtual trava, UIs pulam e o usuário percebe.

Com Pretext.js, você obtém a altura em microssegundos em vez de milissegundos — diferença que escala rapidamente.

O problema que o Pretext.js resolve

Reflow síncrono forçado

Um exemplo prático:

const elements = document.querySelectorAll('.text-block');
elements.forEach(el => {
  const height = el.getBoundingClientRect().height; // REFLOW!
  // use height...
});
Enter fullscreen mode Exit fullscreen mode

Cada chamada força o navegador a:

  1. Pausar JS
  2. Limpar estilos pendentes
  3. Recalcular layout do documento (ou sub-árvore)
  4. Retornar valor

Isso gera “layout thrashing”. Medir 1.000 elementos = 1.000 recálculos completos (~94ms, 6 frames perdidos a 60fps).

O problema do scroll virtual

Bibliotecas como react-window ou tanstack-virtual precisam da altura de cada item. Para altura fixa é simples; com textos de altura variável é um pesadelo.

A solução comum é renderizar off-screen, medir e posicionar, mas isso anula o propósito do virtual scroll. Outras estimam alturas e corrigem depois, causando jumps. Algumas obrigam a altura fixa.

Com Pretext.js, você calcula a altura exata antes de criar qualquer nó DOM.

Números reais

Abordagem 1.000 blocos 500 blocos
DOM (getBoundingClientRect) ~94ms ~47ms
Pretext.js (layout()) ~2ms ~0.09ms
Diferença de velocidade ~47x ~500x

Quanto menor o lote, mais dramática a vantagem do Pretext.js, pois o custo por chamada DOM é fixo, enquanto o aritmético escala linearmente.

Como o Pretext.js funciona por baixo do capô

A biblioteca opera em três fases. Entenda-as para otimizar seu uso.

Fase 1: Segmentação de texto

Ao chamar prepare(), o texto é normalizado, tratados espaços em branco e regras de quebra de linha Unicode (UAX #14) aplicadas, segmentando o texto em unidades quebráveis.

Suporte multilíngue cobre:

  • CJK (chinês, japonês, coreano)
  • Árabe/hebraico (RTL e bidirecional)
  • Tailandês (segmentação por dicionário)
  • Hindi/Devanágari (ligaduras complexas)
  • Emoji (sequências ZWJ, tons de pele)
  • Hífens suaves (­)

Fase 2: Medição do Canvas

Cada segmento é medido via Canvas measureText(). Não há reflow de layout.

const ctx = offscreenCanvas.getContext('2d');
ctx.font = '16px "Inter"';
const metrics = ctx.measureText('Hello');
const width = metrics.width;
Enter fullscreen mode Exit fullscreen mode

Medições são cacheadas por segmento/fonte. Chamadas repetidas reutilizam dados.

Fase 3: Layout aritmético

layout() recebe larguras cacheadas e a largura do container, então:

  1. Soma larguras até exceder o container
  2. Quebra linha
  3. Repete até terminar
  4. Multiplica linhas pela altura da linha

Nenhum DOM, nenhum Canvas — só aritmética.

Handle reutilizável

prepare() retorna um identificador. Uma chamada serve para todas as larguras:

const handle = prepare(longArticleText, '16px "Inter"');

const mobile = layout(handle, 375, 24);
const tablet = layout(handle, 768, 24);
const desktop = layout(handle, 1200, 24);
Enter fullscreen mode Exit fullscreen mode

Ideal para design responsivo: meça uma vez, use em qualquer largura.

Casos de uso práticos

1. Scroll virtual com texto de altura variável

Integração típica com scroller virtual:

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 padding
  });
}

// 10.000 itens em ~4ms
const heights = computeHeights(chatMessages, 600);
Enter fullscreen mode Exit fullscreen mode

Nada de render off-screen, nada de estimativa, nada de jumps.

2. UIs de chat com IA

Recalcule a altura a cada token sem tocar o 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);

  scroller.updateItemHeight(messageId, height + padding);
});
Enter fullscreen mode Exit fullscreen mode

3. Grids de dados com colunas de texto

Ajuste automático de largura de coluna:

function computeColumnWidth(values: string[], font: string, padding: number) {
  let maxWidth = 0;
  for (const value of values) {
    const handle = prepare(value, font);
    const { height } = layout(handle, Infinity, 20);
    // Use a largura interna do handle para sizing
    maxWidth = Math.max(maxWidth, /* computed width */);
  }
  return maxWidth + padding;
}
Enter fullscreen mode Exit fullscreen mode

4. Feeds multilíngues

Medição correta para qualquer script Unicode:

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' },
];

posts.forEach(post => {
  const handle = prepare(post.text, '16px system-ui');
  const { height } = layout(handle, 400, 24);
});
Enter fullscreen mode Exit fullscreen mode

Testando seu layout de texto com Apidog

Construir UIs orientadas a API exige garantir que as respostas alimentando seus textos estejam corretas em dados, formato e velocidade.

Testando layout com Apidog

Apidog facilita isso: simule respostas streaming, teste integração Pretext.js com textos variados, idiomas e casos Unicode, e valide comportamento do scroller virtual antes de ir para produção.

Para equipes de chat com IA, o ambiente do Apidog permite:

  • Simular streaming para simular saída LLM
  • Testar payloads multilíngues e achar bugs antes dos usuários
  • Validar schemas de resposta para garantir formato correto
  • Executar testes automatizados cobrindo bordas de renderização

Uma biblioteca de layout é tão boa quanto os dados que recebe. Respostas ruins geram layouts ruins, mesmo com o motor mais rápido.

Limitações e críticas conhecidas

Casos de borda de precisão

Há relatos de texto ultrapassando caixas box-model em Safari/Chrome. Diferenças podem ocorrer com:

  • Fontes com kerning incomum
  • Tamanhos de fonte mistos em um bloco
  • Diferenças subpixel Canvas vs DOM
  • Peculiaridades específicas de navegador

Para scroll virtual, não afetam; para tipografia pixel-perfect, podem importar.

Medição Canvas não é grátis

prepare() usa o motor de texto do Canvas. Gerar milhares de handles únicos por frame pode ser gargalo. Use cache e agrupamento manualmente.

Sem suporte a propriedades CSS

Só a especificação de fonte é considerada. Não há suporte para:

  • letter-spacing
  • word-spacing
  • text-indent
  • text-transform
  • font-feature-settings
  • font-variant

Se seu layout depende dessas propriedades, pode haver diferenças.

Não substitui renderização do DOM

Pretext.js só informa altura; não renderiza texto. Você ainda precisa de DOM/Canvas/SVG para exibir.

Pretext.js vs. abordagens tradicionais

Funcionalidade Pretext.js Medição DOM Alturas estimadas
Velocidade (1K itens) ~2ms ~94ms ~0ms
Precisão Alta (Canvas) Perfeita (DOM) Baixa (heurística)
Dependência DOM Nenhuma após prepare() Total Nenhuma
Gatilhos de reflow Zero Um por medição Zero
Multilíngue Unicode total Total (nativo) Ruim
Suporte CSS Só fonte Total Nenhum
Custo memória Cache de segmentos Nós DOM Mínimo
Layout responsivo 1 prepare(), vários layout() Remedir por largura Reestimar

Se precisa de precisão pixel-perfect e CSS completo, use DOM. Para velocidade em milhares de itens e tolerância a pequenas diferenças, Pretext.js vence.

Começando

Instalação

npm install @chenglou/pretext
# ou
pnpm add @chenglou/pretext
# ou
bun add @chenglou/pretext
Enter fullscreen mode Exit fullscreen mode

Uso básico

import { prepare, layout } from '@chenglou/pretext';

// Medir um parágrafo
const handle = prepare(
  'Pretext.js calcula layout sem tocar no DOM.',
  '16px "Inter"'
);

// Altura para largura específica
const result = layout(handle, 600, 24);
console.log(result.height);    // ex: 48 (2 linhas x 24px)
console.log(result.lineCount); // ex: 2
Enter fullscreen mode Exit fullscreen mode

Integração com 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; // padding
    });
  }, [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>
  );
}
Enter fullscreen mode Exit fullscreen mode

Assim, você tem um chat virtual com alturas precisas antes de renderizar qualquer mensagem no DOM.

Playground interativo

O site do Pretext.js tem um playground em pretextjs.dev/playground para colar texto, escolher fonte, ajustar largura e ver o layout em tempo real.

Quando NÃO usar Pretext.js

Evite nos casos:

  • Páginas estáticas com conteúdo conhecido: CSS resolve.
  • Layouts de impressão pixel-perfect: Use DOM.
  • Estilização CSS pesada: Propriedades ignoradas.
  • Server-side rendering: Canvas não disponível sem polyfill.
  • Listas pequenas e estáticas: DOM é rápido o suficiente.

Perguntas Frequentes

O Pretext.js está pronto para produção?

Sim, lançado em 2026, 14.000+ estrelas, mantido por quem lidera frontend do Midjourney. Teste com seu conteúdo e fixe a versão.

Funciona com React, Vue e Svelte?

Sim, é framework-agnostic. Use prepare() e layout() onde quiser.

Como lida com fontes web?

prepare() mede usando a fonte carregada no momento. Se a fonte web não estiver pronta, usa fallback. Aguarde document.fonts.ready.

Dá para usar em Canvas ou SVG?

Sim, o layout serve para Canvas 2D, WebGL, SVG, DOM, etc.

Suporta idiomas RTL?

Sim, árabe, hebraico e textos bidirecionais, incluindo mistos.

Qual o tamanho?

15KB minificado, zero dependências. Usa apenas APIs nativas (Canvas e Intl.Segmenter).

Quão preciso é comparado ao DOM?

Normalmente 1-2px de diferença. Se usa propriedades CSS ignoradas, pode variar mais. Para scroll virtual, é mais que suficiente.

Mede texto estilizado (negrito, itálico, tamanhos mistos)?

Cada prepare() recebe uma fonte única. Para múltiplos estilos, segmente você mesmo e crie handles separados.

Conclusão

Pretext.js resolve um gargalo ignorado por décadas: medição de texto rápida e precisa sem reflow do DOM. Para scrollers virtuais, UIs de chat, grids de dados ou qualquer interface que mede milhares de blocos de texto, ela substitui hacks por duas funções simples.

Não é bala de prata: não suporta todas propriedades CSS, pode ter pequenas diferenças subpixel, e ainda não roda server-side. Mas para pré-computar alturas de listas virtualizadas, não há igual.

Pronto para construir UIs mais rápidas e ricas em texto? Comece testando seus endpoints de API com Apidog para garantir uma camada de dados sólida, e então adicione Pretext.js ao seu pipeline de renderização.

Top comments (0)