DEV Community

Cover image for Pretext.js: Library 15KB yang Membuat Tata Letak Teks 500x Lebih Cepat
Walse
Walse

Posted on • Originally published at apidog.com

Pretext.js: Library 15KB yang Membuat Tata Letak Teks 500x Lebih Cepat

Singkatnya

Pretext.js adalah pustaka TypeScript tanpa dependensi yang mengukur dan memposisikan teks multi-baris melalui aritmatika murni alih-alih operasi DOM. Ini menghilangkan reflow sinkron paksa, menghasilkan pengukuran teks ~500x lebih cepat daripada getBoundingClientRect(), dan mendukung setiap sistem penulisan utama di planet ini. Jika Anda membangun scroller virtual, UI obrolan, atau data grid, pustaka ini adalah solusi yang sudah lama dibutuhkan.

Coba Apidog hari ini

Pendahuluan

Setiap kali JavaScript Anda memanggil getBoundingClientRect() atau membaca offsetHeight, browser melakukan reflow sinkron paksa—menghentikan eksekusi, membersihkan perubahan gaya, menghitung ulang tata letak, dan merender ulang. Ini adalah operasi browser yang paling mahal.

Bayangkan Anda melakukan ini untuk 1.000 gelembung obrolan di daftar virtual atau 10.000 baris data grid—hasilnya adalah performa buruk, lag, dan UX yang mengecewakan.

💡 Apidog: Tim yang membangun frontend berbasis API sangat paham tantangan ini; mengalirkan data respons ke UI dinamis tanpa gangguan adalah perjuangan konstan saat mesin tata letak browser tidak berpihak pada Anda.

Cheng Lou, pengembang di balik react-motion dan kontributor utama React/ReasonML di Meta, membangun Pretext.js untuk mengatasi masalah klasik ini. Dirilis Maret 2026 dan langsung viral. Artikel ini akan membahas apa itu Pretext.js, cara kerjanya, kapan menggunakannya, kekurangan, dan implementasi teknis agar Anda tahu apakah library ini cocok untuk stack Anda.

Apa itu Pretext.js?

Pretext.js adalah engine tata letak teks JavaScript/TypeScript murni. Ia mengukur dan memposisikan teks multi-baris sepenuhnya melalui aritmatika—tanpa getBoundingClientRect(), tanpa offsetHeight, tanpa reflow, dan tanpa thrash layout.

Pretext.js example

Alih-alih bertanya ke browser "seberapa tinggi teks ini?" (yang memicu render), Pretext.js menghitungnya secara matematis menggunakan metrik font dari Canvas API.

API utama:

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

// 1. Siapkan teks (sekali, bisa di-cache)
const handle = prepare('Halo, pretext.js', '16px "Inter"');

// 2. Hitung layout pada lebar berapapun (microsecond speed)
const { height, lineCount } = layout(handle, 400, 24);
Enter fullscreen mode Exit fullscreen mode

Cukup dua fungsi: prepare() menyimpan hasil pengukuran, layout() melakukan aritmatika tata letak. prepare() adalah satu-satunya operasi yang menyentuh browser (Canvas measureText()); setelah itu, semua perhitungan murni matematika.

Mengapa Penting untuk Aplikasi Heavy API

Jika aplikasi Anda menerima data streaming dari API—misal asisten AI, dashboard real-time, atau editor kolaboratif—Anda butuh tahu tinggi teks SEBELUM merender DOM. Tanpa ini, virtual scroller Anda akan macet dan UI terasa tidak mulus.

Pretext.js memberi tinggi teks dalam mikrodetik, bukan milidetik. Bedanya sangat signifikan.

Masalah yang Diselesaikan Pretext.js

Penjelasan Reflow Sinkron Paksa

Contoh berikut mengilustrasikan masalah klasik:

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

Setiap panggilan getBoundingClientRect():

  1. Menjeda eksekusi JS,
  2. Membersihkan gaya tertunda,
  3. Menghitung ulang layout seluruh dokumen/subtree,
  4. Mengembalikan hasil.

Loop 1.000 elemen = 1.000 reflow = performa buruk (~94ms, kehilangan banyak frame pada 60fps).

Masalah Virtual Scrolling

Library virtual scroller (misal react-window, tanstack-virtual) butuh tahu tinggi setiap item. Untuk tinggi tetap mudah, tapi untuk teks dinamis tingginya variabel—ini mimpi buruk.

Solusi klasik: render off-screen, ukur, lalu posisikan. Ini mengalahkan tujuan virtualisasi. Estimasi = lompatan posisi. Pretext.js: hitung tinggi tepat SEBELUM DOM dibuat.

Benchmark

