← ब्लॉग

कंसोल में सर्वर रेंडरिंग

Text.js 0.4 सर्वर पर पेज रेंडर करके उसके फ़्रेम सेल डेल्टा के रूप में कंसोल तक स्ट्रीम कर सकता है। स्टैटिक रूट के पहले फ़्रेम की प्रीरेंडरिंग भी HTML में होती है।

Adam

Text.js 0.4 ने पेज रेंडर करने के लिए एक दूसरी जगह जोड़ी। डिफ़ॉल्ट रूप से, पेज का React ट्री ब्राउज़र टैब में चलता है। जो पेज runtime = "server" एक्सपोर्ट करता है, वह सर्वर पर चलता है और ब्राउज़र को उसके फ़्रेम मिलते हैं।

app/status/page.tsxtsx
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 एक async function कंपोनेंट है। सर्वर पर इसका प्रॉमिस रिज़ॉल्व होने तक यह सबसे नज़दीकी <Suspense> या loading.tsx पर सस्पेंड होता है, और यह सीधे फ़ाइलें, डेटाबेस और node:* मॉड्यूल पढ़ सकता है। एसिंक कंपोनेंट डेटा लोड करते हैं और हुक इस्तेमाल नहीं कर सकते। उनके आसपास के सामान्य कंपोनेंट में हुक, फ़ोकस, useInput, <Link> और useRouter() बने रहते हैं, जो दोनों रनटाइम में एक जैसे काम करते हैं।

ब्राउज़र क्या डाउनलोड करता है

सर्वर पर रेंडर किया गया पेज React, Yoga या आपके रूट नहीं भेजता। ब्राउज़र लगभग 21 kB gzip का फ़्रेम प्लेयर लोड करता है, जिसमें एक CellBuffer और वही CssConsoleDriver होता है जिसे क्लाइंट रेंडरर इस्तेमाल करता है। यह फ़्रेम को उसी 15fps की दर से चलाता है, इसलिए सर्वर पर रेंडर किया गया फ़्रेम वही console.log कॉल बनाता है जो क्लाइंट बनाता।

प्रोटोकॉल

हर टैब सर्वर पर अपना सेशन खोलता है: एक React रूट, मेमोरी में राउटर हिस्ट्री और एक सेल बफ़र। एक एंडपॉइंट दोनों दिशाओं का डेटा ले जाता है:

browser tab ──(GET /__textjs/live, server-sent events)──▶ frame deltasbrowser tab ──(POST /__textjs/live, JSON)──────────────▶ keys, paste, wheel, resize, focus, Back/Forward

सर्वर सिर्फ़ वे पंक्तियां भेजता है जिनका हैश उस कनेक्शन को मिले पिछले फ़्रेम के बाद बदला है। हर पंक्ति टेक्स्ट रन के रूप में भेजी जाती है, जो सिर्फ़ नई एंट्री जोड़ने वाली स्टाइल टेबल का इस्तेमाल करते हैं। चौड़े ग्रैफ़ीम, इमोजी क्लस्टर और अर्धपारदर्शी रंग बिना बदले पहुंचते हैं।

फ़्रेम के बीच कम से कम 66 ms का अंतर होता है, और हर फ़्रेम की तुलना सॉकेट के आख़िरी स्वीकार किए गए डेटा से करके डिफ़ निकाला जाता है। जब धीमे कनेक्शन पर डेटा निकलना बंद हो जाता है, तो बीच की स्थितियां छोड़ दी जाती हैं और अगला फ़्रेम सीधे सबसे नई स्थिति पर पहुंचता है।

अगर स्ट्रीम टूट जाती है, तो प्लेयर x-textjs-live हेडर में अपना सेशन आईडी देकर दोबारा कनेक्ट करता है और एक नए कीफ़्रेम से उसी सेशन को उसकी स्थिति बरकरार रखते हुए फिर शुरू करता है। सेशन आईडी 192-बिट रैंडम वैल्यू होते हैं जो मेमोरी में रखे जाते हैं, कभी URL में नहीं। बिना कनेक्शन वाला सेशन रेंडरिंग रोक देता है और 30 सेकंड बाद अनमाउंट हो जाता है।

