← المدونة

عرض React في console.log

يرسم Text.js تطبيق React في وحدة تحكم DevTools باستدعاء console.log منسّق واحد لكل إطار.

Adam

يعرض Text.js تطبيقات React في وحدة التحكم في ⁦Chrome DevTools⁩. لا تحتوي الصفحة على واجهة مستخدم باستثناء حقل إدخال مخفي. كل إطار من التطبيق عبارة عن استدعاء واحد للدالة console.log.

وحدة التحكم كشاشة عرض

تقبل console.log سلسلة تنسيق. يطبّق كل %c تنسيقات CSS الواردة في الوسيط التالي على النص الذي يليه، حتى %c التالي:

console.log("%c▼ Text.js%c ready", "color:#ededed;font-weight:700", "color:#45a557");

يمكن لهذه التنسيقات ضبط الألوان والخلفيات والخط العريض والمائل والتسطير، وخط وحدة التحكم أحادي المسافة (Menlo على macOS). بفضل الخط أحادي المسافة والألوان التي يمكن تغييرها لكل تتابع من المحارف، تعمل وحدة التحكم كشبكة من خلايا المحارف المنسّقة. يمكن رسم الحدود واللوحات وحقول الإدخال والمخططات عليها.

الإطار شبكة من الخلايا

يحتفظ العارض بكل إطار في CellBuffer. تُخزّن المحارف في مصفوفة ذات نوع محدد، وألوان المقدمة والخلفية في مصفوفتين أخريين، والسمات مثل الخط العريض والتسطير في مصفوفة رابعة. تُخصّص المصفوفات مرة واحدة لحجم معيّن ويُعاد استخدامها لكل إطار.

لتسجيل إطار، يمر برنامج التشغيل على كل صف ويجمع الخلايا المتجاورة ذات التنسيق نفسه في تتابعات. يصبح كل تتابع span واحدًا من نوع %c، وتُوصل الصفوف باستخدام \n. تُرمّز لوحة معلومات الاختبار لدينا، بأبعاد ⁦100×32⁩ وعدد خلايا يبلغ 3,200، إلى نحو ⁦200 span⁩.

استدعاء واحد لكل إطار

لا يصلح تسجيل كل صف كرسالة مستقلة. تحصل كل رسالة على حدود ورابط مصدر خاصين بها في DevTools، وتطوي DevTools الرسائل المتتالية المتطابقة خلف عدّاد تكرار، لذا يندمج صفّان متطابقان من جدول في صف واحد. يبقى الإطار المسجّل كرسالة واحدة قطعة واحدة.

يحتاج عنصران في النص إلى تهريب أولًا. تُكتب علامة % الحرفية بالشكل %%، كي لا يستهلك نص مثل 100% وسيط تنسيق. تحوّل DevTools أيضًا عناوين URL إلى روابط وتعيد رسمها بألوانها الخاصة، لذا يُضاف إلى :// و www. محرف ⁦word joiner⁩ ‏(⁦U+2060⁩)، وهو محرف بلا عرض:

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;}

الإطارات الخاملة لا تسجّل شيئًا

تُحسب قيمة تجزئة لكل صف في المخزن المؤقت. إذا لم تتغيّر أي قيمة تجزئة منذ الإطار الأخير، يعود برنامج التشغيل قبل إنشاء سلسلة نصية ولا يتعامل مع وحدة التحكم مطلقًا. لا يجري التطبيق الذي ينتظر إدخالًا أي استدعاءات.

عندما يتغيّر صف، يمسح برنامج التشغيل وحدة التحكم ويسجّل الإطار الجديد:

const { frame, dirtyRows } = this.encode(buffer, options);if (frame === null) return SKIPPED;if (this.clearEachFrame) target.clear();target.log(frame.format, ...frame.args);

يُعاد ترميز الصفوف التي تغيّرت قيمة تجزئتها فقط. تعيد الصفوف الأخرى استخدام عناصر span من الإطار السابق.

الإدخال

لا تستقبل وحدة التحكم ضغطات المفاتيح، لذا تجمعها الصفحة. يستقبل عنصر <textarea> مخفي ضغطات المفاتيح واللصق وتركيب النص عبر IME، ويحوّلها إلى أحداث مفاتيح للتطبيق. يؤدي النقر على الصفحة إلى نقل التركيز إليه.

الخطوة التالية

تضم النماذج الأولية من @textjs/core بالإصدار ⁦0.0.x⁩ على npm كل شيء في حزمة واحدة. ينقل ⁦Text.js 0.1⁩ العارض الموصوف هنا إلى @textjs/core، وموفّق React إلى @textjs/react، ويضيف ⁦App Router⁩.