Rendering di React in console.log
Text.js disegna un’app React nella console DevTools con una sola chiamata console.log formattata per frame.
Text.js renderizza app React nella console di Chrome DevTools. A parte un campo di input nascosto, la pagina non ha un’interfaccia utente. Ogni frame dell’app è una singola chiamata a console.log.
La console come display
console.log accetta una stringa di formato. Ogni %c applica il CSS dell’argomento successivo al testo che lo segue, fino al prossimo %c:
console.log("%c▼ Text.js%c ready", "color:#ededed;font-weight:700", "color:#45a557");Questi stili possono impostare colori, sfondi, grassetto, corsivo e sottolineatura, e il font della console è a spaziatura fissa (Menlo su macOS). Con un font a spaziatura fissa e colori che possono cambiare per ogni sequenza di caratteri, la console funziona come una griglia di celle di caratteri con stili. Su questa griglia si possono disegnare bordi, pannelli, campi di input e grafici.
Un frame è una griglia di celle
Il renderer conserva ogni frame in un CellBuffer. I caratteri risiedono in un array tipizzato, i colori di primo piano e di sfondo in altri due, e gli attributi come grassetto e sottolineatura in un quarto. Gli array vengono allocati una sola volta per una data dimensione e riutilizzati per ogni frame.
Per scrivere un frame nella console, il driver percorre ogni riga e raggruppa in sequenze le celle adiacenti che condividono uno stile. Ogni sequenza diventa uno span %c e le righe vengono unite con \n. La nostra dashboard di test da 100×32, 3200 celle, viene codificata in circa 200 span.
Una chiamata per frame
Scrivere ogni riga come messaggio separato non funziona. Ogni messaggio ha un proprio bordo e un link al codice sorgente in DevTools, e DevTools comprime i messaggi consecutivi identici dietro un contatore di ripetizioni, quindi due righe identiche di una tabella si fonderebbero in una sola. Un frame scritto come singolo messaggio rimane integro.
Prima occorre applicare l’escape a due elementi del testo. Un % letterale viene scritto come %%, così un testo come 100% non può consumare un argomento di stile. DevTools trasforma anche gli URL in link e li ridisegna con i propri colori, quindi a :// e www. viene aggiunto un word joiner (U+2060), che non occupa larghezza:
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;}I frame inattivi non scrivono nulla nella console
Per ogni riga del buffer viene calcolato un hash. Se nessun hash è cambiato dall’ultimo frame, il driver termina prima di costruire una stringa e non tocca la console. Un’app in attesa di input non effettua chiamate.
Quando una riga è cambiata, il driver svuota la console e scrive il nuovo frame:
const { frame, dirtyRows } = this.encode(buffer, options);if (frame === null) return SKIPPED;if (this.clearEachFrame) target.clear();target.log(frame.format, ...frame.args);Solo le righe il cui hash è cambiato vengono ricodificate. Le altre riutilizzano gli span del frame precedente.
Input
La console non riceve le pressioni dei tasti, quindi le raccoglie la pagina. Un <textarea> nascosto riceve pressioni dei tasti, operazioni di incolla e composizione IME e le trasforma in eventi di tastiera per l’app. Un clic sulla pagina gli assegna il focus.
Prossimi passi
I prototipi @textjs/core 0.0.x su npm contengono tutto in un unico pacchetto. Text.js 0.1 sposta il renderer descritto qui in @textjs/core, il reconciler di React in @textjs/react e aggiunge un App Router.