التضمين في مواقع أخرى

أضف تطبيقًا إلى موقع آخر بوسم script واحد: ⁦text build --embed⁩، <text-console>، ومسارات معروضة على الخادم واستضافة.

يبني الأمر text build --embed تطبيقك ليتمكن موقع آخر من إضافته بوسم نص برمجي واحد، مثل صفحة تسويقية، أو موقع توثيق، أو صفحة WordPress أو Webflow، أو تطبيق ⁦React 18⁩. يحصل الزوار الذين يفتحون DevTools على تطبيقك في وحدة التحكم. ينزّل الزوار الآخرون embed.js ‏(نحو 3 kB باستخدام gzip) ولا شيء غيره، إلا إذا احتوت الصفحة على <text-console>.

التضمينات تجريبية. قد يتغير الأمر text build --embed، والخيار embed، والعنصر <text-console>، وصادرات @textjs/app/experimental في إصدار ثانوي. راجع التغييرات غير المتوافقة.

إضافة تطبيق إلى موقع

ابنِ التطبيق:

text build --embed

ارفع dist/ إلى أي مضيف ثابت أو CDN، أو انسخه إلى مجلد public/ في الموقع، ثم أضف وسمًا واحدًا إلى صفحات الموقع:

<script type="module" src="https://console.example.com/embed.js"></script>

يحمّل embed.js ملفاته الأخرى نسبة إلى عنوان URL الخاص به، لذا يعمل dist/ من أي مسار أو أصل. لا يحتوي إلا على embed.js و assets/، دون index.html أو 404.html قد يحل محل ملفات الموقع نفسه. يحتاج التطبيق الذي يحتوي على مسارات معروضة على الخادم، أو معالجات مسارات، أو middleware إلى خادم بدلًا من رفع ملفات ثابتة فقط. يشغّل المحوّلان vercel و cloudflare معالجات المسارات و middleware، وتحتاج المسارات المعروضة على الخادم إلى text start أو المحوّل bun.

لإظهار مكان التطبيق للزوار، أضف <text-console> في أي موضع في الصفحة. يبدأ التطبيق أيضًا عند الضغط على أي عنصر يحمل data-textjs-engage:

<button type="button" data-textjs-engage>Open in the console</button>

لتجربته محليًا، شغّل text dev --embed. يقدّم /embed.js بإطار أول جديد مع كل طلب، وصفحة معاينة على / ‏(على /__textjs/embed مع standalone) تحمّله بالطريقة التي يحمّله بها موقع آخر. يمكن أيضًا لموقع على منفذ محلي آخر تحميل http://localhost:3000/embed.js.

الإعداد

يعادل text build --embed الإعداد embed: true في text.config.ts. مرّر كائنًا لتغيير القيم الافتراضية:

text.config.tsts
import { defineConfig } from "@textjs/app";export default defineConfig({  embed: {    initialPath: "/status",  },});
الخيارالقيمة الافتراضيةالوصف
initialPath"/"المسار الذي تفتح عليه وحدة التحكم. يجب أن يبدأ بـ /
globalName"text"الاسم العام الذي يثبّت التطبيق تحته واجهة API لوحدة التحكم عند بدء تشغيله. لا يثبّت false أي اسم
initialClearfalseمسح وحدة تحكم الصفحة قبل الإطار الأول. تبقى افتراضيًا مخرجات الصفحة السابقة فوق التطبيق
standalonefalseبناء صفحات التطبيق نفسه أيضًا (index.html)، كما يفعل text build دون embed
allowOrigins[]المواقع التي يُسمح لصفحاتها بتشغيل مسارات معروضة على الخادم من هذا الخادم. راجع المسارات المعروضة على الخادم
credentialsfalseإرسال بيانات اعتماد المتصفح لهذا الخادم مع طلبات الجلسات المباشرة من allowOrigins، بدلًا من إسقاط ملفات تعريف الارتباط و Authorization. يتطلب أصولًا محددة بدقة

يحدد viewport.width × viewport.height ‏(افتراضيًا ⁦120 × 36⁩) حجم الإطار الأول. تمثل title و hints و action القيم الافتراضية للعنصر <text-console>. لا يحتوي embed.js على إطار أول معروض مسبقًا عند استخدام ppr: false، أو حين يمكن أن يطابق middleware.ts المسار initialPath، لأن أي شخص يستطيع تنزيل embed.js. تبقى المرآة فارغة حتى يرسم التطبيق.

