Renderowanie React do console.log
Text.js rysuje aplikację React w konsoli DevTools jako jedno stylowane wywołanie console.log na klatkę.
Text.js renderuje aplikacje React w konsoli DevTools przeglądarki Chrome. Poza ukrytym polem wejściowym strona nie ma interfejsu użytkownika. Każda klatka aplikacji to jedno wywołanie console.log.
Konsola jako wyświetlacz
console.log przyjmuje ciąg formatujący. Każde %c stosuje CSS z następnego argumentu do tekstu, który po nim następuje, aż do kolejnego %c:
console.log("%c▼ Text.js%c ready", "color:#ededed;font-weight:700", "color:#45a557");Te style pozwalają ustawiać kolory, tła, pogrubienie, kursywę i podkreślenie, a czcionka konsoli ma stałą szerokość znaków (Menlo w systemie macOS). Dzięki czcionce o stałej szerokości i kolorom, które mogą się zmieniać dla każdego ciągu znaków, konsola działa jak siatka komórek znakowych ze stylami. Można na niej rysować obramowania, panele, pola wejściowe i wykresy.
Klatka jako siatka komórek
Renderer przechowuje każdą klatkę w CellBuffer. Znaki znajdują się w jednej tablicy typowanej, kolory tekstu i tła w dwóch kolejnych, a atrybuty takie jak pogrubienie i podkreślenie w czwartej. Tablice są alokowane raz dla danego rozmiaru i używane ponownie w każdej klatce.
Aby zapisać klatkę w konsoli, sterownik przechodzi przez każdy wiersz i grupuje sąsiednie komórki o wspólnym stylu w ciągi. Każdy ciąg staje się jednym spanem %c, a wiersze są łączone za pomocą \n. Nasz testowy pulpit 100×32, czyli 3200 komórek, jest kodowany do około 200 spanów.
Jedno wywołanie na klatkę
Zapisywanie każdego wiersza jako osobnego komunikatu nie działa. Każdy komunikat dostaje własne obramowanie i link do źródła w DevTools, a DevTools zwija identyczne kolejne komunikaty pod licznikiem powtórzeń, więc dwa identyczne wiersze tabeli połączyłyby się w jeden. Klatka zapisana jako jeden komunikat pozostaje w całości.
Najpierw trzeba zabezpieczyć dwa elementy tekstu przed specjalną interpretacją. Dosłowny znak % zapisuje się jako %%, więc tekst taki jak 100% nie może zużyć argumentu stylu. DevTools zamienia też adresy URL w linki i rysuje je ponownie we własnych kolorach, dlatego do :// i www. dodawany jest word joiner (U+2060), który nie ma szerokości:
export function escapeConsoleText(text: string, allowLink = false): string { let s = text; if (s.includes("%")) s = s.replaceAll("%", "%%"); if (!allowLink) { if (s.includes(LINK_SCHEME)) s = s.replaceAll(LINK_SCHEME, LINK_SCHEME_SAFE); if (s.includes(WWW) || s.includes("WWW.")) s = s.replace(/www\./gi, (m) => `${m.slice(0, 3)}\u2060.`); } return s;}Bezczynne klatki bez wpisów w konsoli
Dla każdego wiersza bufora obliczany jest hash. Jeśli żaden hash nie zmienił się od ostatniej klatki, sterownik kończy działanie przed zbudowaniem ciągu znaków i nie dotyka konsoli. Aplikacja czekająca na dane wejściowe nie wykonuje żadnych wywołań.
Gdy wiersz się zmieni, sterownik czyści konsolę i zapisuje nową klatkę:
const { frame, dirtyRows } = this.encode(buffer, options);if (frame === null) return SKIPPED;if (this.clearEachFrame) target.clear();target.log(frame.format, ...frame.args);Ponownie kodowane są tylko wiersze, których hash się zmienił. Pozostałe używają spanów z poprzedniej klatki.
Dane wejściowe
Konsola nie odbiera naciśnięć klawiszy, więc zbiera je strona. Ukryty element <textarea> odbiera naciśnięcia klawiszy, wklejanie i kompozycję IME, po czym zamienia je w zdarzenia klawiatury dla aplikacji. Kliknięcie strony ustawia na nim fokus.
Dalsze kroki
Prototypy @textjs/core 0.0.x w npm zawierają wszystko w jednym pakiecie. Text.js 0.1 przenosi opisany tutaj renderer do @textjs/core, reconciler React do @textjs/react i dodaje App Router.