jsdom ist eine reine JavaScript-Implementierung vieler Webstandards, insbesondere der WHATWG DOM- und HTML-Standards, zur Verwendung mit Node.js. Im Allgemeinen besteht das Ziel des Projekts darin, eine ausreichende Teilmenge eines Webbrowsers zu emulieren, um zum Testen und Scrapen realer Webanwendungen nützlich zu sein.
Die neuesten Versionen von jsdom erfordern Node.js v18 oder neuer. (Versionen von jsdom unter v23 funktionieren weiterhin mit früheren Node.js-Versionen, werden jedoch nicht unterstützt.)
const jsdom = require("jsdom");const { JSDOM } = jsdom;
Um jsdom zu verwenden, verwenden Sie in erster Linie den JSDOM
Konstruktor, bei dem es sich um einen benannten Export des jsdom-Hauptmoduls handelt. Übergeben Sie dem Konstruktor eine Zeichenfolge. Sie erhalten ein JSDOM
Objekt zurück, das über eine Reihe nützlicher Eigenschaften verfügt, insbesondere window
:
const dom = new JSDOM(`<!DOCTYPE html><p>Hallo Welt</p>`);console.log(dom.window.document.querySelector("p").textContent); // „Hallo Welt“
(Beachten Sie, dass jsdom den von Ihnen übergebenen HTML-Code genau wie ein Browser analysiert, einschließlich der impliziten Tags <html>
, <head>
und <body>
.)
Das resultierende Objekt ist eine Instanz der JSDOM
-Klasse, die neben window
eine Reihe nützlicher Eigenschaften und Methoden enthält. Im Allgemeinen kann es verwendet werden, um von „außen“ auf das jsdom einzuwirken und Dinge zu tun, die mit den normalen DOM-APIs nicht möglich sind. Für einfache Fälle, in denen Sie diese Funktionalität nicht benötigen, empfehlen wir ein Codierungsmuster wie
const { window } = new JSDOM(`...`);// or evenconst { document } = (new JSDOM(`...`)).window;
Die vollständige Dokumentation zu allem, was Sie mit der JSDOM
-Klasse tun können, finden Sie weiter unten im Abschnitt „ JSDOM
Objekt-API“.
Der JSDOM
Konstruktor akzeptiert einen zweiten Parameter, der verwendet werden kann, um Ihr JSDOM auf folgende Weise anzupassen.
const dom = new JSDOM(``, { URL: „https://example.org/“, Referrer: „https://example.com/“, contentType: „text/html“, includeNodeLocations: true, storageQuota: 10000000});
url
legt den Wert fest, der von window.location
, document.URL
und document.documentURI
zurückgegeben wird, und beeinflusst Dinge wie die Auflösung relativer URLs innerhalb des Dokuments sowie die Same-Origin-Einschränkungen und Referrer, die beim Abrufen von Unterressourcen verwendet werden. Der Standardwert ist "about:blank"
.
referrer
wirkt sich nur auf den aus document.referrer
gelesenen Wert aus. Der Standardwert ist „kein Referrer“ (was als leere Zeichenfolge angezeigt wird).
contentType
beeinflusst den aus document.contentType
gelesenen Wert sowie die Art und Weise, wie das Dokument analysiert wird: als HTML oder als XML. Werte, die kein HTML-MIME-Typ oder XML-MIME-Typ sind, werden ausgelöst. Der Standardwert ist "text/html"
. Wenn ein charset
vorhanden ist, kann er sich auf die Binärdatenverarbeitung auswirken.
includeNodeLocations
behält die vom HTML-Parser erzeugten Standortinformationen bei, sodass Sie sie mit der Methode nodeLocation()
(unten beschrieben) abrufen können. Es stellt außerdem sicher, dass die in Ausnahme-Stack-Traces für Code, der innerhalb von <script>
-Elementen ausgeführt wird, gemeldeten Zeilennummern korrekt sind. Der Standardwert ist false
, um die beste Leistung zu erzielen, und kann nicht mit einem XML-Inhaltstyp verwendet werden, da unser XML-Parser keine Standortinformationen unterstützt.
storageQuota
ist die maximale Größe in Codeeinheiten für die separaten Speicherbereiche, die von localStorage
und sessionStorage
verwendet werden. Versuche, Daten zu speichern, die diesen Grenzwert überschreiten, führen zum Auslösen einer DOMException
. Standardmäßig ist es auf 5.000.000 Codeeinheiten pro Ursprung eingestellt, wie von der HTML-Spezifikation inspiriert.
Beachten Sie, dass sowohl url
als auch referrer
kanonisiert werden, bevor sie verwendet werden. Wenn Sie also beispielsweise "https:example.com"
übergeben, interpretiert jsdom dies so, als hätten Sie "https://example.com/"
angegeben. Wenn Sie eine nicht analysierbare URL übergeben, wird der Aufruf ausgelöst. (URLs werden gemäß dem URL-Standard analysiert und serialisiert.)
Die leistungsstärkste Fähigkeit von jsdom besteht darin, dass es Skripte innerhalb des jsdom ausführen kann. Diese Skripte können den Inhalt der Seite ändern und auf alle Webplattform-APIs zugreifen, die jsdom implementiert.
Allerdings ist dies auch im Umgang mit nicht vertrauenswürdigen Inhalten äußerst gefährlich. Die jsdom-Sandbox ist nicht narrensicher, und Code, der in den <script>
s des DOM ausgeführt wird, kann, wenn er sich genug anstrengt, Zugriff auf die Node.js-Umgebung und damit auf Ihren Computer erhalten. Daher ist die Möglichkeit, im HTML eingebettete Skripte auszuführen, standardmäßig deaktiviert:
const dom = new JSDOM(`<body> <div id="content"></div> <script>document.getElementById("content").append(document.createElement("hr"));</script> </body>`);// Das Skript wird standardmäßig nicht ausgeführt:console.log(dom.window.document.getElementById("content").children.length); // 0
Um die Ausführung von Skripten innerhalb der Seite zu ermöglichen, können Sie die Option runScripts: "dangerously"
verwenden:
const dom = new JSDOM(`<body> <div id="content"></div> <script>document.getElementById("content").append(document.createElement("hr"));</script> </body>`, { runScripts: "dangerously" });// Das Skript wird ausgeführt und ändert das DOM:console.log(dom.window.document.getElementById("content").children.length); // 1
Wir betonen erneut, dass Sie dies nur verwenden sollten, wenn Sie JSDOM-Code eingeben, von dem Sie wissen, dass er sicher ist. Wenn Sie es mit beliebigem, vom Benutzer bereitgestelltem Code oder Code aus dem Internet verwenden, führen Sie effektiv nicht vertrauenswürdigen Node.js-Code aus und Ihr Computer könnte gefährdet sein.
Wenn Sie externe Skripte ausführen möchten, die über <script src="">
eingebunden sind, müssen Sie auch sicherstellen, dass diese geladen werden. Fügen Sie dazu die Option resources: "usable"
wie unten beschrieben hinzu. (Aus den dort genannten Gründen möchten Sie wahrscheinlich auch die url
Option festlegen.)
Event-Handler-Attribute wie <div onclick="">
unterliegen ebenfalls dieser Einstellung. Sie funktionieren nur, wenn runScripts
auf "dangerously"
gesetzt ist. (Event-Handler- Eigenschaften wie div.onclick = ...
funktionieren jedoch unabhängig von runScripts
.)
Wenn Sie einfach versuchen, das Skript „von außen“ auszuführen, anstatt <script>
-Elemente und Event-Handler-Attribute „von innen“ ausführen zu lassen, können Sie die Option runScripts: "outside-only"
verwenden, die neue Kopien von ermöglicht alle von der JavaScript-Spezifikation bereitgestellten Globals, die auf window
installiert werden sollen. Dazu gehören Dinge wie window.Array
, window.Promise
usw. Dazu gehört insbesondere auch window.eval
, das die Ausführung von Skripten ermöglicht, jedoch mit dem jsdom- window
als globalem:
const dom = new JSDOM(`<body> <div id="content"></div> <script>document.getElementById("content").append(document.createElement("hr"));</script> </body>`, { runScripts: "outside-only" });// ein Skript außerhalb von ausführen JSDOM:dom.window.eval('document.getElementById("content").append(document.createElement("p"));');console.log(dom.window.document.getElementById("content"). Kinder.Länge); // 1console.log(dom.window.document.getElementsByTagName("hr").length); // 0console.log(dom.window.document.getElementsByTagName("p").length); // 1
Dies ist aus Leistungsgründen standardmäßig deaktiviert, kann aber problemlos aktiviert werden.
Beachten Sie, dass in der Standardkonfiguration ohne die Einstellung von runScripts
die Werte von window.Array
, window.eval
usw. mit denen übereinstimmen, die von der äußeren Node.js-Umgebung bereitgestellt werden. Das heißt, window.eval === eval
bleibt bestehen, sodass window.eval
Skripte nicht auf sinnvolle Weise ausführt.
Wir raten dringend davon ab, zu versuchen, „Skripte auszuführen“, indem die globalen Umgebungen von jsdom und Node zusammengeführt werden (z. B. indem global.window = dom.window
ausgeführt wird) und dann Skripte oder Testcode innerhalb der globalen Umgebung von Node ausgeführt werden. Stattdessen sollten Sie jsdom wie einen Browser behandeln und alle Skripte und Tests, die Zugriff auf ein DOM benötigen, innerhalb der jsdom-Umgebung ausführen, indem Sie window.eval
oder runScripts: "dangerously"
verwenden. Dies kann beispielsweise die Erstellung eines Browserify-Bundles erfordern, das als <script>
-Element ausgeführt wird – genau wie Sie es in einem Browser tun würden.
Schließlich können Sie für fortgeschrittene Anwendungsfälle die unten dokumentierte Methode dom.getInternalVMContext()
verwenden.
jsdom ist nicht in der Lage, visuelle Inhalte darzustellen und verhält sich standardmäßig wie ein Headless-Browser. Über APIs wie document.hidden
werden Webseiten darauf hingewiesen, dass deren Inhalt nicht sichtbar ist.
Wenn die Option pretendToBeVisual
auf true
gesetzt ist, tut jsdom so, als würde es Inhalte rendern und anzeigen. Dies geschieht durch:
Ändern Sie document.hidden
so, dass false
statt true
zurückgegeben wird
Ändern von document.visibilityState
, um "visible"
anstelle von "prerender"
zurückzugeben.
Aktivieren der Methoden window.requestAnimationFrame()
und window.cancelAnimationFrame()
, die sonst nicht existieren
const window = (new JSDOM(``, { PretendToBeVisual: true })).window;window.requestAnimationFrame(timestamp => { console.log(Zeitstempel > 0);});
Beachten Sie, dass jsdom immer noch kein Layout oder Rendering durchführt. Es geht also nur darum, vorzutäuschen , visuell zu sein, und nicht darum, die Teile der Plattform zu implementieren, die ein echter, visueller Webbrowser implementieren würde.
Standardmäßig lädt jsdom keine Unterressourcen wie Skripte, Stylesheets, Bilder oder Iframes. Wenn Sie möchten, dass jsdom solche Ressourcen lädt, können Sie die Option resources: "usable"
übergeben, die alle verwendbaren Ressourcen lädt. Das sind:
Frames und Iframes über <frame>
und <iframe>
Stylesheets, über <link rel="stylesheet">
Skripte, über <script>
, aber nur, wenn auch runScripts: "dangerously"
gesetzt ist
Bilder, über <img>
, aber nur, wenn auch das canvas
NPM-Paket installiert ist (siehe „Canvas-Unterstützung“ unten)
Denken Sie beim Versuch, Ressourcen zu laden, daran, dass der Standardwert für die url
Option "about:blank"
ist, was bedeutet, dass alle über relative URLs eingebundenen Ressourcen nicht geladen werden können. (Das Ergebnis des Versuchs, die URL /something
mit der URL about:blank
zu analysieren, ist ein Fehler.) Daher möchten Sie in diesen Fällen wahrscheinlich einen nicht standardmäßigen Wert für die url
Option festlegen oder eine der praktischen Möglichkeiten nutzen APIs, die dies automatisch tun.
Um das Ressourcenladeverhalten von jsdom umfassender anzupassen, können Sie eine Instanz der ResourceLoader
-Klasse als resources
übergeben:
const resourcesLoader = new jsdom.ResourceLoader({ Proxy: „http://127.0.0.1:9001“, strictSSL: false, userAgent: "Mellblomenator/9000",});const dom = new JSDOM(``, { resources: resourcesLoader });
Die drei Optionen für den ResourceLoader
-Konstruktor sind:
proxy
ist die Adresse eines zu verwendenden HTTP-Proxys.
strictSSL
kann auf „false“ gesetzt werden, um die Anforderung, dass SSL-Zertifikate gültig sein müssen, zu deaktivieren.
userAgent
wirkt sich auf den gesendeten User-Agent
-Header und damit auf den resultierenden Wert für navigator.userAgent
aus. Der Standardwert ist `Mozilla/5.0 (${process.platform || "unknown OS"}) AppleWebKit/537.36 (KHTML, like Gecko) jsdom/${jsdomVersion}`
.
Sie können das Abrufen von Ressourcen weiter anpassen, indem Sie ResourceLoader
in eine Unterklasse umwandeln und die fetch()
Methode überschreiben. Hier ist beispielsweise eine Version, die die für eine bestimmte URL bereitgestellte Antwort überschreibt:
Klasse CustomResourceLoader erweitert jsdom.ResourceLoader { fetch(url, options) {// Überschreiben Sie den Inhalt dieses Skripts, um etwas Ungewöhnliches zu tun.if (url === "https://example.com/some-special-script.js") { return Promise.resolve( Buffer.from("window.someGlobal = 5;"));}return super.fetch(url, options); }}
jsdom ruft fetch()
-Methode Ihres benutzerdefinierten Ressourcenladers auf, wenn es auf eine „verwendbare“ Ressource trifft, wie im obigen Abschnitt beschrieben. Die Methode benötigt eine URL-Zeichenfolge sowie einige Optionen, die Sie beim Aufruf von super.fetch()
unverändert übergeben sollten. Es muss ein Versprechen für ein Node.js- Buffer
zurückgeben oder null
zurückgeben, wenn die Ressource absichtlich nicht geladen werden soll. Im Allgemeinen möchten Sie in den meisten Fällen an super.fetch()
delegieren, wie gezeigt.
Eine der Optionen, die Sie in fetch()
erhalten, ist das Element (falls zutreffend), das eine Ressource abruft.
Klasse CustomResourceLoader erweitert jsdom.ResourceLoader { fetch(url, options) {if (options.element) { console.log(`Element ${options.element.localName} fordert die URL ${url}`);}return super.fetch(url, options); }}
Wie Webbrowser verfügt auch jsdom über das Konzept einer „Konsole“. Dadurch werden sowohl direkt von der Seite gesendete Informationen über Skripte, die im Dokument ausgeführt werden, als auch Informationen aus der jsdom-Implementierung selbst aufgezeichnet. Wir nennen die vom Benutzer steuerbare Konsole eine „virtuelle Konsole“, um sie von der Node.js console
API und der window.console
API innerhalb der Seite zu unterscheiden.
Standardmäßig gibt der JSDOM
-Konstruktor eine Instanz mit einer virtuellen Konsole zurück, die ihre gesamte Ausgabe an die Node.js-Konsole weiterleitet. Um Ihre eigene virtuelle Konsole zu erstellen und an jsdom zu übergeben, können Sie diese Standardeinstellung auf diese Weise überschreiben
const virtualConsole = new jsdom.VirtualConsole();const dom = new JSDOM(``, { virtualConsole });
Code wie dieser erstellt eine virtuelle Konsole ohne Verhalten. Sie können ihm Verhalten verleihen, indem Sie Ereignis-Listener für alle möglichen Konsolenmethoden hinzufügen:
virtualConsole.on("error", () => { ... });virtualConsole.on("warn", () => { ... });virtualConsole.on("info", () => { ... });virtualConsole.on("dir", () => { ... });// ... usw. Siehe https://console.spec.whatwg.org/#logging
(Beachten Sie, dass es wahrscheinlich am besten ist, diese Ereignis-Listener vor dem Aufruf von new JSDOM()
einzurichten, da beim Parsen Fehler oder konsolenaufrufende Skripte auftreten können.)
Wenn Sie die Ausgabe der virtuellen Konsole einfach auf eine andere Konsole umleiten möchten, beispielsweise auf die Standardkonsole von Node.js, können Sie dies tun
virtualConsole.sendTo(console);
Es gibt auch ein spezielles Ereignis, "jsdomError"
, das mit Fehlerobjekten ausgelöst wird, um Fehler von jsdom selbst zu melden. Dies ähnelt der häufigen Anzeige von Fehlermeldungen in Webbrowser-Konsolen, auch wenn sie nicht von console.error
initiiert werden. Bisher werden folgende Fehler auf diese Weise ausgegeben:
Fehler beim Laden oder Parsen von Unterressourcen (Skripte, Stylesheets, Frames und Iframes)
Skriptausführungsfehler, die nicht von einem Window- onerror
-Ereignishandler behandelt werden, der true
zurückgibt oder event.preventDefault()
aufruft
Nicht implementierte Fehler, die aus Aufrufen von Methoden wie window.alert
resultieren, die jsdom nicht implementiert, aber aus Gründen der Webkompatibilität trotzdem installiert
Wenn Sie sendTo(c)
verwenden, um Fehler an c
zu senden, wird standardmäßig c.error(errorStack[, errorDetail])
mit Informationen von "jsdomError"
-Ereignissen aufgerufen. Wenn Sie lieber eine strikte Eins-zu-Eins-Zuordnung von Ereignissen zu Methodenaufrufen beibehalten und möglicherweise selbst mit "jsdomError"
-Fehlern umgehen möchten, können Sie dies tun
virtualConsole.sendTo(c, { omitJSDOMErrors: true });
Wie Webbrowser verfügt jsdom über das Konzept einer Cookie-Dose, in der HTTP-Cookies gespeichert werden. Auf Cookies, deren URL sich auf derselben Domäne wie das Dokument befindet und die nicht als „Nur HTTP“ gekennzeichnet sind, kann über die document.cookie
-API zugegriffen werden. Darüber hinaus wirken sich alle Cookies in der Keksdose auf das Abrufen von Unterressourcen aus.
Standardmäßig gibt der JSDOM
Konstruktor eine Instanz mit einem leeren Cookie-Glas zurück. Um Ihr eigenes Cookie-Glas zu erstellen und es an jsdom zu übergeben, können Sie diese Standardeinstellung auf diese Weise überschreiben
const cookieJar = new jsdom.CookieJar(store, options);const dom = new JSDOM(``, { cookieJar });
Dies ist vor allem dann nützlich, wenn Sie dieselbe Keksdose für mehrere Jsdoms freigeben oder die Keksdose vorab mit bestimmten Werten füllen möchten.
Keksdosen sind im Tough-Cookie-Paket enthalten. Der jsdom.CookieJar
-Konstruktor ist eine Unterklasse der Tough-Cookie-Cookie-Jar-Datei, die standardmäßig die Option looseMode: true
festlegt, da diese besser zum Verhalten von Browsern passt. Wenn Sie die Dienstprogramme und Klassen von Tough-Cookie selbst verwenden möchten, können Sie den Modulexport jsdom.toughCookie
verwenden, um Zugriff auf die mit jsdom gepackte Tough-Cookie-Modulinstanz zu erhalten.
Mit jsdom können Sie sehr früh in die Erstellung eines jsdom eingreifen: nach der Erstellung der Window
und Document
Objekte, aber bevor HTML analysiert wird, um das Dokument mit Knoten zu füllen:
const dom = new JSDOM(`<p>Hallo</p>`, { beforeParse(window) {window.document.childNodes.length === 0;window.someCoolAPI = () => { /* ... */ }; }});
Dies ist besonders nützlich, wenn Sie die Umgebung auf irgendeine Weise ändern möchten, beispielsweise durch das Hinzufügen von Shims für Webplattform-APIs, die jsdom nicht unterstützt.
JSDOM
Objekt-API Sobald Sie ein JSDOM
-Objekt erstellt haben, verfügt es über die folgenden nützlichen Funktionen:
Das window
ruft das für Sie erstellte Window
Objekt ab.
Die Eigenschaften virtualConsole
und cookieJar
spiegeln die von Ihnen übergebenen Optionen oder die für Sie erstellten Standardeinstellungen wider, wenn für diese Optionen nichts übergeben wurde.
serialize()
Die Methode serialize()
gibt die HTML-Serialisierung des Dokuments zurück, einschließlich des Dokumenttyps:
const dom = new JSDOM(`<!DOCTYPE html>hello`);dom.serialize() === "<!DOCTYPE html><html><head></head><body>hello</body></ html>";// Kontrast zu:dom.window.document.documentElement.outerHTML === "<html><head></head><body>hello</body></html>";
nodeLocation(node)
ermitteln Die Methode nodeLocation()
findet heraus, wo sich ein DOM-Knoten im Quelldokument befindet, und gibt die Parse5-Standortinformationen für den Knoten zurück:
const dom = neues JSDOM( `<p>Hallo <img src="foo.jpg"> </p>`, { includeNodeLocations: true });const document = dom.window.document;const bodyEl = document.body; // implizit erstelltconst pEl = document.querySelector("p");const textNode = pEl.firstChild;const imgEl = document.querySelector("img");console.log(dom.nodeLocation(bodyEl)); // null; es ist nicht in der 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 }
Beachten Sie, dass diese Funktion nur funktioniert, wenn Sie die Option includeNodeLocations
festgelegt haben. Knotenstandorte sind aus Leistungsgründen standardmäßig deaktiviert.
vm
Modul mithilfe von getInternalVMContext()
Das integrierte vm
Modul von Node.js ist die Grundlage für die Magie der Skriptausführung von jsdom. Einige fortgeschrittene Anwendungsfälle, wie das Vorkompilieren eines Skripts und dessen anschließende mehrmalige Ausführung, profitieren von der direkten Verwendung des vm
Moduls mit einem von jsdom erstellten Window
.
Um Zugriff auf das kontextifizierte globale Objekt zu erhalten, das für die Verwendung mit den vm
-APIs geeignet ist, können Sie die Methode getInternalVMContext()
verwenden:
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);
Hierbei handelt es sich um eine etwas erweiterte Funktionalität. Wir empfehlen, sich an normale DOM-APIs (wie window.eval()
oder document.createElement("script")
) zu halten, es sei denn, Sie haben sehr spezielle Anforderungen.
Beachten Sie, dass diese Methode eine Ausnahme auslöst, wenn die JSDOM
Instanz ohne festgelegte runScripts
erstellt wurde oder wenn Sie jsdom in einem Webbrowser verwenden.
reconfigure(settings)
Die top
Eigenschaft im window
ist in der Spezifikation als [Unforgeable]
gekennzeichnet, was bedeutet, dass es sich um eine nicht konfigurierbare eigene Eigenschaft handelt und daher nicht durch normalen Code überschrieben oder überschattet werden kann, der innerhalb des jsdom ausgeführt wird, selbst wenn Object.defineProperty
verwendet wird.
Ebenso übernimmt jsdom derzeit keine Navigation (z. B. das Festlegen von window.location.href = "https://example.com/"
); Dies führt dazu, dass die virtuelle Konsole einen "jsdomError"
ausgibt, der erklärt, dass diese Funktion nicht implementiert ist, und es ändert sich nichts: Es wird kein neues Window
oder Document
geben und das location
des vorhandenen window
wird weiterhin dasselbe haben Immobilienwerte.
Wenn Sie jedoch von außerhalb des Fensters agieren, z. B. in einem Test-Framework, das jsdoms erstellt, können Sie eines oder beide davon mit der speziellen reconfigure()
-Methode überschreiben:
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/“;
Beachten Sie, dass sich eine Änderung der jsdom-URL auf alle APIs auswirkt, die die aktuelle Dokument-URL zurückgeben, z. B. window.location
, document.URL
und document.documentURI
, sowie auf die Auflösung relativer URLs innerhalb des Dokuments und die Prüfungen auf denselben Ursprung und Referrer, der beim Abrufen von Unterressourcen verwendet wird. Es wird jedoch keine Navigation zum Inhalt dieser URL durchgeführt; Der Inhalt des DOM bleibt unverändert und es werden keine neuen Instanzen von Window
, Document
usw. erstellt.
fromURL()
Zusätzlich zum JSDOM
Konstruktor selbst bietet jsdom eine Promise-Returning-Factory-Methode zum Erstellen eines jsdom aus einer URL:
JSDOM.fromURL("https://example.com/", Optionen).then(dom => { console.log(dom.serialize());});
Das zurückgegebene Versprechen wird mit einer JSDOM
Instanz erfüllt, wenn die URL gültig und die Anfrage erfolgreich ist. Alle Weiterleitungen werden bis zu ihrem endgültigen Ziel verfolgt.
Die für fromURL()
bereitgestellten Optionen ähneln denen des JSDOM
Konstruktors, mit den folgenden zusätzlichen Einschränkungen und Konsequenzen:
Die Optionen url
und contentType
können nicht bereitgestellt werden.
Die referrer
Option wird als HTTP- Referer
-Anfrageheader der ersten Anfrage verwendet.
Die Option resources
wirkt sich auch auf die Erstanfrage aus; Dies ist nützlich, wenn Sie beispielsweise einen Proxy konfigurieren möchten (siehe oben).
Die URL, der Inhaltstyp und der Referrer des resultierenden jsdom werden aus der Antwort ermittelt.
Alle über HTTP- Set-Cookie
-Antwortheader gesetzten Cookies werden im Cookie-JAR des jsdom gespeichert. Ebenso werden alle Cookies, die sich bereits in einer bereitgestellten Cookie-Dose befinden, als HTTP- Cookie
Anforderungsheader gesendet.
fromFile()
Ähnlich wie fromURL()
bietet jsdom auch eine fromFile()
-Factory-Methode zum Erstellen eines jsdom aus einem Dateinamen:
JSDOM.fromFile("stuff.html", Optionen).then(dom => { console.log(dom.serialize());});
Das zurückgegebene Versprechen wird mit einer JSDOM
Instanz erfüllt, wenn die angegebene Datei geöffnet werden kann. Wie in Node.js-APIs üblich, wird der Dateiname relativ zum aktuellen Arbeitsverzeichnis angegeben.
Die für fromFile()
bereitgestellten Optionen ähneln denen des JSDOM
Konstruktors, mit den folgenden zusätzlichen Standardwerten:
Die url
-Option verwendet standardmäßig eine Datei-URL, die dem angegebenen Dateinamen entspricht, und nicht "about:blank"
.
Die Option contentType
lautet standardmäßig "application/xhtml+xml"
wenn der angegebene Dateiname auf .xht
, .xhtml
oder .xml
endet. andernfalls wird weiterhin standardmäßig "text/html"
verwendet.
fragment()
Im einfachsten Fall benötigen Sie möglicherweise keine ganze JSDOM
Instanz mit der gesamten zugehörigen Leistung. Möglicherweise benötigen Sie nicht einmal ein Window
oder Document
! Stattdessen müssen Sie nur etwas HTML analysieren und ein DOM-Objekt erhalten, das Sie bearbeiten können. Dafür haben wir fragment()
, das aus einem gegebenen String ein DocumentFragment
erstellt:
const frag = JSDOM.fragment(`<p>Hallo</p><p><strong>Hallo!</strong>`);frag.childNodes.length === 2;frag.querySelector("strong"). textContent === "Hallo!";// usw.
Hier ist frag
eine DocumentFragment
-Instanz, deren Inhalt durch Parsen der bereitgestellten Zeichenfolge erstellt wird. Das Parsen erfolgt mithilfe eines <template>
-Elements, sodass Sie dort jedes beliebige Element einschließen können (einschließlich solcher mit seltsamen Parsing-Regeln wie <td>
). Es ist außerdem wichtig zu beachten, dass dem resultierenden DocumentFragment
kein Browsing-Kontext zugeordnet ist: Das heißt, ownerDocument
des Elements hat eine Null- defaultView
-Eigenschaft, Ressourcen werden nicht geladen usw.
Alle Aufrufe der fragment()
Factory führen zu DocumentFragment
s, die denselben Vorlageneigentümer Document
haben. Dies ermöglicht viele Aufrufe von fragment()
ohne zusätzlichen Overhead. Es bedeutet aber auch, dass Aufrufe von fragment()
nicht mit irgendwelchen Optionen angepasst werden können.
Beachten Sie, dass die Serialisierung mit DocumentFragment
s nicht so einfach ist wie mit vollständigen JSDOM
Objekten. Wenn Sie Ihr DOM serialisieren müssen, sollten Sie wahrscheinlich den JSDOM
Konstruktor direkter verwenden. Aber für den Sonderfall eines Fragments, das ein einzelnes Element enthält, ist es mit normalen Mitteln ziemlich einfach:
const frag = JSDOM.fragment(`<p>Hallo</p>`);console.log(frag.firstChild.outerHTML); // protokolliert „<p>Hallo</p>“
jsdom bietet Unterstützung für die Verwendung des canvas
-Pakets, um alle <canvas>
-Elemente mit der Canvas-API zu erweitern. Damit dies funktioniert, müssen Sie canvas
als Abhängigkeit in Ihr Projekt einbinden, als Peer von jsdom
. Wenn jsdom das canvas
-Paket finden kann, wird es es verwenden. Wenn es jedoch nicht vorhanden ist, verhalten sich <canvas>
-Elemente wie <div>
s. Seit jsdom v13 ist Version 2.x von canvas
erforderlich; Version 1.x wird nicht mehr unterstützt.
Zusätzlich zur Bereitstellung einer Zeichenfolge kann der JSDOM
Konstruktor auch Binärdaten in Form eines Node.js Buffer
oder eines standardmäßigen JavaScript-Binärdatentyps wie ArrayBuffer
, Uint8Array
, DataView
usw. bereitstellen. Wenn dies erledigt ist, führt jsdom einen Sniff durch die Codierung anhand der bereitgestellten Bytes und sucht nach <meta charset>
-Tags, genau wie ein Browser dies tut.
Wenn die bereitgestellte contentType
-Option einen charset
enthält, überschreibt diese Codierung die Sniff-Codierung – es sei denn, es ist eine UTF-8- oder UTF-16-BOM vorhanden. In diesem Fall haben diese Vorrang. (Auch dies ist wie ein Browser.)
Dieses Codierungs-Sniffing gilt auch für JSDOM.fromFile()
und JSDOM.fromURL()
. Im letzteren Fall haben alle mit der Antwort gesendeten Content-Type
Header Vorrang, genau wie die contentType
Option des Konstruktors.
Beachten Sie, dass die Bereitstellung von Bytes auf diese Weise in vielen Fällen besser sein kann als die Bereitstellung einer Zeichenfolge. Wenn Sie beispielsweise versuchen, buffer.toString("utf-8")
-API von Node.js zu verwenden, entfernt Node.js keine führenden Stücklisten. Wenn Sie diese Zeichenfolge dann an jsdom übergeben, wird sie wörtlich interpretiert, sodass die Stückliste intakt bleibt. Aber der binäre Datendekodierungscode von jsdom entfernt führende Stücklisten, genau wie ein Browser; In solchen Fällen führt die direkte Bereitstellung buffer
zum gewünschten Ergebnis.
Timer im jsdom (festgelegt durch window.setTimeout()
oder window.setInterval()
) führen per Definition künftig Code im Kontext des Fensters aus. Da es in Zukunft keine Möglichkeit mehr gibt, Code auszuführen, ohne den Prozess am Leben zu halten, halten ausstehende jsdom-Timer Ihren Node.js-Prozess am Leben. Da es keine Möglichkeit gibt, Code im Kontext eines Objekts auszuführen, ohne das Objekt am Leben zu halten, verhindern ausstehende jsdom-Timer ebenfalls die Garbage Collection des Fensters, für das sie geplant sind.
Wenn Sie sicherstellen möchten, dass ein jsdom-Fenster geschlossen wird, verwenden Sie window.close()
, wodurch alle laufenden Timer beendet werden (und auch alle Ereignis-Listener im Fenster und Dokument entfernt werden).
In Node.js können Sie Programme mit Chrome DevTools debuggen. Informationen zu den ersten Schritten finden Sie in der offiziellen Dokumentation.
Standardmäßig werden jsdom-Elemente in der Konsole als einfache alte JS-Objekte formatiert. Um das Debuggen zu vereinfachen, können Sie jsdom-devtools-formatter verwenden, mit dem Sie sie wie echte DOM-Elemente untersuchen können.
Bei der Verwendung von jsdom haben Benutzer häufig Probleme mit dem asynchronen Laden von Skripten. Viele Seiten laden Skripte asynchron, aber es gibt keine Möglichkeit zu sagen, wann sie damit fertig sind und wann es daher ein guter Zeitpunkt ist, Ihren Code auszuführen und die resultierende DOM-Struktur zu überprüfen. Dies ist eine grundlegende Einschränkung; Wir können nicht vorhersagen, was Skripte auf der Webseite tun werden, und können Ihnen daher nicht sagen, wann das Laden weiterer Skripte abgeschlossen ist.
Dies kann auf verschiedene Arten umgangen werden. Wenn Sie die betreffende Seite steuern, ist es am besten, die vom Skript-Ladeprogramm bereitgestellten Mechanismen zu verwenden, um zu erkennen, wann der Ladevorgang abgeschlossen ist. Wenn Sie beispielsweise einen Modullader wie RequireJS verwenden, könnte der Code so aussehen:
// Auf der Node.js-Seite:const window = (new JSDOM(...)).window;window.onModulesLoaded = () => { console.log("ready to roll!");};
<!-- Innerhalb des HTML, das Sie jsdom bereitstellen --><script>requirejs(["entry-module"], () => { window.onModulesLoaded();});</script>
Wenn Sie die Seite nicht kontrollieren, können Sie Problemumgehungen ausprobieren, z. B. eine Abfrage, ob ein bestimmtes Element vorhanden ist.
Weitere Einzelheiten finden Sie in der Diskussion in #640, insbesondere im aufschlussreichen Kommentar von @matthewkastor.
Obwohl es uns Spaß macht, neue Funktionen zu jsdom hinzuzufügen und es mit den neuesten Web-Spezifikationen auf dem neuesten Stand zu halten, fehlen viele APIs. Wenn etwas fehlt, können Sie jederzeit eine Problemmeldung einreichen. Da wir jedoch ein kleines und vielbeschäftigtes Team sind, funktioniert ein Pull-Request möglicherweise noch besser.
Einige Funktionen von jsdom werden durch unsere Abhängigkeiten bereitgestellt. Zu den bemerkenswerten Dokumentationen in diesem Zusammenhang gehört die Liste der unterstützten CSS-Selektoren für unsere CSS-Selektor-Engine nwsapi
.
Abgesehen von den Funktionen, die wir noch nicht kennengelernt haben, gibt es zwei Hauptfunktionen, die derzeit außerhalb des Anwendungsbereichs von jsdom liegen. Diese sind:
Navigation : Die Möglichkeit, das globale Objekt und alle anderen Objekte zu ändern, wenn Sie auf einen Link klicken oder location.href
oder ähnliches zuweisen.
Layout : Die Möglichkeit zu berechnen, wo Elemente aufgrund von CSS visuell angeordnet werden, was sich auf Methoden wie getBoundingClientRects()
oder Eigenschaften wie offsetTop
auswirkt.
Derzeit verfügt jsdom über Dummy-Verhalten für einige Aspekte dieser Funktionen, z. B. das Senden eines „nicht implementierten“ "jsdomError"
an die virtuelle Konsole zur Navigation oder die Rückgabe von Nullen für viele Layout-bezogene Eigenschaften. Häufig können Sie diese Einschränkungen in Ihrem Code umgehen, z. B. indem Sie für jede Seite, zu der Sie während eines Crawls „navigieren“, neue JSDOM
Instanzen erstellen oder Object.defineProperty()
verwenden, um zu ändern, was verschiedene Layout-bezogene Getter und Methoden zurückgeben.
Beachten Sie, dass andere Tools im gleichen Bereich, wie z. B. PhantomJS, diese Funktionen unterstützen. Im Wiki finden Sie eine ausführlichere Beschreibung zu jsdom vs. PhantomJS.
jsdom ist ein von der Community getragenes Projekt, das von einem Team aus Freiwilligen betrieben wird. Sie könnten jsdom unterstützen, indem Sie:
Erhalten Sie professionellen Support für jsdom im Rahmen eines Tidelift-Abonnements. Tidelift trägt dazu bei, Open Source für uns nachhaltig zu machen und gibt den Teams gleichzeitig Garantien für Wartung, Lizenzierung und Sicherheit.
Direkter Beitrag zum Projekt.
Wenn Sie Hilfe bei jsdom benötigen, können Sie gerne einen der folgenden Orte nutzen:
Die Mailingliste (am besten für „Wie mache ich“-Fragen)
Der Issue-Tracker (am besten für Fehlerberichte)
Der Matrix-Raum: #jsdom:matrix.org