آلية العمل

  • لا يُحمّل أي شيء آخر حتى ظهور نية استخدام وحدة التحكم. يسجّل embed.js الإطار الأول المعروض مسبقًا، ثم ينتظر. يُحمّل التطبيق عند إرساء DevTools (عند التحميل أو لاحقًا)، أو عندما يضغط الزائر اختصار DevTools في الصفحة (⁦F12⁩، ⁦Ctrl+Shift+J⁩، I، C أو K، أو ⁦⌘⌥J⁩، I، C أو K على Mac)، أو عندما يضغط عنصر <text-console> أو [data-textjs-engage]. بعد ذلك، يبدأ التطبيق فورًا عند إعادة التحميل في علامة التبويب نفسها. لا يغيّر DevTools في نافذة مستقلة حجم الصفحة، لذا يرى الزوار الذين فتحوه من قائمة الإطار الأول حتى يضغطوا أحد تلك العناصر.
  • لا يبدأ التطبيق على شاشات اللمس. على جهاز يطابق (hover: none) and (pointer: coarse)، لا يسجّل embed.js شيئًا ولا يضيف مستمعات. الملف الوحيد الذي يحمّله هو ملف <text-console>، لعرض مرآة.
  • لا تصل ضغطات المفاتيح إلى التطبيق إلا بعد أن يفعّله الزائر. لتفعيله، يضغط الزائر عنصر <text-console> أو [data-textjs-engage]، أو ينقر الصفحة (خارج الروابط والأزرار والحقول) لإعادة التركيز من DevTools. لا تصل الكتابة في حقول الصفحة نفسها أو في عنصر [data-textjs-bypass] إلى التطبيق مطلقًا، وتمرّر العجلة الصفحة دائمًا.
  • لا يتغير عنوان URL للصفحة أو سجلها أو عنوانها مطلقًا. تتنقل المسارات و <Link> و useRouter() داخل وحدة التحكم. في التطبيقات المعروضة على العميل، تُفتح عناوين URL المطلقة في علامة تبويب جديدة.
  • تكون الأولوية لتطبيق Text.js في الصفحة. عندما يبدأ، يُزال التضمين الجاري من شجرة المكوّنات، ويتوقف عن قراءة المفاتيح ويحذف اسمه العام. يسجّل التضمين الذي يُحمّل بينما يستحوذ تطبيق آخر على وحدة التحكم تحذيرًا واحدًا ولا يبدأ.
  • يضمّن التضمين نسخته الخاصة من ⁦React 19⁩. لا يحتوي على react-dom أو استيرادات مجردة، لذا لا يؤثر فيه إصدار React في الصفحة، أو خريطة استيراد تخص react، أو ⁦React DevTools⁩.

الملفات وأوقات تحميلها

الملفوقت التحميلgzip
embed.jsمع الصفحة2.9 kB
assets/text-console-[hash].jsعندما تحتوي الصفحة على <text-console>4.0 kB
assets/embed-client-[hash].js و assets/yoga-[hash].wasmعند ظهور نية استخدام وحدة التحكم112.8 ⁦kB + 29.0⁩ kB
assets/embed-player-[hash].jsعند ظهور نية استخدام وحدة التحكم، بدلًا من العميل، عند استخدام مسار معروض على الخادم22.7 kB
assets/bidi-[hash].jsأول مرة يعرض فيها التطبيق نصًا من اليمين إلى اليسار (راجع تحميل محرك bidi)10.1 kB

تخص هذه الأحجام مربع حالة بحدود وأبعاد ⁦120 × 36⁩. يطبع text build --embed أحجام تطبيقك. الملف assets/embed-[hash].js نسخة من embed.js باسم لا يتغير مطلقًا (راجع عمليات النشر).

<text-console>

يعرّف embed.js العنصر <text-console> ولا يحمّل ملفه إلا عند وجوده في الصفحة. يستخدم ⁦Shadow DOM⁩، لذا لا يصل CSS الخاص بالصفحة إلى داخله، ولا تحتاج سياسة style-src 'self' إلى تغييرات.

<text-console></text-console><text-console mirror></text-console><text-console chrome></text-console>

من دون سمات، يكون شريطًا من سطر واحد. يعرض اختصار المتصفح لفتح وحدة التحكم، ثم يطلب نقرة، ثم يسرد مفاتيح التطبيق التي تضيء عند وصولها إليه. في Safari، تطلب الخطوة الأولى تفعيل «إظهار الميزات لمطوري الويب» في إعدادات Safari.

مع mirror، يعرض إطار التطبيق في الصفحة: الإطار الأول المعروض مسبقًا قبل تحميل أي شيء آخر، ثم التطبيق الجاري. يبدأ الضغط عليه التطبيق ويرسل إليه ضغطات مفاتيح الزائر، لذا يعمل التطبيق حتى مع إغلاق DevTools. يرسم chrome نافذة DevTools حول المرآة ويضمّن تفعيل mirror.