दोनों एंडपॉइंट क्रॉस-साइट रिक्वेस्ट अस्वीकार करते हैं, और हर इनपुट इवेंट को एक तय स्कीमा के अनुसार वैलिडेट किया जाता है। एक सेशन हर POST में अधिकतम 64 इवेंट और हर सेकंड लगभग 240 इवेंट स्वीकार करता है।

एसिंक कंपोनेंट और डेटा

हर एसिंक कंपोनेंट का प्रॉमिस हर सेशन के लिए कैश होता है, जिसकी कुंजी कंपोनेंट और उसके प्रॉप्स होते हैं, इसलिए दोबारा कोशिश करने वाला रेंडर पहले से चल रही रिक्वेस्ट का फिर इस्तेमाल करता है। रूट बदलने पर और आपके router.refresh() कॉल करने पर कैश साफ़ हो जाता है:

const router = useRouter();useInput((e) => e.input === "r" && router.refresh());

सिर्फ़ सर्वर पर चलने वाले मॉड्यूल का नाम *.server.ts होता है। अगर ब्राउज़र को भेजा जाने वाला कोड ऐसा कोई मॉड्यूल या node:* बिल्ट-इन इंपोर्ट करता है, तो बिल्ड फ़ेल हो जाता है और इंपोर्ट करने वाले का नाम बताता है।

आंशिक प्रीरेंडरिंग

आंशिक प्रीरेंडरिंग बिना सर्वर के काम करती है। text build कमांड अब हर स्टैटिक रूट को एक बार रेंडर करती है और उसके पहले फ़्रेम को एक छोटे क्लासिक <script> के रूप में रूट के HTML में इनलाइन करती है, इसलिए किसी भी मॉड्यूल के लोड होने से पहले कंसोल पेंट हो जाता है।

सिंक्रोनस रूप से रेंडर होने वाली हर चीज़ शेल का हिस्सा है। जो <Suspense> बाउंड्री अभी इंतज़ार कर रही है, वह अपना फ़ॉलबैक दिखाती है, इसमें एसिंक कंपोनेंट भी शामिल हैं, और शेल रेंडर होते समय fetch() कभी रिज़ॉल्व नहीं होता।

JavaScript शुरू होने पर क्लाइंट रेंडरर या फ़्रेम प्लेयर असली फ़्रेम स्ट्रीम करता है। चूंकि 0.4 भी फ़्रेम साफ़ करने के बजाय उन्हें जोड़ता है, इसलिए हाइड्रेट करने वाला रेंडरर initialClear: false के साथ शुरू होता है और शेल के नीचे लॉग करता है, और उसका पर्दा उसी DevTools पेंट में शेल को नज़र से हटा देता है। कंसोल ठीक एक बार साफ़ होता है, और शेल उसे साफ़ करता है।

 ✓ Prerendering console shells (5/5) · 1 server-rendered, frame player 56.5 kB (21.3 kB gzip)

शेल कॉन्फ़िगर किए गए viewport साइज़ पर रेंडर होता है, जो डिफ़ॉल्ट रूप से 120 × 36 है, क्योंकि बिल्ड विज़िटर के कंसोल का साइज़ नहीं जान सकता। viewport.responsive चालू होने पर पहला लाइव फ़्रेम डॉक किए गए कंसोल के साइज़ में ढल जाता है। डायनैमिक रूट कोई शेल रेंडर नहीं करते, और text.config.ts फ़ाइल में ppr: false प्रीरेंडरिंग बंद कर देता है।

रनटाइम चुनना

क्लाइंट रनटाइम डिफ़ॉल्ट बना रहता है। कीस्ट्रोक उसी टास्क में पेंट होते हैं, और ऐप किसी भी स्टैटिक होस्ट पर डिप्लॉय होता है। सर्वर रनटाइम उन डैशबोर्ड और एडमिन व्यू के लिए उपयुक्त है जो निजी डेटा पढ़ते हैं, और उन रूट के लिए जिनकी डिपेंडेंसी आप ब्राउज़र बंडल में नहीं चाहते। एक ऐप दोनों को मिला सकता है, और उनके बीच नेविगेट करने पर टारगेट URL एक नए डॉक्यूमेंट के रूप में लोड होता है।

पूरा रेफ़रेंस सर्वर रेंडरिंग में है।