← وبلاگ

معماری وارونه

نحوهٔ تبدیل یک commit در React به یک فراخوانی console.log در DevTools.

Adam

در یک فریم‌ورک DOM، کامپوننت‌ها به گره‌های DOM تبدیل می‌شوند که مرورگر آن‌ها را در صفحه چیدمان و ترسیم می‌کند. در Text.js، کامپوننت‌ها به سلول تبدیل می‌شوند و هر فریم تغییریافته از سلول‌ها به یک فراخوانی console.log در DevTools تبدیل می‌شود. ما این را معماری وارونه می‌نامیم.

خط پردازش

React ─▶ reconciler ─▶ Yoga ─▶ CellBuffer ─▶ row-hash gate ─▶ RLE ─▶ console.log

هر مرحله به این دلیل وجود دارد که کنسول نمایشگری نامعمول است. کنسول تک‌عرض و سطرمحور است، با %c سبک‌دهی می‌شود و DevTools تصمیم می‌گیرد چه زمانی دوباره ترسیم شود.

تطبیق‌دهنده

پکیج @textjs/react یک تطبیق‌دهنده برای ⁦React 19⁩ است ([email protected]) که دو عنصر میزبان، tx-box و tx-text، به‌همراه نمونه‌های متنی برای فرزندان رشته‌ای دارد. هر چیزی که وارد می‌کنید، از <Box> تا <Markdown>، بر پایه آن‌ها ساخته شده است. کامیت‌ها با requestAnimationFrame زمان‌بندی می‌شوند و فشردن کلید یا جای‌گذاری، به‌صورت همگام در همان تسک رندر می‌شود.

چیدمان

⁦Yoga⁩ که به WebAssembly کامپایل شده است، درخت را با pointScaleFactor = 1 چیدمان می‌کند تا هر کادر روی یک سلول کامل قرار بگیرد. متن با Intl.Segmenter و جدول‌های عرض نویسه‌های شرق آسیا اندازه‌گیری می‌شود، بنابراین 你好 چهار سلول و 🚀 دو سلول اشغال می‌کند. اگر ویرایشی نتواند اندازه گره متنی را تغییر دهد، دوباره از Yoga درخواست نمی‌شود.

CellBuffer

فریم شبکه‌ای از آرایه‌های نوع‌دار است که فقط یک‌بار حافظه می‌گیرند: نویسه‌ها در یک Uint32Array، رنگ پیش‌زمینه و پس‌زمینه به‌صورت فشرده با قالب 0xRRGGBBAA و ویژگی‌ها به‌صورت ماسک بیتی Uint16Array. فرایند ترسیم در این آرایه‌ها می‌نویسد و سطرهایی را که به آن‌ها دست زده است ردیابی می‌کند.

دروازه

هر سطر یک هش 32 بیتی ⁦FNV-1a⁩ در حافظه نهان دارد. فقط هش سطرهایی که از فریم قبلی در آن‌ها نوشته شده است دوباره محاسبه می‌شود. اگر هیچ سطری تغییر نکرده باشد، درایور پیش از ساخت رشته برمی‌گردد و هیچ فراخوانی کنسولی انجام نمی‌دهد. نشانگر چرخان در یک زبانه پنهان هیچ هزینه‌ای ندارد، چون به‌روزرسانی نمی‌شود.

کدگذاری طول دنباله

ردیف‌های تغییریافته دوباره پیمایش و به دنباله‌هایی با سبک یکسان تقسیم می‌شوند و هر دنباله به یک span از نوع %c تبدیل می‌شود. دنباله‌ها در شکست‌های خط هم ادامه پیدا می‌کنند. رشته‌های CSS با شناسه‌های سبکِ عدد صحیح ذخیره و بازاستفاده می‌شوند، بنابراین مسیر پرتکرار هرگز یک رشته را دو بار نمی‌سازد. یک داشبورد ⁦100×32⁩ شامل 3,200 سلول و ⁦149 span⁩ است.

رشته قالب در برابر دو رفتار DevTools هم محافظت می‌کند. نویسه % به‌صورت %% نوشته می‌شود. یک ⁦word joiner⁩ پس از ://، داخل www. و در path:line قرار می‌گیرد تا DevTools متن شما را به پیوند تبدیل نکند و آن را با رنگ‌های خودش دوباره ترسیم نکند.

یک فراخوانی

هر فریم تغییریافته یک فراخوانی واحد است:

console.log("%c\u200b%c\n%c▼ OpenCode Lite%c …", "margin-top:100vh;", "", "color:#fafafa;…", …);

فریم‌ها به انتها افزوده می‌شوند، با محدودکننده 50 ms در DevTools هم‌گام می‌شوند و در یک نوبت ترسیم جایگزین می‌شوند. Turbolog: حذف چشمک‌زدن آن بخش را پوشش می‌دهد.

ورودی

کنسول نمی‌تواند ورودی صفحه‌کلید را دریافت کند، پس صفحه آن را دریافت می‌کند. یک <textarea> پنهان، ورودی کلیدها، جای‌گذاری و ترکیب نویسه‌های IME را جمع‌آوری می‌کند و رویدادهای wheel کامپوننت‌های ScrollBox را پیمایش می‌کنند. در textjs.org، صفحه‌های Playground و OpenCode یک پد فوکوس بزرگ هستند. کنسول را باز کنید، سپس برای تایپ کردن روی صفحه کلیک کنید.

آنچه صفحه می‌بیند

صفحه نمی‌تواند اندازه کنسول را بخواند و فقط اندازه پنجره خودش را می‌خواند، بنابراین Text.js آن را استنباط می‌کند: یک پنل متصل، innerWidth یا innerHeight را کوچک می‌کند، درحالی‌که outer* ثابت می‌ماند و هر فرض درباره محل اتصال برای سطوح بزرگ‌نمایی Chrome حل می‌شود. نزدیک‌ترین تطابق انتخاب می‌شود. ثابت‌ها (56 px فاصله داخلی افقی، 104 px رابط پیرامونی کنسول و 14 px فاصله سطرها) در رابط واقعی DevTools اندازه‌گیری شده‌اند.