السمةالوصف
mirrorعرض إطارات التطبيق
chromeعرض الإطارات في نافذة DevTools
titleعنوان الشريط (الافتراضي: title في text.config.ts)
hintsالمفاتيح المراد سردها، بالصيغة "← →:switch region, Enter:open" (الافتراضي: hints في text.config.ts)
actionالإجراء الأول المطلوب، ويُعرض بصيغة «انقر هنا و…» (الافتراضي: action في text.config.ts، أو الضغط على أول مفتاح في التلميحات)

على شاشات اللمس، تعرض المرآة الإطار الأول دون بدء التطبيق، ويُخفى الشريط. نص العنصر باللغة الإنجليزية.

حجز المساحة

يطبع text build --embed سطر CSS واحدًا لشبكة تطبيقك. الصقه في ورقة أنماط الصفحة. يحدد حجم <text-console> قبل تحميل ملف العنصر، فلا يتحرك أي شيء في الصفحة عند وصول الملف. للشبكة الافتراضية ⁦120 × 36⁩، يكون السطر:

text-console{display:block;min-height:58px}text-console:is([mirror],[chrome]){position:relative;box-sizing:content-box;min-height:0;max-width:885px;aspect-ratio:885/522}text-console[chrome]{padding-top:43px}@media (hover:none) and (pointer:coarse){text-console:not([mirror],[chrome]){display:none}}

يبلغ ارتفاع الشريط 58 px. تحافظ المرآة على نسبة أبعاد الشبكة، حتى عرضها الطبيعي (885 px عند 120 عمودًا)، ويضيف chrome مقدار 43 px. لا يعتمد الحجم على الخطوط. تعيد الدالة reserveCss(cols, rows) من @textjs/app/experimental السطر نفسه.

تكون المرآة الظاهرة قبل التمرير عادة أكبر عنصر في الصفحة، لذا تحدد مقياس أكبر رسم محتوى (LCP) للصفحة. يُرسم إطارها الأول بعد تحميل embed.js ثم ملف العنصر، الواحد تلو الآخر. يُرسم باقي الصفحة كما لو لم يكن التضمين موجودًا. لا تؤثر المرآة الموجودة أسفل الصفحة في LCP. في صفحة لها نصوص برمجية مؤجلة خاصة بها، مثل تطبيق React، يشغّل <script type="module" async src="…"> الملف embed.js دون انتظارها، فتُرسم المرآة الظاهرة قبل التمرير في وقت أبكر.

التنسيق

لتوسيط المرآة، امنحها هوامش تلقائية على المحور السطري:

text-console {  margin-inline: auto;}

تكون داكنة افتراضيًا. اضبط color-scheme: light لاستخدام ألوان فاتحة، أو اضبط اللونين اللذين تُمزج منهما سائر الدرجات. ينسّق ::part(frame) الإطار نفسه. يحتفظ chrome بخلفية DevTools الخاصة.

