← Blog

Odwrócona architektura

Jak commit React staje się jednym wywołaniem console.log w DevTools.

Adam

We frameworku DOM komponenty stają się węzłami DOM, które przeglądarka rozmieszcza i rysuje na stronie. W Text.js komponenty stają się komórkami, a każda zmieniona klatka komórek staje się jednym wywołaniem console.log w DevTools. Nazywamy to odwróconą architekturą.

Potok

React ─▶ reconciler ─▶ Yoga ─▶ CellBuffer ─▶ row-hash gate ─▶ RLE ─▶ console.log

Każdy etap istnieje dlatego, że konsola jest nietypowym wyświetlaczem. Używa pisma o stałej szerokości, jest zorganizowana w wiersze i stylowana za pomocą %c, a DevTools decyduje, kiedy ją odmalować.

Reconciler

@textjs/react to reconciler dla React 19 ([email protected]) z dwoma elementami hosta, tx-box i tx-text, oraz instancjami tekstowymi dla dzieci będących ciągami znaków. Wszystko, co importujesz, od <Box> po <Markdown>, jest na nich zbudowane. Commity są planowane przez requestAnimationFrame, a naciśnięcie klawisza lub wklejenie powoduje synchroniczne renderowanie w tym samym zadaniu.

Layout

Yoga, skompilowana do WebAssembly, rozmieszcza drzewo z pointScaleFactor = 1, więc każdy box trafia na pełną komórkę. Tekst jest mierzony za pomocą Intl.Segmenter i tabel szerokości znaków wschodnioazjatyckich, więc 你好 zajmuje cztery komórki, a 🚀 dwie. Jeśli edycja nie może zmienić rozmiaru węzła tekstowego, Yoga nie jest ponownie wywoływana.

CellBuffer

Klatka to siatka tablic typowanych alokowanych raz: znaki w Uint32Array, kolor tekstu i tła jako spakowane 0xRRGGBBAA, atrybuty jako maska bitowa Uint16Array. Rysowanie zapisuje dane w tych tablicach i śledzi, których wierszy dotknęło.

Bramka

Każdy wiersz ma zapisany w pamięci podręcznej 32-bitowy hash FNV-1a. Hash jest obliczany ponownie tylko dla wierszy zapisanych od ostatniej klatki. Jeśli żaden wiersz się nie zmienił, sterownik kończy działanie, zanim zbuduje ciąg znaków, i nie wykonuje żadnego wywołania konsoli. Spinner na ukrytej karcie nic nie kosztuje, ponieważ się nie obraca.

Kodowanie długości serii

Zmienione wiersze są ponownie skanowane i dzielone na serie o identycznym stylu, a każda seria staje się jednym spanem %c. Serie mogą przechodzić przez podziały wierszy. Ciągi CSS są internowane pod całkowitoliczbowymi identyfikatorami stylów, więc najczęściej wykonywana ścieżka nigdy nie buduje tego samego ciągu dwa razy. Panel 100×32 to 3200 komórek i 149 spanów.

Ciąg formatujący chroni też przed dwoma zachowaniami DevTools. Dosłowny % jest zapisywany jako %%. Znak word joiner trafia po ://, wewnątrz www. i w path:line, aby DevTools nie zamieniało twojego tekstu w link i nie odmalowywało go własnymi kolorami.

Jedno wywołanie

Każda zmieniona klatka to pojedyncze wywołanie:

console.log("%c\u200b%c\n%c▼ OpenCode Lite%c …", "margin-top:100vh;", "", "color:#fafafa;…", …);

Klatki są dopisywane w tempie dopasowanym do ogranicznika DevTools działającego co 50 ms i podmieniane w ramach jednego odmalowania. Ten etap opisuje artykuł Turbolog: eliminowanie migotania.

Obsługa wejścia

Konsola nie może odbierać naciśnięć klawiszy, więc robi to strona. Ukryty element <textarea> zbiera naciśnięcia klawiszy, wklejany tekst i kompozycję IME, a zdarzenia wheel przewijają komponenty ScrollBox. W serwisie textjs.org strony Playground i OpenCode stanowią jeden duży panel fokusu. Otwórz konsolę, a następnie kliknij stronę, aby pisać.

Co widzi strona

Strona nie może odczytać rozmiaru konsoli, tylko własnego okna, więc Text.js go wywnioskuje: zadokowany panel zmniejsza innerWidth lub innerHeight, podczas gdy outer* pozostaje bez zmian, a każda hipoteza dokowania jest sprawdzana dla poziomów powiększenia przeglądarki Chrome. Wygrywa najlepsze dopasowanie. Stałe (56 px poziomego odstępu wewnętrznego, 104 px interfejsu konsoli, 14 px odstępu między wierszami) zmierzono w rzeczywistym frontendzie DevTools.