jsdom — это реализация многих веб-стандартов на чистом JavaScript, в частности стандартов WHATWG DOM и HTML, для использования с Node.js. В общем, цель проекта — эмулировать достаточную часть веб-браузера, чтобы его можно было использовать для тестирования и очистки реальных веб-приложений.
Для последних версий jsdom требуется Node.js v18 или новее. (Версии jsdom ниже v23 по-прежнему работают с предыдущими версиями Node.js, но не поддерживаются.)
const jsdom = require("jsdom");const {JSDOM} = jsdom;
Чтобы использовать jsdom, вы в первую очередь будете использовать конструктор JSDOM
, который представляет собой именованный экспорт основного модуля jsdom. Передайте конструктору строку. Вы получите объект JSDOM
, который имеет ряд полезных свойств, в частности window
:
const dom = new JSDOM(`<!DOCTYPE html><p>Привет, мир</p>`);console.log(dom.window.document.querySelector("p").textContent); // "Привет, мир"
(Обратите внимание, что jsdom будет анализировать передаваемый вами HTML-код точно так же, как это делает браузер, включая подразумеваемые теги <html>
, <head>
и <body>
.)
Полученный объект является экземпляром класса JSDOM
, который помимо window
содержит ряд полезных свойств и методов. В общем, его можно использовать для воздействия на jsdom «извне», делая вещи, которые невозможны с помощью обычных API-интерфейсов DOM. В простых случаях, когда вам не нужны какие-либо из этих функций, мы рекомендуем такой шаблон кодирования, как
const { window } = new JSDOM(`...`);// или Evenconst { document } = (new JSDOM(`...`)).window;
Полная документация обо всем, что вы можете делать с классом JSDOM
находится ниже, в разделе «API объектов JSDOM
».
Конструктор JSDOM
принимает второй параметр, который можно использовать для настройки вашего jsdom следующими способами.
const dom = новый JSDOM(``, { URL: "https://example.org/", реферер: «https://example.com/», ContentType: "текст/html", includeNodeLocations: правда, StorageQuota: 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. Эти сценарии могут изменять содержимое страницы и получать доступ ко всем API-интерфейсам веб-платформы, реализованным jsdom.
Однако это также очень опасно при работе с ненадежным контентом. Песочница jsdom не является надежной, и код, выполняющийся внутри <script>
DOM, может, если приложить достаточно усилий, получить доступ к среде 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"). дети.длина); // 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 не имеет возможности отображать визуальный контент и по умолчанию работает как безгласный браузер. Он предоставляет веб-страницам подсказки через API, такие как 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(метка времени > 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
, или использовать один из удобных способов. API, которые делают это автоматически.
Чтобы более полно настроить поведение загрузки ресурсов jsdom, вы можете передать экземпляр класса ResourceLoader
в качестве значения параметра resources
:
const resourcesLoader = новый jsdom.ResourceLoader({ прокси: "http://127.0.0.1:9001", строгийSSL: ложь, userAgent: "Mellblomenator/9000",});const dom = new JSDOM(``, { resources: resourcesLoader });
Три параметра конструктора 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-specific-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. Мы называем управляемую пользователем консоль «виртуальной консолью», чтобы отличить ее от API console
Node.js и от API-интерфейса внутри страницы window.console
.
По умолчанию конструктор JSDOM
возвращает экземпляр с виртуальной консолью, которая перенаправляет весь вывод на консоль Node.js. Чтобы создать свою собственную виртуальную консоль и передать ее в jsdom, вы можете переопределить это значение по умолчанию, выполнив
const virtualConsole = новый jsdom.VirtualConsole();const dom = новый JSDOM(``, { virtualConsole });
Подобный код создаст виртуальную консоль без поведения. Вы можете задать ему поведение, добавив прослушиватели событий для всех возможных методов консоли:
virtualConsole.on("ошибка", () => { ... });virtualConsole.on("предупреждать", () => { ... });virtualConsole.on("info", () => { ... });virtualConsole.on("dir", () => { ... });// ... и т. д. См. https://console.spec.whatwg.org/#logging
(Обратите внимание, что, вероятно, лучше настроить эти прослушиватели событий перед вызовом new JSDOM()
, поскольку во время анализа могут возникнуть ошибки или сценарий вызова консоли.)
Если вы просто хотите перенаправить вывод виртуальной консоли на другую консоль, например консоль Node.js по умолчанию, вы можете сделать
virtualConsole.sendTo(консоль);
Существует также специальное событие "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 использует концепцию jar-файла cookie, в котором хранятся файлы cookie HTTP. Файлы cookie, URL-адрес которых находится в том же домене, что и документ, и не помечены как «только HTTP», доступны через API document.cookie
. Кроме того, все файлы cookie в банке файлов cookie будут влиять на выборку подресурсов.
По умолчанию конструктор JSDOM
вернет экземпляр с пустой банкой cookie. Чтобы создать свою собственную банку cookie и передать ее в jsdom, вы можете переопределить это значение по умолчанию, выполнив
const cookieJar = новый jsdom.CookieJar(store, options);const dom = новый JSDOM(``, {cookieJar });
Это особенно полезно, если вы хотите использовать одну и ту же банку cookie для нескольких jsdom или заранее заполнить банку cookie определенными значениями.
Баночки для печенья входят в комплект жесткого печенья. Конструктор jsdom.CookieJar
— это подкласс jar-файла cookie с жесткими файлами cookie, который по умолчанию устанавливает параметр looseMode: true
, поскольку он лучше соответствует поведению браузеров. Если вы хотите самостоятельно использовать утилиты и классы Hard-Cookie, вы можете использовать экспорт модуля jsdom.toughCookie
, чтобы получить доступ к экземпляру модуля Tough-Cookie, упакованному вместе с jsdom.
jsdom позволяет вам вмешаться в создание jsdom очень рано: после создания объектов Window
и Document
, но до того, как будет проанализирован любой HTML для заполнения документа узлами:
const dom = new JSDOM(`<p>Привет</p>`, { beforeParse(window) {window.document.childNodes.length === 0;window.someCoolAPI = () => { /* ... */ }; }});
Это особенно полезно, если вы хотите каким-либо образом изменить среду, например добавить прокладки для API веб-платформы, которые 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>привет</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; // неявно созданоconst 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)); // { startOffset: 13, endOffset: 32 }
Обратите внимание, что эта функция работает только в том случае, если вы установили параметр includeNodeLocations
; Расположение узлов по умолчанию отключено из соображений производительности.
vm
Node.js с помощью getInternalVMContext()
Встроенный модуль vm
Node.js — это то, что лежит в основе магии запуска сценариев jsdom. Некоторые расширенные варианты использования, такие как предварительная компиляция сценария и его последующий запуск несколько раз, выгоднее использовать модуль vm
напрямую с Window
созданным jsdom.
Чтобы получить доступ к контекстному глобальному объекту, подходящему для использования с API 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);
Это несколько расширенная функциональность, и мы советуем придерживаться обычных API-интерфейсов DOM (таких как window.eval()
или document.createElement("script")
), если у вас нет очень специфических потребностей.
Обратите внимание, что этот метод выдаст исключение, если экземпляр JSDOM
был создан без установленного runScripts
или если вы используете jsdom в веб-браузере.
reconfigure(settings)
top
свойство window
помечено в спецификации как [Unforgeable]
, что означает, что оно является ненастраиваемым собственным свойством и, следовательно, не может быть переопределено или затенено обычным кодом, выполняющимся внутри 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 повлияет на все API, которые возвращают 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
используется в качестве заголовка запроса HTTP Referer
исходного запроса.
Опция resources
также влияет на первоначальный запрос; это полезно, если вы хотите, например, настроить прокси (см. выше).
URL-адрес результирующего jsdom, тип контента и реферер определяются на основе ответа.
Любые файлы cookie, установленные через заголовки ответа HTTP Set-Cookie
хранятся в банке файлов cookie jsdom. Аналогичным образом, любые файлы cookie, уже находящиеся в поставляемом банке файлов cookie, отправляются как заголовки запроса HTTP Cookie
.
fromFile()
Подобно fromURL()
, jsdom также предоставляет фабричный метод fromFile()
для создания jsdom из имени файла:
JSDOM.fromFile("stuff.html", options).then(dom => { console.log(dom.serialize());});
Возвращенное обещание будет выполнено с экземпляром JSDOM
, если данный файл можно открыть. Как обычно в API 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>Привет</p><p><strong>Привет!</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>
с помощью API холста. Чтобы это работало, вам нужно включить canvas
в качестве зависимости в ваш проект, как аналог jsdom
. Если jsdom сможет найти пакет canvas
, он будет использовать его, но если он отсутствует, то элементы <canvas>
будут вести себя как <div>
. Начиная с jsdom v13, требуется canvas
версии 2.x; версия 1.x больше не поддерживается.
Помимо предоставления строки, конструктор JSDOM
также может предоставлять двоичные данные в форме Node.js Buffer
или стандартного типа двоичных данных JavaScript, такого как ArrayBuffer
, Uint8Array
, DataView
и т. д. Когда это будет сделано, jsdom будет анализировать кодировка из предоставленных байтов, сканирование тегов <meta charset>
так же, как это делает браузер.
Если предоставленный параметр contentType
содержит параметр charset
, эта кодировка будет переопределять прослушиваемую кодировку — если только не присутствует спецификация UTF-8 или UTF-16, в этом случае они имеют приоритет. (Опять же, это похоже на браузер.)
Этот анализ кодировки также применим к JSDOM.fromFile()
и JSDOM.fromURL()
. В последнем случае все заголовки Content-Type
отправленные с ответом, будут иметь приоритет, так же, как и опция contentType
конструктора.
Обратите внимание, что во многих случаях предоставление байтов таким способом может быть лучше, чем предоставление строки. Например, если вы попытаетесь использовать API- buffer.toString("utf-8")
Node.js, Node.js не удалит ведущие спецификации. Если вы затем передадите эту строку jsdom, он интерпретирует ее дословно, оставив спецификацию нетронутой. Но код декодирования двоичных данных jsdom удаляет ведущие спецификации, как и браузер; в таких случаях прямая поставка 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 новые функции и поддерживать его в актуальном состоянии в соответствии с новейшими веб-спецификациями, в нем много недостающих API. Пожалуйста, не стесняйтесь сообщать о проблеме, если чего-то не хватает, но у нас небольшая и занятая команда, поэтому запрос на включение может работать еще лучше.
Некоторые возможности jsdom обеспечиваются нашими зависимостями. Примечательная документация в этом отношении включает список поддерживаемых селекторов CSS для нашего механизма селекторов CSS nwsapi
.
Помимо функций, которые мы еще не рассмотрели, есть две основные функции, которые в настоящее время выходят за рамки jsdom. Это:
Навигация : возможность изменять глобальный объект и все другие объекты при нажатии ссылки или назначении location.href
или аналогичного.
Макет : возможность рассчитать, где элементы будут визуально расположены в результате использования CSS, что влияет на такие методы, как getBoundingClientRects()
или такие свойства, как offsetTop
.
В настоящее время jsdom имеет фиктивное поведение для некоторых аспектов этих функций, таких как отправка «не реализованного» "jsdomError"
на виртуальную консоль для навигации или возврат нулей для многих свойств, связанных с макетом. Часто вы можете обойти эти ограничения в своем коде, например, создавая новые экземпляры JSDOM
для каждой страницы, на которую вы «переходите» во время сканирования, или используя Object.defineProperty()
для изменения того, что возвращают различные геттеры и методы, связанные с макетом.
Обратите внимание, что другие инструменты в той же области, такие как PhantomJS, поддерживают эти функции. В вики есть более полная информация о jsdom и PhantomJS.
jsdom — это проект, управляемый сообществом и поддерживаемый командой волонтеров. Вы можете поддержать jsdom:
Получение профессиональной поддержки jsdom в рамках подписки Tidelift. Tidelift помогает нам сделать открытый исходный код устойчивым, одновременно давая командам гарантии обслуживания, лицензирования и безопасности.
Непосредственный вклад в проект.
Если вам нужна помощь с jsdom, воспользуйтесь любым из следующих мест:
Список рассылки (лучше всего подходит для вопросов «как дела»)
Трекер проблем (лучше всего подходит для отчетов об ошибках)
Комната «Матрица»: #jsdom:matrix.org