text-console {  --text-console-bg: #fafafa;  --text-console-fg: #171717;}text-console::part(frame) {  border-radius: 0;}

المسارات المعروضة على الخادم

عندما يكون initialPath مسارًا يحتوي على export const runtime = "server"، يحمّل embed.js مشغّل الإطارات بدلًا من بيئة تشغيل العميل. يبث الإطارات من الخادم الذي جاء منه embed.js، لذا شغّل text start أو المحوّل bun. تفشل عملية البناء مع المحوّلات الأخرى. راجع العرض على الخادم.

لا تستطيع صفحة على موقع آخر فتح جلسة مباشرة إلا عندما يكون أصلها ضمن allowOrigins:

text.config.tsts
import { defineConfig } from "@textjs/app";export default defineConfig({  embed: {    initialPath: "/status",    allowOrigins: ["https://www.example.com", "https://*.example.com"],  },});
  • تستجيب الجلسة فقط للأصل الذي فتحها.
  • يسقط Text.js افتراضيًا ملفات تعريف الارتباط وترويسة Authorization للجلسات القادمة من مواقع أخرى، لذا يرى middleware زائرًا لم يسجّل الدخول. يحتفظ بها credentials: true، ويتطلب أصولًا محددة بدقة. يرسلها المتصفح عندما تشترك الصفحة والخادم في نطاق قابل للتسجيل (www.example.com و console.example.com) ويشمل ملف تعريف الارتباط الخادم (Domain=example.com، أو مضبوط على console.example.com). من المواقع الأخرى، لا يرسل إلا ملفات تعريف الارتباط المضبوطة باستخدام SameSite=None; Secure، ولا ترسل المتصفحات التي تحظر ملفات تعريف الارتباط التابعة لجهات خارجية أيًا منها.
  • ترسل الجلسة المباشرة طلبات تحقق مسبق من نوع OPTIONS إلى /__textjs/live فور اتصالها، وعند إعادة الاتصال. لا تحمل طلبات التحقق المسبق ملفات تعريف ارتباط أو Authorization، لذا يجب أن يسمح middleware المطابق لذلك المسار بمرور طلبات OPTIONS دون تسجيل الدخول.
  • خلف وكيل عكسي أو CDN، اضبط server.trustProxy (راجع خلف وكيل). من دونه، يحمل كل زائر عنوان الوكيل، فيشتركون جميعًا في حد واحد لعدد الجلسات لكل عميل.

يقبل text dev الصفحات على localhost و 127.0.0.1 و [::1] على أي منفذ دون allowOrigins، ما لم يُضبط credentials.

الاستضافة

يقدّم text start والمحوّلات bun و vercel و cloudflare بناء التضمين بهذه الترويسات. يكتبها المحوّل static في _headers، الذي تقرؤه ⁦Cloudflare Pages⁩ و Netlify. على أي مضيف آخر، مثل حاوية تخزين أو مجلد public/ الخاص بالموقع، اضبطها بنفسك:

المسارالترويسات
/embed.jsCache-Control: public, max-age=0, must-revalidate
/assets/*Cache-Control: public, max-age=31536000, immutable
كلاهما، عندما يكون الموقع على أصل آخرAccess-Control-Allow-Origin: * و Cross-Origin-Resource-Policy: cross-origin

تجلب المتصفحات نصوص الوحدات البرمجية و WebAssembly باستخدام CORS، لذا تحتاج الملفات على أصل آخر إلى Access-Control-Allow-Origin. قدّم yoga-[hash].wasm مضغوطًا بالنوع application/wasm (راجع WebAssembly).

عمليات النشر

يُعاد التحقق من صلاحية embed.js مع كل تحميل للصفحة، فيحصل الزوار الجدد على النسخة المنشورة الجديدة فورًا. تظل علامة التبويب التي حمّلت ملف embed.js السابق تستورد ملفات البناء السابق عندما يفتح الزائر وحدة التحكم، ربما بعد ساعات. أبقِ مجلد assets/ للنسخة المنشورة السابقة متاحًا عبر الإنترنت: انسخ الملفات الجديدة فوق القديمة بدلًا من المزامنة مع الحذف، واحذف الملفات القديمة بعد يوم أو يومين. لتثبيت بناء معين، ضع ملفه assets/embed-[hash].js في الوسم. يجب أن تبقى ملفات ذلك البناء متاحة عبر الإنترنت أيضًا.

عندما تصبح الملفات غير متاحة، يسجّل embed.js تحذيرًا واحدًا يطلب من الزائر إعادة التحميل، ولا يبدأ التشغيل أو يعيد تحميل الصفحة بنفسه.

سياسة أمان المحتوى

لا يحتاج embed.js إلى نصوص برمجية أو أنماط مضمنة. أضف هذه القيم إلى سياسة الصفحة المضيفة، مع وضع 'self' بدلًا من الأصل عندما يكون dist/ على الأصل نفسه:

التوجيهالقيمةالاستخدام
script-srchttps://console.example.comembed.js وأجزاؤه
script-src'wasm-unsafe-eval'Yoga، في التطبيقات المعروضة على العميل
connect-srchttps://console.example.comyoga-[hash].wasm، و /__textjs/live للمسارات المعروضة على الخادم
img-srcdata:عنصر <text-console mirror> الذي يرسم تطبيقه <Image> أو <Pixels> فقط

تحميل التطبيق ذاتيًا

يصدّر @textjs/app/experimental الأجزاء التي بُني منها embed.js، للصفحات التي تحمّل تطبيقًا بطريقتها الخاصة:

التصديرالوصف
startEmbed(paint, boot, src, element?)ما يشغّله embed.js: تسجيل الإطار الأول، وانتظار نية استخدام وحدة التحكم، ثم استدعاء boot
bootEmbedClient(options)بدء تطبيق معروض على العميل مع توجيه في الذاكرة وإدخال لوحة مفاتيح مشروط بالتفاعل مع الصفحة
bootEmbedPlayer(options)بدء مشغّل الإطارات لمسار معروض على الخادم
createHostInput(options)إدخال لوحة مفاتيح يتبع القواعد أعلاه
createTextConsoleMount(config)إرجاع دالة mount(element, shell?) للعنصر <text-console>
reserveCss(cols, rows)شيفرة CSS التي تحجز مساحة <text-console>

يمثل ENGAGE_SELECTOR و BYPASS_SELECTOR محددَي العناصر التي تبدأ التطبيق والعناصر التي تحتفظ بإدخال مفاتيحها لنفسها.

تحرير هذه الصفحة على GitHub