Renderowanie po stronie serwera w konsoli
Text.js 0.4 potrafi wyrenderować stronę na serwerze i strumieniować jej klatki do konsoli jako różnice komórek. Trasy statyczne wykonują też prerenderowanie pierwszej klatki do HTML.
Text.js 0.4 dodał drugie miejsce renderowania strony. Domyślnie drzewo React strony działa w karcie przeglądarki. Strona eksportująca runtime = "server" działa na serwerze, a przeglądarka otrzymuje jej klatki.
import { Suspense, type ReactNode } from "react";import os from "node:os";import { Box, Text } from "textjs";export const runtime = "server";async function Load(): Promise<ReactNode> { const [one, five] = os.loadavg(); return <Text>{`load ${one.toFixed(2)} ${five.toFixed(2)}`}</Text>;}export default function StatusPage(): ReactNode { return ( <Box flexDirection="column"> <Text bold>Status</Text> <Suspense fallback={<Text color="gray">Reading host…</Text>}> <Load /> </Suspense> </Box> );}Load to komponent typu async function. Na serwerze wstrzymuje renderowanie do najbliższego <Suspense> lub loading.tsx, dopóki jego obietnica się nie rozwiąże, i może bezpośrednio odczytywać pliki, bazy danych oraz moduły node:*. Komponenty asynchroniczne wczytują dane i nie mogą używać hooków. Otaczające je zwykłe komponenty zachowują hooki, fokus, useInput, <Link> i useRouter(), które działają tak samo w obu środowiskach wykonawczych.
Zawartość pobierana przez przeglądarkę
Strona renderowana po stronie serwera nie przesyła React, Yoga ani twoich tras. Przeglądarka wczytuje odtwarzacz klatek o rozmiarze około 21 kB po kompresji gzip, który zawiera CellBuffer i ten sam CssConsoleDriver, którego używa renderer klienta. Utrzymuje tę samą częstotliwość 15 klatek na sekundę, więc klatka renderowana po stronie serwera generuje takie samo wywołanie console.log jak po stronie klienta.
Protokół
Każda karta otwiera własną sesję na serwerze: korzeń React, historię routera w pamięci i bufor komórek. Jeden endpoint obsługuje oba kierunki:
browser tab ──(GET /__textjs/live, server-sent events)──▶ frame deltasbrowser tab ──(POST /__textjs/live, JSON)──────────────▶ keys, paste, wheel, resize, focus, Back/ForwardSerwer wysyła tylko wiersze, których hash zmienił się od ostatniej klatki otrzymanej przez dane połączenie. Każdy wiersz jest wysyłany jako ciągi tekstu odwołujące się do tabeli stylów, do której można tylko dopisywać. Szerokie grafemy, klastry emoji i półprzezroczyste kolory docierają bez zmian.
Klatki dzieli co najmniej 66 ms, a każda jest porównywana z tym, co gniazdo ostatnio zaakceptowało. Gdy wolne połączenie przestaje odbierać dane, stany pośrednie są pomijane, a następna klatka przechodzi do najnowszego stanu.
Jeśli strumień zostanie przerwany, odtwarzacz łączy się ponownie z identyfikatorem sesji w nagłówku x-textjs-live i wznawia tę samą sesję od nowej klatki kluczowej, zachowując jej stan. Identyfikatory sesji to losowe wartości 192-bitowe przechowywane w pamięci, nigdy w adresie URL. Sesja bez połączenia przestaje renderować i jest odmontowywana po 30 sekundach.
Oba endpointy odrzucają żądania z innych witryn, a każde zdarzenie wejściowe jest sprawdzane według stałego schematu. Sesja przyjmuje najwyżej 64 zdarzenia na żądanie POST i około 240 na sekundę.
Komponenty asynchroniczne i dane
Obietnica każdego komponentu asynchronicznego jest buforowana dla danej sesji pod kluczem złożonym z komponentu i jego propsów, więc ponowiona próba renderowania korzysta z już trwającego żądania. Pamięć podręczna jest czyszczona przy zmianie trasy i po wywołaniu router.refresh():
const router = useRouter();useInput((e) => e.input === "r" && router.refresh());Moduły przeznaczone tylko dla serwera mają nazwy *.server.ts. Budowanie kończy się błędem, jeśli kod wysyłany do przeglądarki importuje taki moduł lub wbudowany moduł node:*, i wskazuje moduł importujący.
Częściowe prerenderowanie
Częściowe prerenderowanie działa bez serwera. text build renderuje teraz każdą statyczną trasę raz i umieszcza jej pierwszą klatkę bezpośrednio w kodzie HTML trasy jako mały klasyczny <script>, dzięki czemu konsola wyświetla zawartość przed wczytaniem jakiegokolwiek modułu.
Powłoka to wszystko, co renderuje się synchronicznie. Granica <Suspense>, która nadal czeka, wyświetla zawartość zastępczą, także dla komponentów asynchronicznych, a fetch() nigdy nie rozwiązuje się podczas renderowania powłoki.
Gdy JavaScript się uruchamia, renderer klienta lub odtwarzacz klatek przesyła właściwą klatkę. Ponieważ wersja 0.4 także dopisuje klatki zamiast czyścić konsolę, renderer przeprowadzający hydratację uruchamia się z initialClear: false i wypisuje zawartość pod powłoką, a jego kurtyna przesuwa powłokę poza widok w tym samym cyklu rysowania DevTools. Konsola jest czyszczona dokładnie raz, przez powłokę.
✓ Prerendering console shells (5/5) · 1 server-rendered, frame player 56.5 kB (21.3 kB gzip)Powłoka jest renderowana w skonfigurowanym rozmiarze viewport, domyślnie 120 × 36, ponieważ proces budowania nie zna rozmiaru konsoli odwiedzającego. Przy włączonym viewport.responsive pierwsza klatka na żywo dopasowuje rozmiar do zadokowanej konsoli. Trasy dynamiczne nie renderują powłoki, a ppr: false w pliku text.config.ts wyłącza prerenderowanie.
Wybór środowiska wykonawczego
Środowisko wykonawcze klienta pozostaje domyślne. Naciśnięcia klawiszy są odwzorowywane w tym samym zadaniu, a aplikację można wdrożyć na dowolnym hostingu statycznym. Środowisko wykonawcze serwera pasuje do dashboardów i widoków administracyjnych odczytujących prywatne dane oraz tras, których zależności nie chcesz umieszczać w pakiecie przeglądarkowym. Jedna aplikacja może łączyć oba środowiska, a nawigacja między nimi wczytuje docelowy adres URL jako nowy dokument.
Pełny opis znajdziesz w sekcji Renderowanie po stronie serwera.