← בלוג

הארכיטקטורה ההפוכה

איך 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>, בנוי עליהם. פעולות commit מתוזמנות באמצעות requestAnimationFrame, ולחיצה על מקש או הדבקה גורמת לרינדור סינכרוני באותה משימה.

layout

⁦Yoga⁩, שמקומפל ל-WebAssembly, מסדר את העץ עם pointScaleFactor = 1, כך שכל תיבה ממוקמת על תא שלם. הטקסט נמדד באמצעות Intl.Segmenter וטבלאות רוחב של תווים ממזרח אסיה, כך ש-你好 תופס ארבעה תאים ו-🚀 תופס שניים. אם עריכה אינה יכולה לשנות את גודלו של צומת טקסט, אין פנייה נוספת ל-Yoga.

CellBuffer

הפריים הוא רשת של מערכים מטיפוס מוגדר שמוקצים פעם אחת: תווים ב-Uint32Array, צבעי חזית ורקע כערכי 0xRRGGBBAA ארוזים, ותכונות כמסכת סיביות מסוג Uint16Array. תהליך הצביעה כותב למערכים האלה ועוקב אחר השורות שבהן נגע.

השער

לכל שורה יש ערך גיבוב ⁦FNV-1a⁩ של 32 סיביות ששמור במטמון. רק שורות שנכתבו מאז הפריים האחרון עוברות גיבוב מחדש. אם אף שורה לא השתנתה, מנהל ההתקן חוזר לפני בניית מחרוזת ואינו מבצע קריאה למסוף. מחוון טעינה בכרטיסייה מוסתרת אינו עולה דבר כי הוא אינו מתקדם.

קידוד אורך רצף

שורות שהשתנו נסרקות מחדש לרצפים בעלי עיצוב זהה, וכל רצף הופך ל-span אחד של %c. הרצפים נמשכים מעבר למעברי שורה. מחרוזות CSS נשמרות במאגר משותף מאחורי מזהי עיצוב שלמים, כך שמסלול הביצוע התכוף לעולם אינו בונה את אותה מחרוזת פעמיים. לוח בקרה בגודל ⁦100×32⁩ הוא 3,200 תאים ו-⁦149 spans⁩.

מחרוזת הפורמט גם מגינה מפני שתי התנהגויות של DevTools. תו % מילולי נכתב בתור %%. תו ⁦word joiner⁩ נוסף אחרי ://, בתוך www. וב-path:line, כדי ש-DevTools לא יהפוך את הטקסט לקישור ויצבע אותו מחדש בצבעים משלו.

קריאה אחת

כל פריים שהשתנה הוא קריאה יחידה:

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

פריימים נוספים לסוף, בקצב המותאם למנגנון הגבלת הקצב של DevTools של 50 ms, ומוחלפים במהלך פעולת צביעה אחת. Turbolog: ביטול ההבהוב מכסה את החלק הזה.

קלט

המסוף אינו יכול לקבל הקשות, ולכן הדף מקבל אותן. רכיב <textarea> מוסתר אוסף הקשות, הדבקות והרכבת טקסט באמצעות IME, ואירועי wheel גוללים רכיבי ScrollBox. באתר textjs.org, דפי Playground ו-OpenCode הם משטח מיקוד גדול אחד. יש לפתוח את המסוף, ואז ללחוץ על הדף כדי להקליד.

מה הדף רואה

הדף אינו יכול לקרוא את גודל המסוף, רק את גודל החלון שלו, ולכן Text.js מסיק אותו: חלונית מעוגנת מקטינה את innerWidth או את innerHeight בעוד outer* נשאר קבוע, וכל השערת עיגון נבדקת מול רמות הזום של Chrome. ההתאמה הטובה ביותר מנצחת. הקבועים (ריווח פנימי אופקי של 56 px, מסגרת ממשק מסוף של 104 px, מרווח שורות של 14 px) נמדדו בממשק DevTools האמיתי.