التضمين في مواقع أخرى
أضف تطبيقًا إلى موقع آخر بوسم 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. مرّر كائنًا لتغيير القيم الافتراضية:
import { defineConfig } from "@textjs/app";export default defineConfig({ embed: { initialPath: "/status", },});| الخيار | القيمة الافتراضية | الوصف |
|---|---|---|
initialPath | "/" | المسار الذي تفتح عليه وحدة التحكم. يجب أن يبدأ بـ / |
globalName | "text" | الاسم العام الذي يثبّت التطبيق تحته واجهة API لوحدة التحكم عند بدء تشغيله. لا يثبّت false أي اسم |
initialClear | false | مسح وحدة تحكم الصفحة قبل الإطار الأول. تبقى افتراضيًا مخرجات الصفحة السابقة فوق التطبيق |
standalone | false | بناء صفحات التطبيق نفسه أيضًا (index.html)، كما يفعل text build دون embed |
allowOrigins | [] | المواقع التي يُسمح لصفحاتها بتشغيل مسارات معروضة على الخادم من هذا الخادم. راجع المسارات المعروضة على الخادم |
credentials | false | إرسال بيانات اعتماد المتصفح لهذا الخادم مع طلبات الجلسات المباشرة من 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:
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.js | Cache-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-src | https://console.example.com | embed.js وأجزاؤه |
script-src | 'wasm-unsafe-eval' | Yoga، في التطبيقات المعروضة على العميل |
connect-src | https://console.example.com | yoga-[hash].wasm، و /__textjs/live للمسارات المعروضة على الخادم |
img-src | data: | عنصر <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 محددَي العناصر التي تبدأ التطبيق والعناصر التي تحتفظ بإدخال مفاتيحها لنفسها.