Pendekatan 1.000 blok teks 500 blok teks
DOM (getBoundingClientRect) ~94ms (6 frame drop) ~47ms
Pretext.js (layout()) ~2ms ~0.09ms
Perbedaan kecepatan ~47x lebih cepat ~500x lebih cepat

Perbedaan makin terasa pada batch kecil karena overhead DOM tetap konstan, sedangkan aritmatika Pretext sangat ringan.

Cara Kerja Pretext.js di Balik Layar

Pretext.js beroperasi dalam tiga fase—pahami ini untuk optimasi implementasi Anda.

Fase 1: Segmentasi Teks

prepare() menormalisasi input: membersihkan whitespace, menerapkan aturan pemisah baris Unicode (UAX #14), dan membagi teks ke unit yang bisa di-break.

Dukungan multibahasa: CJK, RTL (Arab/Ibrani), Thailand (kamus segmentasi), Devanagari (ligatur), emoji (ZWJ), dan soft hyphens.

Fase 2: Pengukuran Canvas

Setiap segmen diukur via Canvas measureText() (tanpa reflow):

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

Segmen dan font di-cache—prepare() dengan input sama akan menggunakan cache.

Fase 3: Tata Letak Aritmatika

layout() menggunakan lebar segmen yang di-cache dan lebar wadah, lalu menghitung wrapping baris via algoritma greedy:

  1. Jumlahkan lebar segmen hingga melebihi wadah,
  2. Pindah baris baru,
  3. Ulangi hingga selesai,
  4. Hitung total tinggi (jumlah baris x tinggi baris).

Pure aritmatika, tanpa DOM maupun Canvas.

Pola Handle yang Reusable

prepare() mengembalikan handle reusable di semua lebar:

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

Cocok untuk desain responsif—ukur sekali, layout berkali-kali.

Kasus Penggunaan Praktis

1. Virtual Scrolling dengan Teks Tinggi Variabel

Integrasi Pretext.js dengan virtual scroller:

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

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

Tanpa render off-screen, tanpa estimasi, tanpa lompatan posisi.

2. Antarmuka Obrolan AI

Untuk streaming token demi token—ukur ulang tinggi dengan setiap token tanpa reflow 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. Data Grid dengan Kolom Teks

Otomatisasi hitung lebar kolom:

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);
    // Gunakan width dari handle untuk ukuran kolom (implementasi tergantung API Pretext.js)
    maxWidth = Math.max(maxWidth, /* lebar yang dihitung */);
  }
  return maxWidth + padding;
}
Enter fullscreen mode Exit fullscreen mode

4. Umpan Multibahasa

Support Unicode penuh langsung:

