jsdom هو تطبيق جافا سكريبت خالص للعديد من معايير الويب، ولا سيما معايير WHATWG DOM وHTML، للاستخدام مع Node.js. بشكل عام، الهدف من المشروع هو محاكاة ما يكفي من مجموعة فرعية من متصفح الويب لتكون مفيدة لاختبار واستخراج تطبيقات الويب في العالم الحقيقي.
تتطلب أحدث الإصدارات من jsdom الإصدار 18 من Node.js أو أحدث. (لا تزال إصدارات jsdom الأقدم من الإصدار 23 تعمل مع إصدارات Node.js السابقة، ولكنها غير مدعومة.)
const jsdom = require("jsdom");const { JSDOM } = jsdom;
لاستخدام jsdom، ستستخدم بشكل أساسي مُنشئ JSDOM
، وهو عبارة عن تصدير مسمى للوحدة الرئيسية jsdom. تمرير سلسلة المنشئ. سوف تستعيد كائن JSDOM
، الذي يحتوي على عدد من الخصائص المفيدة، أبرزها window
:
const dom = new JSDOM(`<!DOCTYPE html><p>Hello World</p>`);console.log(dom.window.document.querySelector("p").textContent); // "مرحبا بالعالم"
(لاحظ أن jsdom سيقوم بتحليل HTML الذي تمرره إليه تمامًا كما يفعل المتصفح، بما في ذلك العلامات الضمنية <html>
و <head>
و <body>
.)
الكائن الناتج هو مثيل لفئة JSDOM
، والذي يحتوي على عدد من الخصائص والأساليب المفيدة إلى جانب window
. بشكل عام، يمكن استخدامه للعمل على jsdom من "الخارج"، والقيام بأشياء غير ممكنة مع واجهات برمجة تطبيقات DOM العادية. بالنسبة للحالات البسيطة، حيث لا تحتاج إلى أي من هذه الوظائف، نوصي بنمط ترميز مثل
const { window } = new JSDOM(`...`);// أو Evenconst { document } = (new JSDOM(`...`)).window;
يوجد أدناه توثيق كامل لكل ما يمكنك فعله باستخدام فئة JSDOM
، في القسم " JSDOM
Object API".
يقبل منشئ JSDOM
معلمة ثانية يمكن استخدامها لتخصيص jsdom الخاص بك بالطرق التالية.
const dom = جديد JSDOM(``, { عنوان URL: "https://example.org/"، المُحيل: "https://example.com/"، نوع المحتوى: "نص/أتش تي أم أل"، includeNodeLocations: صحيح، حصة التخزين: 10000000});
url
يعين القيمة التي يتم إرجاعها بواسطة window.location
و document.URL
و document.documentURI
، ويؤثر على أشياء مثل تحليل عناوين URL النسبية داخل المستند والقيود ذات الأصل نفسه والمرجع المستخدم أثناء جلب الموارد الفرعية. يتم تعيينه افتراضيًا على "about:blank"
.
يؤثر referrer
فقط على القيمة المقروءة من document.referrer
. يتم تعيينه افتراضيًا على عدم وجود مُحيل (والذي ينعكس كسلسلة فارغة).
يؤثر contentType
على القيمة المقروءة من document.contentType
، بالإضافة إلى كيفية تحليل المستند: بتنسيق HTML أو XML. سيتم طرح القيم التي ليست من نوع HTML MIME أو نوع XML MIME. يتم تعيينه افتراضيًا على "text/html"
. في حالة وجود معلمة charset
، فقد يؤثر ذلك على معالجة البيانات الثنائية.
يحتفظ includeNodeLocations
بمعلومات الموقع التي ينتجها محلل HTML، مما يسمح لك باستعادتها باستخدام طريقة nodeLocation()
(الموضحة أدناه). كما أنه يضمن صحة أرقام الأسطر التي تم الإبلاغ عنها في تتبعات مكدس الاستثناءات للتعليمات البرمجية التي يتم تشغيلها داخل عناصر <script>
. يتم تعيينه افتراضيًا على false
لتوفير أفضل أداء، ولا يمكن استخدامه مع نوع محتوى XML نظرًا لأن محلل XML الخاص بنا لا يدعم معلومات الموقع.
storageQuota
هو الحد الأقصى للحجم في وحدات التعليمات البرمجية لمناطق التخزين المنفصلة التي يستخدمها localStorage
و sessionStorage
. ستؤدي محاولات تخزين بيانات أكبر من هذا الحد إلى طرح DOMException
. افتراضيًا، يتم تعيينها على 5,000,000 وحدة تعليمات برمجية لكل أصل، كما هو مستوحى من مواصفات HTML.
لاحظ أنه يتم تحديد url
referrer
بشكل أساسي قبل استخدامهما، لذلك، على سبيل المثال، إذا قمت بالتمرير إلى "https:example.com"
، فسوف يفسر jsdom ذلك كما لو كنت قد قدمت "https://example.com/"
. إذا قمت بتمرير عنوان URL غير قابل للتحليل، فسيتم طرح المكالمة. (يتم تحليل عناوين URL وتسلسلها وفقًا لمعايير URL.)
أقوى قدرة لـ jsdom هي أنه يمكنه تنفيذ البرامج النصية داخل ملف jsdom. يمكن لهذه البرامج النصية تعديل محتوى الصفحة والوصول إلى جميع واجهات برمجة تطبيقات منصة الويب التي تنفذها jsdom.
ومع ذلك، يعد هذا أيضًا خطيرًا للغاية عند التعامل مع محتوى غير موثوق به. إن صندوق الحماية jsdom ليس مضمونًا، ويمكن للتعليمات البرمجية التي تعمل داخل <script>
s، إذا حاولت جاهدة بما فيه الكفاية، الوصول إلى بيئة Node.js، وبالتالي إلى جهازك. على هذا النحو، يتم تعطيل القدرة على تنفيذ البرامج النصية المضمنة في HTML افتراضيًا:
const dom = new JSDOM(`<body> <div id="content"></div> <script>document.getElementById("content").append(document.createElement("hr"));</script> </body>`);// لن يتم تنفيذ البرنامج النصي افتراضيًا:console.log(dom.window.document.getElementById("content").children.length); // 0
لتمكين تنفيذ البرامج النصية داخل الصفحة، يمكنك استخدام خيار runScripts: "dangerously"
:
const dom = new JSDOM(`<body> <div id="content"></div> <script>document.getElementById("content").append(document.createElement("hr"));</script> </body>`, { runScripts: "dangerously" });// سيتم تنفيذ البرنامج النصي وتعديله DOM:console.log(dom.window.document.getElementById("content").children.length); // 1
نؤكد مرة أخرى على استخدام هذا فقط عند تغذية كود jsdom الذي تعلم أنه آمن. إذا كنت تستخدمه على تعليمات برمجية عشوائية يقدمها المستخدم، أو تعليمات برمجية من الإنترنت، فأنت تقوم فعليًا بتشغيل تعليمات برمجية Node.js غير موثوقة، وقد يتعرض جهازك للخطر.
إذا كنت تريد تنفيذ برامج نصية خارجية ، مضمنة عبر <script src="">
، فستحتاج أيضًا إلى التأكد من تحميلها. للقيام بذلك، أضف resources: "usable"
كما هو موضح أدناه. (من المحتمل أيضًا أن ترغب في تعيين خيار url
، للأسباب التي تمت مناقشتها هناك.)
سمات معالج الأحداث، مثل <div onclick="">
، تخضع أيضًا لهذا الإعداد؛ لن تعمل إلا إذا تم ضبط runScripts
على "dangerously"
. (ومع ذلك، فإن خصائص معالج الأحداث، مثل div.onclick = ...
، ستعمل بغض النظر عن runScripts
.)
إذا كنت تحاول ببساطة تنفيذ البرنامج النصي "من الخارج"، فبدلاً من السماح لعناصر <script>
وسمات معالجات الأحداث بالعمل "من الداخل"، يمكنك استخدام خيار runScripts: "outside-only"
، الذي يتيح نسخًا جديدة من سيتم تثبيت جميع العناصر العالمية المتوفرة بمواصفات JavaScript على window
. يتضمن ذلك أشياء مثل window.Array
و window.Promise
وما إلى ذلك. كما يتضمن أيضًا window.eval
الذي يسمح بتشغيل البرامج النصية، ولكن مع window
jsdom باعتبارها النافذة العامة:
const dom = new JSDOM(`<body> <div id="content"></div> <script>document.getElementById("content").append(document.createElement("hr"));</script> </body>`, { runScripts: "outside-only" });// قم بتشغيل برنامج نصي خارج JSDOM:dom.window.eval('document.getElementById("content").append(document.createElement("p"));');console.log(dom.window.document.getElementById("content"). children.length); // 1console.log(dom.window.document.getElementsByTagName("hr").length); // 0console.log(dom.window.document.getElementsByTagName("p").length); // 1
يتم إيقاف تشغيل هذا بشكل افتراضي لأسباب تتعلق بالأداء، ولكن تمكينه آمن.
لاحظ أنه في التكوين الافتراضي، بدون تعيين runScripts
، ستكون قيم window.Array
و window.eval
وما إلى ذلك هي نفسها التي توفرها بيئة Node.js الخارجية. وهذا يعني أن window.eval === eval
سيظل ثابتًا، لذا لن يقوم window.eval
بتشغيل البرامج النصية بطريقة مفيدة.
ننصح بشدة بعدم محاولة "تنفيذ البرامج النصية" عن طريق دمج البيئتين العالميتين jsdom وNode معًا (على سبيل المثال عن طريق إجراء global.window = dom.window
)، ثم تنفيذ البرامج النصية أو اختبار التعليمات البرمجية داخل بيئة Node العالمية. بدلاً من ذلك، يجب عليك التعامل مع jsdom كما تفعل مع المتصفح، وتشغيل جميع البرامج النصية والاختبارات التي تحتاج إلى الوصول إلى DOM داخل بيئة jsdom، باستخدام window.eval
أو runScripts: "dangerously"
. قد يتطلب هذا، على سبيل المثال، إنشاء حزمة browserify لتنفيذها كعنصر <script>
، تمامًا كما تفعل في المتصفح.
أخيرًا، بالنسبة لحالات الاستخدام المتقدمة، يمكنك استخدام طريقة dom.getInternalVMContext()
، الموثقة أدناه.
ليس لدى jsdom القدرة على عرض المحتوى المرئي، وسيعمل كمتصفح بدون رأس افتراضيًا. فهو يوفر تلميحات لصفحات الويب من خلال واجهات برمجة التطبيقات (APIs) مثل document.hidden
بأن محتواها غير مرئي.
عندما يتم ضبط خيار pretendToBeVisual
على true
، سيتظاهر jsdom بأنه يقوم بعرض المحتوى وعرضه. يقوم بذلك عن طريق:
تغيير document.hidden
لإرجاع false
بدلاً من true
تغيير document.visibilityState
لإرجاع "visible"
بدلاً من "prerender"
تمكين أساليب window.requestAnimationFrame()
و window.cancelAnimationFrame()
، والتي لا توجد بخلاف ذلك
const window = (new JSDOM(``, {presentToBeVisual: true })).window;window.requestAnimationFrame(timestamp => { console.log(timestamp > 0);});
لاحظ أن jsdom لا يزال لا يقوم بأي تخطيط أو عرض، لذا فإن الأمر يتعلق فقط بالتظاهر بأنه مرئي، وليس حول تنفيذ أجزاء النظام الأساسي التي سينفذها متصفح ويب مرئي حقيقي.
افتراضيًا، لن يقوم jsdom بتحميل أي موارد فرعية مثل البرامج النصية أو أوراق الأنماط أو الصور أو إطارات iframe. إذا كنت تريد أن يقوم jsdom بتحميل مثل هذه الموارد، فيمكنك تمرير خيار resources: "usable"
، والذي سيقوم بتحميل جميع الموارد القابلة للاستخدام. هؤلاء هم:
الإطارات وإطارات iframe، عبر <frame>
و <iframe>
أوراق الأنماط، عبر <link rel="stylesheet">
البرامج النصية، عبر <script>
، ولكن فقط إذا تم تعيين runScripts: "dangerously"
أيضًا
الصور عبر <img>
، ولكن فقط إذا كانت حزمة canvas
npm مثبتة أيضًا (راجع "دعم Canvas" أدناه)
عند محاولة تحميل الموارد، تذكر أن القيمة الافتراضية لخيار عنوان url
هي "about:blank"
، مما يعني أن أي موارد مضمنة عبر عناوين URL النسبية سيفشل تحميلها. (نتيجة محاولة تحليل عنوان URL /something
مقابل عنوان URL about:blank
هي خطأ.) لذا، من المحتمل أن ترغب في تعيين قيمة غير افتراضية لخيار url
في تلك الحالات، أو استخدام أحد الخيارات الملائمة واجهات برمجة التطبيقات التي تقوم بذلك تلقائيًا.
لتخصيص سلوك تحميل الموارد الخاص بـ jsdom بشكل كامل، يمكنك تمرير مثيل لفئة ResourceLoader
كقيمة خيار resources
:
const ResourceLoader = new jsdom.ResourceLoader({ الوكيل: "http://127.0.0.1:9001"، صارمةSSL: خطأ، userAgent: "Mellblomenator/9000"،});const dom = new JSDOM(``, { Resources: ResourceLoader });
الخيارات الثلاثة لمنشئ ResourceLoader
هي:
proxy
هو عنوان وكيل HTTP الذي سيتم استخدامه.
يمكن ضبط strictSSL
على false لتعطيل شرط صلاحية شهادات SSL.
يؤثر userAgent
على رأس User-Agent
المرسل، وبالتالي القيمة الناتجة لـ navigator.userAgent
. القيمة الافتراضية هي `Mozilla/5.0 (${process.platform || "unknown OS"}) AppleWebKit/537.36 (KHTML, like Gecko) jsdom/${jsdomVersion}`
.
يمكنك أيضًا تخصيص جلب الموارد عن طريق تصنيف ResourceLoader
فرعيًا وتجاوز طريقة fetch()
. على سبيل المثال، إليك إصدار يتجاوز الاستجابة المقدمة لعنوان URL محدد:
فئة CustomResourceLoader تمتد jsdom.ResourceLoader { fetch(url, options) {// تجاوز محتويات هذا البرنامج النصي للقيام بشيء غير عادي.if (url === "https://example.com/some-spec-script.js") { return Promise.resolve( Buffer.from("window.someGlobal = 5;"));}return super.fetch(url, options); }}
سوف يقوم jsdom باستدعاء طريقة fetch()
الخاصة بمحمل الموارد المخصص لديك عندما يواجه موردًا "قابلاً للاستخدام"، وفقًا للقسم أعلاه. تأخذ الطريقة سلسلة عنوان URL، بالإضافة إلى بعض الخيارات التي يجب عليك تمريرها دون تعديل في حالة استدعاء super.fetch()
. يجب أن يُرجع وعدًا لكائن Node.js Buffer
، أو يُرجع null
إذا كان المورد متعمدًا ألا يتم تحميله. بشكل عام، سترغب معظم الحالات في تفويض الأمر إلى super.fetch()
، كما هو موضح.
أحد الخيارات التي ستتلقاها في fetch()
سيكون العنصر (إن أمكن) الذي يقوم بجلب المورد.
فئة CustomResourceLoader تمتد jsdom.ResourceLoader { fetch(url, options) {if (options.element) { console.log(`العنصر ${options.element.localName} يطلب عنوان url ${url}`);}return super.fetch(url, options); }}
مثل متصفحات الويب، لدى jsdom مفهوم "وحدة التحكم". يؤدي هذا إلى تسجيل المعلومات المرسلة مباشرة من الصفحة، عبر البرامج النصية التي يتم تنفيذها داخل المستند، بالإضافة إلى المعلومات من تطبيق jsdom نفسه. نطلق على وحدة التحكم التي يمكن للمستخدم التحكم فيها اسم "وحدة التحكم الافتراضية"، لتمييزها عن واجهة console
Node.js وعن واجهة برمجة التطبيقات window.console
الموجودة داخل الصفحة.
افتراضيًا، سيعيد منشئ JSDOM
مثيلًا بوحدة تحكم افتراضية تقوم بإعادة توجيه جميع مخرجاتها إلى وحدة تحكم Node.js. لإنشاء وحدة التحكم الافتراضية الخاصة بك وتمريرها إلى jsdom، يمكنك تجاوز هذا الإعداد الافتراضي عن طريق القيام بذلك
const virtualConsole = new jsdom.VirtualConsole();const dom = new JSDOM(``, { virtualConsole });
سيؤدي رمز مثل هذا إلى إنشاء وحدة تحكم افتراضية بدون أي سلوك. يمكنك منحه سلوكًا عن طريق إضافة مستمعي الأحداث لجميع طرق وحدة التحكم الممكنة:
virtualConsole.on("error", () => { ... });virtualConsole.on("warn", () => { ... });virtualConsole.on("info", () => { ... });virtualConsole.on("dir", () => { ... });// ... إلخ. راجع https://console.spec.whatwg.org/#logging
(لاحظ أنه من الأفضل إعداد مستمعي الأحداث قبل استدعاء new JSDOM()
، حيث قد تحدث أخطاء أو برنامج استدعاء وحدة التحكم أثناء التحليل.)
إذا كنت تريد ببساطة إعادة توجيه مخرجات وحدة التحكم الافتراضية إلى وحدة تحكم أخرى، مثل وحدة Node.js الافتراضية، فيمكنك القيام بذلك
virtualConsole.sendTo(console);
هناك أيضًا حدث خاص، "jsdomError"
، والذي سيتم إطلاقه مع كائنات الخطأ للإبلاغ عن الأخطاء من jsdom نفسها. وهذا مشابه لكيفية ظهور رسائل الخطأ غالبًا في وحدات تحكم متصفح الويب، حتى لو لم يتم تشغيلها بواسطة console.error
. حتى الآن، يتم إخراج الأخطاء التالية بهذه الطريقة:
أخطاء في تحميل الموارد الفرعية أو تحليلها (البرامج النصية وأوراق الأنماط والإطارات وإطارات iframe)
أخطاء تنفيذ البرنامج النصي التي لا تتم معالجتها بواسطة معالج حدث onerror
الذي يقوم بإرجاع true
أو يستدعي event.preventDefault()
الأخطاء غير المنفذة الناتجة عن استدعاء الأساليب، مثل window.alert
، والتي لا ينفذها jsdom، ولكن يتم تثبيتها على أي حال للتوافق مع الويب
إذا كنت تستخدم sendTo(c)
لإرسال الأخطاء إلى c
، فسيتم استدعاء c.error(errorStack[, errorDetail])
افتراضيًا مع معلومات من أحداث "jsdomError"
. إذا كنت تفضل الحفاظ على تعيين صارم للأحداث من واحد إلى واحد لاستدعاءات الطريقة، وربما التعامل مع "jsdomError"
بنفسك، فيمكنك القيام بذلك
virtualConsole.sendTo(c, { omitJSDOMErrors: true });
مثل متصفحات الويب، لدى jsdom مفهوم جرة ملفات تعريف الارتباط لتخزين ملفات تعريف الارتباط HTTP. يمكن الوصول إلى ملفات تعريف الارتباط التي لها عنوان URL على نفس المجال الموجود في المستند، ولم يتم وضع علامة HTTP عليها فقط، عبر document.cookie
API. بالإضافة إلى ذلك، ستؤثر جميع ملفات تعريف الارتباط الموجودة في جرة ملفات تعريف الارتباط على جلب الموارد الفرعية.
افتراضيًا، سيعيد مُنشئ JSDOM
نسخة تحتوي على وعاء ملفات تعريف ارتباط فارغ. لإنشاء جرة ملفات تعريف الارتباط الخاصة بك وتمريرها إلى jsdom، يمكنك تجاوز هذا الإعداد الافتراضي عن طريق القيام بذلك
const cookieJar = new jsdom.CookieJar(store, options);const dom = new JSDOM(``, { cookieJar });
يعد هذا مفيدًا في الغالب إذا كنت تريد مشاركة نفس جرة ملف تعريف الارتباط بين عدة jsdoms، أو قم بتجهيز جرة ملف تعريف الارتباط بقيم معينة مسبقًا.
يتم توفير أوعية ملفات تعريف الارتباط من خلال حزمة ملفات تعريف الارتباط القوية. إن مُنشئ jsdom.CookieJar
هو فئة فرعية من جرة ملفات تعريف الارتباط الصعبة والتي تقوم افتراضيًا بتعيين الخيار looseMode: true
، نظرًا لأن ذلك يتطابق بشكل أفضل مع سلوك المتصفحات. إذا كنت تريد استخدام أدوات وفئات ملفات تعريف الارتباط الصعبة بنفسك، فيمكنك استخدام تصدير وحدة jsdom.toughCookie
للوصول إلى مثيل وحدة ملفات تعريف الارتباط القوية المعبأة مع jsdom.
يسمح لك jsdom بالتدخل في إنشاء jsdom مبكرًا جدًا: بعد إنشاء كائنات Window
Document
، ولكن قبل تحليل أي HTML لملء المستند بالعقد:
const dom = new JSDOM(`<p>Hello</p>`, { beforeParse(window) {window.document.childNodes.length === 0;window.someCoolAPI = () => { /* ... */ }; }});
يعد هذا مفيدًا بشكل خاص إذا كنت تريد تعديل البيئة بطريقة ما، على سبيل المثال، لا يدعم إضافة حشوات لواجهات برمجة تطبيقات منصة الويب jsdom.
JSDOM
بمجرد إنشاء كائن JSDOM
، سيكون لديه الإمكانيات المفيدة التالية:
تقوم window
الخاصية باسترداد كائن Window
الذي تم إنشاؤه لك.
تعكس الخاصيتان virtualConsole
و cookieJar
الخيارات التي تمررها، أو الإعدادات الافتراضية التي تم إنشاؤها لك إذا لم يتم تمرير أي شيء لهذه الخيارات.
serialize()
سيعيد التابع serialize()
تسلسل HTML للمستند، بما في ذلك نوع المستند:
const dom = new JSDOM(`<!DOCTYPE html>hello`);dom.serialize() === "<!DOCTYPE html><html><head></head><body>hello</body></ html>";// التباين مع:dom.window.document.documentElement.outerHTML === "<html><head></head><body>hello</body></html>";
nodeLocation(node)
سيبحث التابع nodeLocation()
عن مكان عقدة DOM داخل المستند المصدر، ويعيد معلومات موقع parse5 للعقدة:
const dom = جديد JSDOM( `<p>مرحبًا <img src="foo.jpg"> </p>`، { includeNodeLocations: true });const document = dom.window.document;const bodyEl = document.body; // createconst ضمنيًا pEl = document.querySelector("p");const textNode = pEl.firstChild;const imgEl = document.querySelector("img");console.log(dom.nodeLocation(bodyEl)); // باطل؛ إنه ليس موجودًا في sourceconsole.log(dom.nodeLocation(pEl)); // { startOffset: 0، endOffset: 39، startTag: ...، endTag: ... }console.log(dom.nodeLocation(textNode)); // { startOffset: 3, endOffset: 13 }console.log(dom.nodeLocation(imgEl)); // { بداية الإزاحة: 13، نهاية الإزاحة: 32 }
لاحظ أن هذه الميزة تعمل فقط إذا قمت بتعيين خيار includeNodeLocations
؛ يتم إيقاف تشغيل مواقع العقد بشكل افتراضي لأسباب تتعلق بالأداء.
vm
باستخدام getInternalVMContext()
وحدة vm
المضمنة في Node.js هي ما يدعم سحر تشغيل البرنامج النصي لـ jsdom. تستفيد بعض حالات الاستخدام المتقدمة، مثل التجميع المسبق للبرنامج النصي ثم تشغيله عدة مرات، من استخدام الوحدة vm
مباشرة مع Window
الذي تم إنشاؤه بواسطة jsdom.
للوصول إلى الكائن العام ذي السياق المناسب للاستخدام مع واجهات برمجة تطبيقات vm
، يمكنك استخدام طريقة getInternalVMContext()
:
const { Script } = require("vm");const dom = new JSDOM(``, { runScripts: "outside-only" });const script = new Script(` if (!this.ran) { this.ran = 0; } ++this.ran;`);const vmContext = dom.getInternalVMContext();script.runInContext(vmContext);script.runInContext(vmContext);script.runInContext(vmContext);console.assert(dom.window.ran === 3);
هذه وظيفة متقدمة إلى حد ما، ونحن ننصح بالالتزام بواجهات DOM API العادية (مثل window.eval()
أو document.createElement("script")
) إلا إذا كانت لديك احتياجات محددة للغاية.
لاحظ أن هذه الطريقة ستطرح استثناءً إذا تم إنشاء مثيل JSDOM
بدون تعيين runScripts
، أو إذا كنت تستخدم jsdom في متصفح الويب.
reconfigure(settings)
تم وضع علامة [Unforgeable]
على الخاصية top
في window
في المواصفات، مما يعني أنها خاصية خاصة غير قابلة للتكوين وبالتالي لا يمكن تجاوزها أو تظليلها بواسطة تعليمات برمجية عادية تعمل داخل jsdom، حتى باستخدام Object.defineProperty
.
وبالمثل، في الوقت الحالي، لا يتعامل jsdom مع التنقل (مثل الإعداد window.location.href = "https://example.com/"
)؛ سيؤدي القيام بذلك إلى إصدار وحدة التحكم الافتراضية "jsdomError"
موضحًا أن هذه الميزة لم يتم تنفيذها، ولن يتغير شيء: لن يكون هناك كائن Window
أو Document
جديد، وسيظل كائن location
window
الحالية كما هو قيم الممتلكات.
ومع ذلك، إذا كنت تتصرف من خارج النافذة، على سبيل المثال، في بعض أطر الاختبار التي تنشئ jsdoms، فيمكنك تجاوز أحدهما أو كليهما باستخدام طريقة reconfigure()
الخاصة:
const dom = new JSDOM();dom.window.top === dom.window;dom.window.location.href === "about:blank";dom.reconfigure({ windowTop: myFakeTopForTesting, url: "https: //example.com/" });dom.window.top === myFakeTopForTesting;dom.window.location.href === "https://example.com/";
لاحظ أن تغيير عنوان URL الخاص بـ jsdom سيؤثر على جميع واجهات برمجة التطبيقات التي تعرض عنوان URL للمستند الحالي، مثل window.location
و document.URL
و document.documentURI
، بالإضافة إلى دقة عناوين URL النسبية داخل المستند وعمليات التحقق من نفس المصدر والمرجع المستخدم أثناء جلب الموارد الفرعية. ومع ذلك، لن يقوم بالتنقل إلى محتويات عنوان URL هذا؛ ستبقى محتويات DOM دون تغيير، ولن يتم إنشاء أي مثيلات جديدة لـ Window
أو Document
أو ما إلى ذلك.
fromURL()
بالإضافة إلى منشئ JSDOM
نفسه، يوفر jsdom طريقة مصنع لإرجاع الوعد لإنشاء jsdom من عنوان URL:
JSDOM.fromURL("https://example.com/"، options).then(dom => { console.log(dom.serialize());});
سيتم الوفاء بالوعد الذي تم إرجاعه بمثيل JSDOM
إذا كان عنوان URL صالحًا وكان الطلب ناجحًا. سيتم اتباع أي عمليات إعادة توجيه إلى وجهتها النهائية.
الخيارات المتوفرة لـ fromURL()
مشابهة لتلك المتوفرة لمنشئ JSDOM
، مع القيود والعواقب الإضافية التالية:
لا يمكن توفير خيارات url
و contentType
.
يتم استخدام خيار referrer
كرأس طلب Referer
HTTP للطلب الأولي.
يؤثر خيار resources
أيضًا على الطلب الأولي؛ وهذا مفيد إذا كنت تريد، على سبيل المثال، تكوين وكيل (انظر أعلاه).
يتم تحديد عنوان URL الخاص بـ jsdom ونوع المحتوى والمُحيل الناتج من الاستجابة.
يتم تخزين أي ملفات تعريف ارتباط تم تعيينها عبر رؤوس استجابة HTTP Set-Cookie
في جرة ملفات تعريف الارتباط الخاصة بـ jsdom. وبالمثل، يتم إرسال أي ملفات تعريف ارتباط موجودة بالفعل في جرة ملفات تعريف الارتباط المتوفرة كرؤوس طلب Cookie
HTTP.
fromFile()
على غرار fromURL()
، يوفر jsdom أيضًا طريقة مصنع fromFile()
لإنشاء jsdom من اسم ملف:
JSDOM.fromFile("stuff.html"، options).then(dom => { console.log(dom.serialize());});
سيتم الوفاء بالوعد الذي تم إرجاعه مع مثيل JSDOM
إذا كان من الممكن فتح الملف المحدد. كالعادة في واجهات برمجة تطبيقات Node.js، يتم إعطاء اسم الملف بالنسبة إلى دليل العمل الحالي.
الخيارات المتوفرة لـ fromFile()
مشابهة لتلك المتوفرة لمنشئ JSDOM
، مع الإعدادات الافتراضية الإضافية التالية:
سيكون خيار url
افتراضيًا هو عنوان URL للملف المطابق لاسم الملف المحدد، بدلاً من "about:blank"
.
سيكون خيار contentType
افتراضيًا هو "application/xhtml+xml"
إذا كان اسم الملف المحدد ينتهي بـ .xht
أو .xhtml
أو .xml
؛ وإلا فإنه سيستمر في الوضع الافتراضي إلى "text/html"
.
fragment()
في أبسط الحالات، قد لا تحتاج إلى مثيل JSDOM
كاملاً بكل قوته المرتبطة به. قد لا تحتاج حتى إلى Window
أو Document
! بدلًا من ذلك، تحتاج فقط إلى تحليل بعض HTML والحصول على كائن DOM يمكنك معالجته. من أجل ذلك، لدينا fragment()
، الذي يُنشئ DocumentFragment
من سلسلة نصية معينة:
const frag = JSDOM.fragment(`<p>Hello</p><p><strong>Hi!</strong>`);frag.childNodes.length === 2;frag.querySelector("strong"). textContent === "مرحبًا!"؛// إلخ.
هنا frag
هو مثيل DocumentFragment
، الذي يتم إنشاء محتوياته عن طريق تحليل السلسلة المتوفرة. يتم التحليل باستخدام عنصر <template>
، بحيث يمكنك تضمين أي عنصر هناك (بما في ذلك العناصر ذات قواعد التحليل الغريبة مثل <td>
). من المهم أيضًا ملاحظة أن DocumentFragment
الناتج لن يكون له سياق تصفح مرتبط: أي أن ownerDocument
للعناصر سيكون له خاصية defaultView
خالية، ولن يتم تحميل الموارد، وما إلى ذلك.
جميع استدعاءات المصنع fragment()
تؤدي إلى DocumentFragment
التي تشترك في نفس مالك القالب Document
. يسمح هذا بالعديد من الاستدعاءات fragment()
دون أي حمل إضافي. ولكنه يعني أيضًا أنه لا يمكن تخصيص الاستدعاءات إلى fragment()
باستخدام أي خيارات.
لاحظ أن التسلسل ليس سهلاً مع DocumentFragment
كما هو الحال مع كائنات JSDOM
الكاملة. إذا كنت بحاجة إلى إجراء تسلسل لـ DOM الخاص بك، فمن المحتمل أن تستخدم مُنشئ JSDOM
بشكل مباشر أكثر. لكن بالنسبة للحالة الخاصة للجزء الذي يحتوي على عنصر واحد، فمن السهل جدًا القيام بذلك من خلال الوسائل العادية:
const frag = JSDOM.fragment(`<p>Hello</p>`);console.log(frag.firstChild.outerHTML); // السجلات "<p>مرحبًا</p>"
يتضمن jsdom دعمًا لاستخدام حزمة canvas
لتوسيع أي عناصر <canvas>
باستخدام واجهة برمجة تطبيقات Canvas. لإنجاز هذا العمل، تحتاج إلى تضمين canvas
باعتباره تبعية في مشروعك، باعتباره نظيرًا لـ jsdom
. إذا تمكن jsdom من العثور على حزمة canvas
، فسوف يستخدمها، ولكن إذا لم تكن موجودة، فسوف تتصرف عناصر <canvas>
مثل عناصر <div>
. منذ الإصدار 13 من jsdom، أصبح الإصدار 2.x من canvas
مطلوبًا؛ الإصدار 1.x لم يعد مدعومًا.
بالإضافة إلى توفير سلسلة، يمكن أيضًا توفير منشئ JSDOM
بيانات ثنائية، في شكل Node.js Buffer
أو نوع بيانات ثنائي قياسي في JavaScript مثل ArrayBuffer
و Uint8Array
و DataView
وما إلى ذلك. عند الانتهاء من ذلك، سوف يتعرف jsdom الترميز من البايتات المتوفرة، والمسح بحثًا عن علامات <meta charset>
تمامًا كما يفعل المتصفح.
إذا كان خيار contentType
المتوفر يحتوي على معلمة charset
، فسيتجاوز هذا التشفير الترميز المستنشق - ما لم يكن UTF-8 أو UTF-16 BOM موجودًا، وفي هذه الحالة يكون لهما الأسبقية. (مرة أخرى، هذا يشبه المتصفح تمامًا).
ينطبق استنشاق التشفير هذا أيضًا على JSDOM.fromFile()
و JSDOM.fromURL()
. في الحالة الأخيرة، أي رؤوس Content-Type
يتم إرسالها مع الاستجابة ستحظى بالأولوية، بنفس طريقة خيار contentType
الخاص بالمنشئ.
لاحظ أنه في كثير من الحالات، يمكن أن يكون توفير البايتات بهذه الطريقة أفضل من توفير سلسلة. على سبيل المثال، إذا حاولت استخدام واجهة برمجة التطبيقات buffer.toString("utf-8")
الخاصة بـ Node.js، فلن تقوم Node.js بإزالة أي قوائم مكونات الصنف البادئة. إذا قمت بعد ذلك بإعطاء هذه السلسلة إلى jsdom، فسوف تفسرها حرفيًا، مع ترك قائمة مكونات الصنف سليمة. لكن كود فك تشفير البيانات الثنائية الخاص بـ jsdom سوف يزيل BOMs الرائدة، تمامًا مثل المتصفح؛ وفي مثل هذه الحالات، فإن توفير buffer
مباشرة سيعطي النتيجة المرجوة.
الموقتات في jsdom (التي تم تعيينها بواسطة window.setTimeout()
أو window.setInterval()
) سوف، حسب التعريف، تنفذ التعليمات البرمجية في المستقبل في سياق النافذة. نظرًا لعدم وجود طريقة لتنفيذ التعليمات البرمجية في المستقبل دون الحفاظ على العملية حية، فإن مؤقتات jsdom الرائعة ستبقي عملية Node.js الخاصة بك حية. وبالمثل، نظرًا لعدم وجود طريقة لتنفيذ التعليمات البرمجية في سياق كائن دون الحفاظ على هذا الكائن حيًا، فإن مؤقتات jsdom المعلقة ستمنع تجميع البيانات المهملة في النافذة التي تم جدولتها عليها.
إذا كنت تريد التأكد من إغلاق نافذة jsdom، استخدم window.close()
، الذي سينهي جميع الموقتات قيد التشغيل (ويزيل أيضًا أي مستمعين للأحداث في النافذة والمستند).
في Node.js، يمكنك تصحيح أخطاء البرامج باستخدام Chrome DevTools. راجع الوثائق الرسمية لكيفية البدء.
افتراضيًا، يتم تنسيق عناصر jsdom ككائنات JS قديمة في وحدة التحكم. لتسهيل تصحيح الأخطاء، يمكنك استخدام jsdom-devtools-formatter، والذي يتيح لك فحصها مثل عناصر DOM الحقيقية.
غالبًا ما يواجه الأشخاص مشكلة في تحميل البرنامج النصي غير المتزامن عند استخدام jsdom. تقوم العديد من الصفحات بتحميل البرامج النصية بشكل غير متزامن، ولكن لا توجد طريقة لمعرفة متى تنتهي من القيام بذلك، وبالتالي عندما يكون الوقت مناسبًا لتشغيل التعليمات البرمجية الخاصة بك وفحص بنية DOM الناتجة. وهذا قيد أساسي؛ لا يمكننا التنبؤ بما ستفعله البرامج النصية الموجودة على صفحة الويب، وبالتالي لا يمكننا إخبارك عند الانتهاء من تحميل المزيد من البرامج النصية.
يمكن حل هذا بعدة طرق. أفضل طريقة، إذا كنت تتحكم في الصفحة المعنية، هي استخدام الآليات التي يوفرها مُحمل البرنامج النصي لاكتشاف وقت التحميل. على سبيل المثال، إذا كنت تستخدم أداة تحميل الوحدات النمطية مثل RequireJS، فقد يبدو الرمز كما يلي:
// من جهة Node.js:const window = (new JSDOM(...)).window;window.onModulesLoaded = () => { console.log("جاهز للبدء!");};
<!-- داخل HTML الذي تقوم بتزويده إلى jsdom --><script>requirejs(["entry-module"], () => { window.onModulesLoaded();});</script>
إذا لم تكن تتحكم في الصفحة، فيمكنك تجربة الحلول البديلة مثل الاستقصاء عن وجود عنصر معين.
لمزيد من التفاصيل، راجع المناقشة في رقم 640، وخاصة تعليق @matthewkastor الثاقب.
على الرغم من أننا نستمتع بإضافة ميزات جديدة إلى jsdom وإبقائه محدثًا بأحدث مواصفات الويب، إلا أنه يفتقد العديد من واجهات برمجة التطبيقات. لا تتردد في تقديم مشكلة لأي شيء مفقود، ولكننا فريق صغير ومشغول، لذا قد يعمل طلب السحب بشكل أفضل.
يتم توفير بعض ميزات jsdom من خلال تبعياتنا. تتضمن الوثائق البارزة في هذا الصدد قائمة محددات CSS المدعومة لمحرك محدد CSS الخاص بنا، nwsapi
.
بالإضافة إلى الميزات التي لم نصل إليها بعد، هناك ميزتان رئيسيتان تقعان حاليًا خارج نطاق jsdom. هذه هي:
التنقل : القدرة على تغيير الكائن العام، وجميع الكائنات الأخرى، عند النقر فوق رابط أو تعيين location.href
أو ما شابه.
Layout : القدرة على حساب مكان تخطيط العناصر بشكل مرئي نتيجة لـ CSS، مما يؤثر على أساليب مثل getBoundingClientRects()
أو خصائص مثل offsetTop
.
لدى jsdom حاليًا سلوكيات وهمية لبعض جوانب هذه الميزات، مثل إرسال " "jsdomError"
إلى وحدة التحكم الافتراضية للتنقل، أو إرجاع الأصفار للعديد من الخصائص المتعلقة بالتخطيط. في كثير من الأحيان يمكنك التغلب على هذه القيود في التعليمات البرمجية الخاصة بك، على سبيل المثال عن طريق إنشاء مثيلات JSDOM
جديدة لكل صفحة "تنتقل" إليها أثناء الزحف، أو استخدام Object.defineProperty()
لتغيير ما ترجعه الأساليب والأساليب المختلفة المتعلقة بالتخطيط.
لاحظ أن الأدوات الأخرى الموجودة في نفس المساحة، مثل PhantomJS، تدعم هذه الميزات. على الويكي، لدينا كتابة أكثر اكتمالًا حول jsdom مقابل PhantomJS.
jsdom هو مشروع مجتمعي يديره فريق من المتطوعين. يمكنك دعم jsdom عن طريق:
الحصول على دعم احترافي لـ jsdom كجزء من اشتراك Tidelift. يساعد Tidelift في جعل المصادر المفتوحة مستدامة بالنسبة لنا مع منح الفرق ضمانات للصيانة والترخيص والأمان.
المساهمة بشكل مباشر في المشروع.
إذا كنت بحاجة إلى مساعدة بخصوص jsdom، فلا تتردد في استخدام أي من الأماكن التالية:
القائمة البريدية (الأفضل للأسئلة "كيف أفعل")
أداة تعقب المشكلات (الأفضل لتقارير الأخطاء)
غرفة الماتريكس: #jsdom:matrix.org