Renderizar React no console.log
Text.js desenha um app React no console do DevTools com uma chamada console.log estilizada por quadro.
Text.js renderiza apps React no console do Chrome DevTools. Além de um campo de entrada oculto, a página não tem interface. Cada quadro do app é uma única chamada a console.log.
O console como tela
console.log aceita uma string de formatação. Cada %c aplica o CSS do próximo argumento ao texto que vem depois dele, até o próximo %c:
console.log("%c▼ Text.js%c ready", "color:#ededed;font-weight:700", "color:#45a557");Esses estilos podem definir cores, fundos, negrito, itálico e sublinhado, e a fonte do console é monoespaçada (Menlo no macOS). Com uma fonte monoespaçada e cores que podem mudar a cada sequência de caracteres, o console funciona como uma grade de células de caracteres estilizadas. Bordas, painéis, campos de entrada e gráficos podem ser desenhados nela.
Um quadro é uma grade de células
O renderizador mantém cada quadro em um CellBuffer. Os caracteres ficam em um array tipado, as cores de primeiro plano e de fundo em outros dois, e atributos como negrito e sublinhado em um quarto. Os arrays são alocados uma vez para um determinado tamanho e reutilizados em todos os quadros.
Para registrar um quadro, o driver percorre cada linha e agrupa células vizinhas que compartilham um estilo em sequências. Cada sequência se torna um span %c, e as linhas são unidas com \n. Nosso painel de teste de 100×32, com 3.200 células, é codificado em cerca de 200 spans.
Uma chamada por quadro
Registrar cada linha como uma mensagem separada não funciona. Cada mensagem recebe sua própria borda e seu próprio link para o código-fonte no DevTools, e o DevTools agrupa mensagens consecutivas idênticas sob um contador de repetições, então duas linhas idênticas de uma tabela seriam mescladas em uma só. Um quadro registrado como uma única mensagem permanece inteiro.
Dois elementos do texto precisam ser escapados primeiro. Um % literal é escrito como %%, para que um texto como 100% não consuma um argumento de estilo. O DevTools também transforma URLs em links e os repinta com suas próprias cores, então :// e www. recebem um word joiner (U+2060), que não tem largura:
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;}Quadros ociosos não registram nada
Um hash é calculado para cada linha do buffer. Se nenhum hash mudou desde o último quadro, o driver retorna antes de montar uma string e não toca no console. Um app que aguarda entrada não faz chamadas.
Quando uma linha muda, o driver limpa o console e registra o novo quadro:
const { frame, dirtyRows } = this.encode(buffer, options);if (frame === null) return SKIPPED;if (this.clearEachFrame) target.clear();target.log(frame.format, ...frame.args);Apenas as linhas cujo hash mudou são recodificadas. As outras reutilizam os spans do quadro anterior.
Entrada
O console não recebe eventos de teclado, então a página os coleta. Um <textarea> oculto recebe pressionamentos de teclas, colagens e composição via IME e os transforma em eventos de teclado para o app. Clicar na página coloca o foco nele.
Próximos passos
Os protótipos 0.0.x de @textjs/core no npm reúnem tudo em um único pacote. O Text.js 0.1 move o renderizador descrito aqui para @textjs/core, o reconciliador do React para @textjs/react e adiciona um App Router.