const posts = [
  { text: 'Pustaka ini mengubah segalanya', lang: 'en' },
  { text: 'Teks RTL dengan tata letak dua arah yang benar', lang: 'ar' },
  { text: 'Teks CJK mendapatkan pemisah tingkat karakter yang tepat', 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

Menguji Tata Letak Teks Anda dengan Apidog

Saat membangun UI padat teks berbasis API, validasi data sama pentingnya dengan layout. Pastikan respons API yang mengalir ke komponen teks Anda formatnya benar dan konsisten.

Apidog test

Apidog memudahkan membuat mock respons API streaming untuk menguji integrasi Pretext.js Anda. Siapkan skenario dengan panjang teks, bahasa, atau edge-case Unicode berbeda, dan pastikan scroller virtual Anda konsisten sebelum produksi.

Untuk tim AI chat, Apidog memungkinkan:

  • Mock streaming response (simulasi output LLM)
  • Uji payload multilingual (tangkap bug layout sebelum user)
  • Validasi skema response (pastikan field teks sesuai format)
  • Suite pengujian otomatis (uji edge-case rendering teks)

Data API yang buruk = layout buruk, secepat apapun engine Anda.

Keterbatasan & Kritik

Akurasi Rendering Edge-case

Kasus tertentu (Safari/Chrome demo) menunjukkan teks melampaui bounding-box. Potensi penyimpangan jika:

  • Font dengan kerning tidak biasa
  • Campuran ukuran font dalam satu blok
  • Perbedaan rendering sub-pixel Canvas vs DOM
  • Kekhasan shaping text per-browser

Untuk virtual scrolling, selisih kecil ini umumnya tidak masalah. Untuk layout pixel-perfect, perlu hati-hati.

Pengukuran Canvas Tidak Gratis

prepare() tetap memanggil Canvas API. Membuat ribuan handle unik per frame bisa berat. Solusi: cache & batch handle—Pretext.js menyerahkan ini ke implementasi Anda.

Tidak Mendukung Properti CSS

Pretext.js hanya mengukur via spesifikasi font. Tidak menghitung:

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

Jika Anda memakai properti ini, hasil Pretext.js bisa berbeda dari browser.

Tidak Merender, Hanya Mengukur

Pretext.js hanya memberi tinggi teks. Untuk render, Anda tetap butuh node DOM/Canvas/SVG. Library ini hanya untuk phase pengukuran.

Pretext.js vs Pendekatan Tradisional

Fitur Pretext.js Pengukuran DOM Perkiraan Tinggi
Kecepatan (1K item) ~2ms ~94ms ~0ms (tanpa ukur)
Akurasi Tinggi (Canvas) Sempurna (DOM) Rendah (heuristik)
Ketergantungan DOM Tidak (setelah prepare) Penuh Tidak
Reflow 0 1 per pengukuran 0
Dukungan multibahasa Unicode penuh Penuh (browser) Buruk (rasio hardcode)
Dukungan properti CSS Terbatas (font saja) Penuh Tidak
Overhead memori Segmen cache Node DOM Minimal
Tata letak responsif Satu prepare(), banyak layout() Ukur ulang per lebar Estimasi ulang

Pilih sesuai use-case Anda: butuh akurasi pixel-perfect & properti CSS penuh? DOM tetap standar. Perlu kecepatan & bisa toleransi deviasi kecil? Pretext.js jauh lebih efisien.

Memulai

Instalasi

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

Penggunaan Dasar

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

const handle = prepare(
  'Pretext.js menghitung tata letak teks tanpa menyentuh DOM.',
  '16px "Inter"'
);

const result = layout(handle, 600, 24);
console.log(result.height);    // misal: 48 (2 baris x 24px)
console.log(result.lineCount); // misal: 2
Enter fullscreen mode Exit fullscreen mode

Integrasi dengan 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

Dengan ini, Anda dapat membuat virtual chat dengan tinggi item akurat SEBELUM rendering DOM—tanpa estimasi, tanpa jump correction, tanpa reflow.

Arena Bermain Interaktif

Situs Pretext.js menyediakan playground di pretextjs.dev/playground untuk eksperimen layout real-time.

Kapan TIDAK Menggunakan Pretext.js

Pretext.js bukan solusi untuk semua kasus:

  • Halaman statis/konten tetap: CSS cukup, tidak butuh library.
  • Layout pixel-perfect/cetak: DOM lebih akurat.
  • Heavy CSS styling: Properti seperti letter-spacing tidak didukung.
  • Server-side rendering: Canvas API tidak tersedia di Node.js (butuh polyfill).
  • Daftar kecil/statis: Untuk <50 item, pengukuran DOM sangat cepat.

FAQ

Apakah Pretext.js siap produksi?

Dirilis Maret 2026, 14.000+ bintang GitHub dalam hitungan hari, digunakan di Midjourney production frontend. Namun tetap, selalu sematkan versi dan uji terhadap font/konten spesifik Anda.

Apakah Pretext.js bekerja di React, Vue, Svelte?

Ya. Pretext.js agnostik framework. Cukup panggil prepare() dan layout() di hooks/composables/stores manapun.

Bagaimana dengan font web?

prepare() mengukur teks dengan font yang sudah dimuat. Jika font web belum ready, hasil salah. Pastikan font sudah loaded (document.fonts.ready) sebelum memanggil prepare().

Untuk rendering Canvas/SVG?

Bisa. Layout yang dihasilkan agnostik target—bisa untuk DOM, Canvas, WebGL, atau SVG.

Mendukung RTL?

Ya. Support Arabic, Hebrew, dan RTL lain, serta teks campuran arah.

Ukuran bundle?

15KB minified, tanpa dependensi, hanya pakai API browser standar.

Akurasi dibanding DOM?

Umumnya deviasi 1-2px dari DOM. Semakin banyak properti CSS, deviasi bertambah. Untuk virtual scrolling, cukup akurat.

Teks bergaya (tebal/miring/ukuran campuran)?

Setiap prepare() hanya untuk satu spesifikasi font. Untuk campuran gaya, pecah sendiri teksnya dan buat handle per gaya.

Kesimpulan

Pretext.js menyelesaikan masalah klasik: pengukuran teks cepat, akurat, tanpa reflow DOM. Untuk virtual scroller, UI chat, data grid, atau interface yang perlu mengukur ribuan blok teks, library ini menggantikan seluruh kategori solusi dengan dua fungsi.

Tidak sempurna—tidak dukung semua properti CSS, deviasi sub-pixel, belum SSR. Namun untuk precompute tinggi teks di virtual list, tidak ada yang menandingi.

Siap membangun UI padat teks dengan performa tinggi? Mulai dengan uji API endpoint Anda menggunakan Apidog untuk memastikan data solid, lalu integrasikan Pretext.js ke rendering pipeline Anda.

Top comments (0)