jsdom est une implémentation purement JavaScript de nombreux standards Web, notamment les standards WHATWG DOM et HTML, à utiliser avec Node.js. En général, l'objectif du projet est d'émuler suffisamment de sous-ensembles d'un navigateur Web pour être utile pour tester et supprimer des applications Web réelles.
Les dernières versions de jsdom nécessitent Node.js v18 ou plus récent. (Les versions de jsdom inférieures à la v23 fonctionnent toujours avec les versions précédentes de Node.js, mais ne sont pas prises en charge.)
const jsdom = require("jsdom");const { JSDOM } = jsdom;
Pour utiliser jsdom, vous utiliserez principalement le constructeur JSDOM
, qui est une exportation nommée du module principal jsdom. Passez une chaîne au constructeur. Vous récupérerez un objet JSDOM
, qui possède un certain nombre de propriétés utiles, notamment window
:
const dom = new JSDOM(`<!DOCTYPE html><p>Bonjour tout le monde</p>`);console.log(dom.window.document.querySelector("p").textContent); // "Bonjour le monde"
(Notez que jsdom analysera le HTML que vous lui transmettez comme le fait un navigateur, y compris les balises implicites <html>
, <head>
et <body>
.)
L'objet résultant est une instance de la classe JSDOM
, qui contient un certain nombre de propriétés et de méthodes utiles en plus de window
. En général, il peut être utilisé pour agir sur le jsdom depuis « l'extérieur », en faisant des choses qui ne sont pas possibles avec les API DOM normales. Pour les cas simples, où vous n'avez besoin d'aucune de ces fonctionnalités, nous recommandons un modèle de codage tel que
const { window } = new JSDOM(`...`);// ou evenconst { document } = (new JSDOM(`...`)).window;
Une documentation complète sur tout ce que vous pouvez faire avec la classe JSDOM
se trouve ci-dessous, dans la section "API Objet JSDOM
".
Le constructeur JSDOM
accepte un deuxième paramètre qui peut être utilisé pour personnaliser votre jsdom des manières suivantes.
const dom = nouveau JSDOM(``, { URL : "https://exemple.org/", référent : "https://example.com/", contentType : "texte/html", includeNodeLocations : vrai, Quota de stockage : 1 000 000} );
url
définit la valeur renvoyée par window.location
, document.URL
et document.documentURI
, et affecte des éléments tels que la résolution des URL relatives dans le document ainsi que les restrictions de même origine et le référent utilisés lors de la récupération des sous-ressources. La valeur par défaut est "about:blank"
.
referrer
affecte simplement la valeur lue à partir de document.referrer
. Par défaut, il n'y a aucun référent (ce qui se reflète sous la forme d'une chaîne vide).
contentType
affecte la valeur lue dans document.contentType
, ainsi que la façon dont le document est analysé : au format HTML ou XML. Les valeurs qui ne sont pas un type HTML MIME ou un type XML MIME seront levées. La valeur par défaut est "text/html"
. Si un paramètre charset
est présent, cela peut affecter le traitement des données binaires.
includeNodeLocations
préserve les informations de localisation produites par l'analyseur HTML, vous permettant de les récupérer avec la méthode nodeLocation()
(décrite ci-dessous). Cela garantit également que les numéros de ligne signalés dans les traces de pile d’exceptions pour le code exécuté dans les éléments <script>
sont corrects. La valeur par défaut est false
pour offrir les meilleures performances et ne peut pas être utilisée avec un type de contenu XML puisque notre analyseur XML ne prend pas en charge les informations de localisation.
storageQuota
est la taille maximale en unités de code pour les zones de stockage distinctes utilisées par localStorage
et sessionStorage
. Les tentatives de stockage de données supérieures à cette limite entraîneront la levée d'une DOMException
. Par défaut, il est défini sur 5 000 000 d’unités de code par origine, comme l’inspire la spécification HTML.
Notez que url
et referrer
sont canonisés avant d'être utilisés, donc par exemple, si vous transmettez "https:example.com"
, jsdom interprétera cela comme si vous aviez donné "https://example.com/"
. Si vous transmettez une URL non analysable, l'appel sera lancé. (Les URL sont analysées et sérialisées conformément à la norme URL.)
La capacité la plus puissante de jsdom est qu'il peut exécuter des scripts à l'intérieur de jsdom. Ces scripts peuvent modifier le contenu de la page et accéder à toutes les API de la plateforme Web implémentées par jsdom.
Cependant, cela s’avère également très dangereux lorsqu’il s’agit de contenus non fiables. Le bac à sable jsdom n'est pas infaillible, et le code exécuté dans les <script>
du DOM peut, s'il fait suffisamment d'efforts, accéder à l'environnement Node.js, et donc à votre machine. Ainsi, la possibilité d'exécuter des scripts intégrés dans le HTML est désactivée par défaut :
const dom = new JSDOM(`<body> <div id="content"></div> <script>document.getElementById("content").append(document.createElement("hr"));</script> </body>`);// Le script ne sera pas exécuté, par défaut :console.log(dom.window.document.getElementById("content").children.length); // 0
Pour activer l'exécution de scripts à l'intérieur de la page, vous pouvez utiliser l'option runScripts: "dangerously"
:
const dom = new JSDOM(`<body> <div id="content"></div> <script>document.getElementById("content").append(document.createElement("hr"));</script> </body>`, { runScripts: "dangerously" });// Le script sera exécuté et modifiera le DOM:console.log(dom.window.document.getElementById("content").children.length); // 1
Encore une fois, nous insistons sur le fait de ne l'utiliser que lorsque vous alimentez du code jsdom dont vous savez qu'il est sûr. Si vous l'utilisez sur du code arbitraire fourni par l'utilisateur ou sur du code provenant d'Internet, vous exécutez effectivement du code Node.js non fiable et votre machine pourrait être compromise.
Si vous souhaitez exécuter des scripts externes , inclus via <script src="">
, vous devrez également vous assurer qu'ils les chargent. Pour ce faire, ajoutez l'option resources: "usable"
comme décrit ci-dessous. (Vous souhaiterez probablement également définir l'option url
, pour les raisons évoquées ici.)
Les attributs du gestionnaire d'événements, comme <div onclick="">
, sont également régis par ce paramètre ; ils ne fonctionneront pas à moins que runScripts
ne soit défini sur "dangerously"
. (Cependant, les propriétés du gestionnaire d'événements, comme div.onclick = ...
, fonctionneront indépendamment des runScripts
.)
Si vous essayez simplement d'exécuter un script "de l'extérieur", au lieu de laisser les éléments <script>
et les attributs des gestionnaires d'événements s'exécuter "de l'intérieur", vous pouvez utiliser l'option runScripts: "outside-only"
, qui permet de nouvelles copies de tous les globaux fournis par les spécifications JavaScript doivent être installés sur window
. Cela inclut des éléments comme window.Array
, window.Promise
, etc. Il inclut également notamment window.eval
, qui permet d'exécuter des scripts, mais avec la window
jsdom comme global :
const dom = new JSDOM(`<body> <div id="content"></div> <script>document.getElementById("content").append(document.createElement("hr"));</script> </body>`, { runScripts: "outside-only" });// exécuter un script en dehors de JSDOM:dom.window.eval('document.getElementById("content").append(document.createElement("p"));');console.log(dom.window.document.getElementById("content"). enfants.longueur); // 1console.log(dom.window.document.getElementsByTagName("hr").length); // 0console.log(dom.window.document.getElementsByTagName("p").length); // 1
Ceci est désactivé par défaut pour des raisons de performances, mais peut être activé en toute sécurité.
Notez que dans la configuration par défaut, sans définir runScripts
, les valeurs de window.Array
, window.eval
, etc. seront les mêmes que celles fournies par l'environnement externe Node.js. Autrement dit, window.eval === eval
tiendra, donc window.eval
n'exécutera pas de scripts de manière utile.
Nous vous déconseillons fortement d'essayer "d'exécuter des scripts" en mélangeant les environnements globaux jsdom et Node (par exemple en faisant global.window = dom.window
), puis d'exécuter des scripts ou de tester du code dans l'environnement global de Node. Au lieu de cela, vous devez traiter jsdom comme vous le feriez avec un navigateur et exécuter tous les scripts et tests nécessitant un accès à un DOM dans l'environnement jsdom, en utilisant window.eval
ou runScripts: "dangerously"
. Cela peut nécessiter, par exemple, la création d'un bundle browserify à exécuter en tant qu'élément <script>
, comme vous le feriez dans un navigateur.
Enfin, pour les cas d'utilisation avancés, vous pouvez utiliser la méthode dom.getInternalVMContext()
, documentée ci-dessous.
jsdom n'a pas la capacité de restituer du contenu visuel et agira par défaut comme un navigateur sans tête. Il fournit des indications aux pages Web via des API telles que document.hidden
indiquant que leur contenu n'est pas visible.
Lorsque l'option pretendToBeVisual
est définie sur true
, jsdom prétendra qu'il restitue et affiche du contenu. Il le fait par :
Changer document.hidden
pour renvoyer false
au lieu de true
Modification de document.visibilityState
pour renvoyer "visible"
au lieu de "prerender"
Activation des méthodes window.requestAnimationFrame()
et window.cancelAnimationFrame()
, qui autrement n'existent pas
const window = (new JSDOM(``, { prétendreToBeVisual: true })).window;window.requestAnimationFrame(timestamp => { console.log(horodatage > 0);});
Notez que jsdom ne fait toujours aucune mise en page ni rendu, il s'agit donc simplement de faire semblant d'être visuel, et non d'implémenter les parties de la plate-forme qu'un véritable navigateur Web visuel implémenterait.
Par défaut, jsdom ne chargera aucune sous-ressource telle que des scripts, des feuilles de style, des images ou des iframes. Si vous souhaitez que jsdom charge de telles ressources, vous pouvez passer l'option resources: "usable"
, qui chargera toutes les ressources utilisables. Ce sont :
Frames et iframes, via <frame>
et <iframe>
Feuilles de style, via <link rel="stylesheet">
Scripts, via <script>
, mais seulement si runScripts: "dangerously"
est également défini
Images, via <img>
, mais uniquement si le package canvas
npm est également installé (voir "Support Canvas" ci-dessous)
Lorsque vous essayez de charger des ressources, rappelez-vous que la valeur par défaut de l'option url
est "about:blank"
, ce qui signifie que toutes les ressources incluses via des URL relatives ne pourront pas être chargées. (Le résultat de l'analyse de l'URL /something
par rapport à l'URL about:blank
est une erreur.) Ainsi, vous souhaiterez probablement définir une valeur autre que celle par défaut pour l'option url
dans ces cas, ou utiliser l'une des options pratiques Des API qui le font automatiquement.
Pour personnaliser plus complètement le comportement de chargement des ressources de jsdom, vous pouvez transmettre une instance de la classe ResourceLoader
comme valeur d'option resources
:
const resourceLoader = nouveau jsdom.ResourceLoader({ proxy : "http://127.0.0.1:9001", strictSSL : faux, userAgent : "Mellblomenator/9000",});const dom = new JSDOM(``, { resources: resourceLoader });
Les trois options du constructeur ResourceLoader
sont :
proxy
est l'adresse d'un proxy HTTP à utiliser.
strictSSL
peut être défini sur false pour désactiver l'exigence de validité des certificats SSL.
userAgent
affecte l'en-tête User-Agent
envoyé, et donc la valeur résultante pour navigator.userAgent
. La valeur par défaut est `Mozilla/5.0 (${process.platform || "unknown OS"}) AppleWebKit/537.36 (KHTML, like Gecko) jsdom/${jsdomVersion}`
.
Vous pouvez personnaliser davantage la récupération des ressources en sous-classant ResourceLoader
et en remplaçant la méthode fetch()
. Par exemple, voici une version qui remplace la réponse fournie pour une URL spécifique :
la classe CustomResourceLoader étend jsdom.ResourceLoader { fetch(url, options) {// Remplacez le contenu de ce script pour faire quelque chose d'inhabituel.if (url === "https://example.com/some-special-script.js") { return Promise.resolve( Buffer.from("window.someGlobal = 5;"));}return super.fetch(url, options); }}
jsdom appellera la méthode fetch()
de votre chargeur de ressources personnalisé chaque fois qu'il rencontrera une ressource "utilisable", conformément à la section ci-dessus. La méthode prend une chaîne d'URL, ainsi que quelques options que vous devez transmettre sans modification si vous appelez super.fetch()
. Il doit renvoyer une promesse pour un objet Buffer
Node.js, ou renvoyer null
si la ressource ne doit pas être chargée intentionnellement. En général, la plupart des cas voudront déléguer à super.fetch()
, comme indiqué.
L'une des options que vous recevrez dans fetch()
sera l'élément (le cas échéant) qui récupère une ressource.
la classe CustomResourceLoader étend jsdom.ResourceLoader { fetch(url, options) {if (options.element) { console.log(`Element ${options.element.localName} demande l'url ${url}`);}return super.fetch(url, options); }}
Comme les navigateurs web, jsdom a le concept de « console ». Cela enregistre à la fois les informations directement envoyées depuis la page, via des scripts exécutés à l'intérieur du document, ainsi que les informations provenant de l'implémentation jsdom elle-même. Nous appelons la console contrôlable par l'utilisateur une « console virtuelle », pour la distinguer de l'API de la console
Node.js et de l'API window.console
à l'intérieur de la page.
Par défaut, le constructeur JSDOM
renverra une instance avec une console virtuelle qui transmettra toute sa sortie à la console Node.js. Pour créer votre propre console virtuelle et la transmettre à jsdom, vous pouvez remplacer cette valeur par défaut en faisant
const virtualConsole = new jsdom.VirtualConsole();const dom = new JSDOM(``, { virtualConsole });
Un code comme celui-ci créera une console virtuelle sans comportement. Vous pouvez lui donner un comportement en ajoutant des écouteurs d'événements pour toutes les méthodes de console possibles :
virtualConsole.on("erreur", () => { ... });virtualConsole.on("warn", () => { ... });virtualConsole.on("info", () => { ... });virtualConsole.on("dir", () => { ... });// ... etc. Voir https://console.spec.whatwg.org/#logging
(Notez qu'il est probablement préférable de configurer ces écouteurs d'événements avant d'appeler new JSDOM()
, car des erreurs ou un script appelant la console peuvent se produire lors de l'analyse.)
Si vous souhaitez simplement rediriger la sortie de la console virtuelle vers une autre console, comme celle par défaut de Node.js, vous pouvez le faire
virtualConsole.sendTo(console);
Il existe également un événement spécial, "jsdomError"
, qui se déclenchera avec des objets d'erreur pour signaler les erreurs de jsdom lui-même. Ceci est similaire à la façon dont les messages d'erreur apparaissent souvent dans les consoles des navigateurs Web, même s'ils ne sont pas initiés par console.error
. Jusqu'à présent, les erreurs suivantes sont générées de cette façon :
Erreurs de chargement ou d'analyse des sous-ressources (scripts, feuilles de style, frames et iframes)
Erreurs d'exécution de script qui ne sont pas gérées par un gestionnaire d'événements onerror
de fenêtre qui renvoie true
ou appelle event.preventDefault()
Erreurs non implémentées résultant d'appels à des méthodes, comme window.alert
, que jsdom n'implémente pas, mais s'installe quand même pour des raisons de compatibilité Web.
Si vous utilisez sendTo(c)
pour envoyer des erreurs à c
, par défaut, il appellera c.error(errorStack[, errorDetail])
avec les informations des événements "jsdomError"
. Si vous préférez maintenir un mappage un à un strict des événements avec les appels de méthode, et peut-être gérer vous-même les "jsdomError"
, vous pouvez le faire
virtualConsole.sendTo(c, { omitJSDOMErrors: true });
Comme les navigateurs Web, jsdom a le concept d'un pot à cookies, stockant les cookies HTTP. Les cookies qui ont une URL sur le même domaine que le document et qui ne sont pas marqués HTTP uniquement sont accessibles via l'API document.cookie
. De plus, tous les cookies du pot à cookies auront un impact sur la récupération des sous-ressources.
Par défaut, le constructeur JSDOM
renverra une instance avec un pot de cookies vide. Pour créer votre propre pot de cookies et le transmettre à jsdom, vous pouvez remplacer cette valeur par défaut en faisant
const cookieJar = new jsdom.CookieJar(store, options);const dom = new JSDOM(``, { cookieJar });
Ceci est particulièrement utile si vous souhaitez partager le même pot à cookies entre plusieurs jsdoms, ou amorcer le pot à cookies avec certaines valeurs à l'avance.
Les pots à biscuits sont fournis dans le package Tough-Cookie. Le constructeur jsdom.CookieJar
est une sous-classe du pot à cookies Tough-Cookie qui définit par défaut l'option looseMode: true
, car elle correspond mieux au comportement des navigateurs. Si vous souhaitez utiliser vous-même les utilitaires et les classes de hard-cookie, vous pouvez utiliser l'exportation du module jsdom.toughCookie
pour accéder à l'instance du module hard-cookie fournie avec jsdom.
jsdom permet d'intervenir très tôt dans la création d'un jsdom : après la création des objets Window
et Document
, mais avant qu'un éventuel code HTML ne soit analysé pour peupler le document avec des nœuds :
const dom = new JSDOM(`<p>Bonjour</p>`, { beforeParse(window) {window.document.childNodes.length === 0;window.someCoolAPI = () => { /* ... */ }; }});
Ceci est particulièrement utile si vous souhaitez modifier l'environnement d'une manière ou d'une autre, par exemple en ajoutant des cales pour les API de plate-forme Web que jsdom ne prend pas en charge.
JSDOM
Une fois que vous avez construit un objet JSDOM
, il aura les fonctionnalités utiles suivantes :
La window
de propriétés récupère l'objet Window
qui a été créé pour vous.
Les propriétés virtualConsole
et cookieJar
reflètent les options que vous transmettez ou les valeurs par défaut créées pour vous si rien n'a été transmis pour ces options.
serialize()
La méthode serialize()
renverra la sérialisation HTML du document, y compris le doctype :
const dom = new JSDOM(`<!DOCTYPE html>bonjour`);dom.serialize() === "<!DOCTYPE html><html><head></head><body>bonjour</body></ html>";// Contraste avec :dom.window.document.documentElement.outerHTML === "<html><head></head><body>bonjour</body></html>";
nodeLocation(node)
La méthode nodeLocation()
trouvera où se trouve un nœud DOM dans le document source, renvoyant les informations d'emplacement parse5 pour le nœud :
const dom = nouveau JSDOM ( `<p>Bonjour <img src="foo.jpg"> </p>`, { includeNodeLocations : true });const document = dom.window.document;const bodyEl = document.body; // implicitement crééconst pEl = document.querySelector("p");const textNode = pEl.firstChild;const imgEl = document.querySelector("img");console.log(dom.nodeLocation(bodyEl)); // nul; ce n'est pas dans le 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 }
Notez que cette fonctionnalité ne fonctionne que si vous avez défini l'option includeNodeLocations
; les emplacements des nœuds sont désactivés par défaut pour des raisons de performances.
vm
Node.js à l'aide de getInternalVMContext()
Le module vm
intégré de Node.js est ce qui sous-tend la magie d'exécution de scripts de jsdom. Certains cas d'utilisation avancés, comme la précompilation d'un script puis son exécution plusieurs fois, bénéficient de l'utilisation du module vm
directement avec un Window
créé par jsdom.
Pour accéder à l'objet global contextifié, adapté à une utilisation avec les API vm
, vous pouvez utiliser la méthode 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);
Il s'agit d'une fonctionnalité quelque peu avancée, et nous vous conseillons de vous en tenir aux API DOM normales (telles que window.eval()
ou document.createElement("script")
), sauf si vous avez des besoins très spécifiques.
Notez que cette méthode lèvera une exception si l'instance JSDOM
a été créée sans runScripts
défini ou si vous utilisez jsdom dans un navigateur Web.
reconfigure(settings)
La propriété top
de window
est marquée [Unforgeable]
dans la spécification, ce qui signifie qu'il s'agit d'une propriété propre non configurable et ne peut donc pas être remplacée ou masquée par du code normal exécuté dans jsdom, même en utilisant Object.defineProperty
.
De même, à l'heure actuelle, jsdom ne gère pas la navigation (comme la définition de window.location.href = "https://example.com/"
); cela entraînera l'émission par la console virtuelle d'un "jsdomError"
expliquant que cette fonctionnalité n'est pas implémentée, et rien ne changera : il n'y aura pas de nouvel objet Window
ou Document
, et l'objet location
de window
existante aura toujours le même valeurs des propriétés.
Cependant, si vous agissez depuis l'extérieur de la fenêtre, par exemple dans un framework de test qui crée des jsdoms, vous pouvez remplacer l'un ou les deux à l'aide de la méthode spéciale reconfigure()
:
const dom = new JSDOM();dom.window.top === dom.window;dom.window.location.href === "about:blank";dom.reconfigure({ windowTop: myFakeTopForTesting, url: "https: //exemple.com/" });dom.window.top === myFakeTopForTesting;dom.window.location.href === "https://exemple.com/" ;
Notez que la modification de l'URL de jsdom aura un impact sur toutes les API qui renvoient l'URL actuelle du document, telles que window.location
, document.URL
et document.documentURI
, ainsi que sur la résolution des URL relatives dans le document et les vérifications de même origine. et référent utilisé lors de la récupération des sous-ressources. Cependant, il n'effectuera pas de navigation vers le contenu de cette URL ; le contenu du DOM restera inchangé et aucune nouvelle instance de Window
, Document
, etc. ne sera créée.
fromURL()
En plus du constructeur JSDOM
lui-même, jsdom fournit une méthode d'usine à retour de promesses pour construire un jsdom à partir d'une URL :
JSDOM.fromURL("https://example.com/", options).then(dom => { console.log(dom.serialize());});
La promesse renvoyée sera remplie avec une instance JSDOM
si l'URL est valide et que la demande aboutit. Toutes les redirections seront suivies jusqu'à leur destination finale.
Les options fournies à fromURL()
sont similaires à celles fournies au constructeur JSDOM
, avec les restrictions et conséquences supplémentaires suivantes :
Les options url
et contentType
ne peuvent pas être fournies.
L'option referrer
est utilisée comme en-tête de requête HTTP Referer
de la requête initiale.
L'option resources
affecte également la demande initiale ; ceci est utile si vous souhaitez, par exemple, configurer un proxy (voir ci-dessus).
L'URL, le type de contenu et le référent du jsdom résultant sont déterminés à partir de la réponse.
Tous les cookies définis via les en-têtes de réponse HTTP Set-Cookie
sont stockés dans le pot à cookies de jsdom. De même, tous les cookies déjà présents dans un pot à cookies fourni sont envoyés en tant qu'en-têtes de requête HTTP Cookie
.
fromFile()
Semblable à fromURL()
, jsdom fournit également une méthode d'usine fromFile()
pour construire un jsdom à partir d'un nom de fichier :
JSDOM.fromFile("stuff.html", options).then(dom => { console.log(dom.serialize());});
La promesse renvoyée se réalisera avec une instance JSDOM
si le fichier donné peut être ouvert. Comme d'habitude dans les API Node.js, le nom de fichier est donné par rapport au répertoire de travail actuel.
Les options fournies à fromFile()
sont similaires à celles fournies au constructeur JSDOM
, avec les valeurs par défaut supplémentaires suivantes :
L'option url
sera par défaut une URL de fichier correspondant au nom de fichier donné, au lieu de "about:blank"
.
L'option contentType
sera par défaut "application/xhtml+xml"
si le nom de fichier donné se termine par .xht
, .xhtml
ou .xml
; sinon, il continuera à utiliser par défaut "text/html"
.
fragment()
Dans le cas le plus simple, vous n’aurez peut-être pas besoin d’une instance JSDOM
entière avec toute la puissance associée. Vous n'aurez peut-être même pas besoin d'une Window
ou Document
! Au lieu de cela, il vous suffit d'analyser du code HTML et d'obtenir un objet DOM que vous pouvez manipuler. Pour cela, nous avons fragment()
, qui crée un DocumentFragment
à partir d'une chaîne donnée :
const frag = JSDOM.fragment(`<p>Bonjour</p><p><strong>Salut !</strong>`);frag.childNodes.length === 2;frag.querySelector("strong"). textContent === "Salut !";// etc.
Ici, frag
est une instance DocumentFragment
, dont le contenu est créé en analysant la chaîne fournie. L'analyse est effectuée à l'aide d'un élément <template>
, vous pouvez donc y inclure n'importe quel élément (y compris ceux avec des règles d'analyse étranges comme <td>
). Il est également important de noter que le DocumentFragment
résultant n'aura pas de contexte de navigation associé : c'est-à-dire que ownerDocument
du document des éléments aura une propriété defaultView
nulle, les ressources ne se chargeront pas, etc.
Tous les appels de la fabrique fragment()
aboutissent à des DocumentFragment
partageant le même propriétaire de modèle Document
. Cela permet de nombreux appels à fragment()
sans surcharge supplémentaire. Mais cela signifie également que les appels à fragment()
ne peuvent être personnalisés avec aucune option.
Notez que la sérialisation n'est pas aussi simple avec DocumentFragment
qu'avec les objets JSDOM
complets. Si vous devez sérialiser votre DOM, vous devriez probablement utiliser le constructeur JSDOM
plus directement. Mais pour le cas particulier d'un fragment contenant un seul élément, c'est assez simple à faire par les moyens normaux :
const frag = JSDOM.fragment(`<p>Bonjour</p>`);console.log(frag.firstChild.outerHTML); // enregistre "<p>Bonjour</p>"
jsdom inclut la prise en charge de l'utilisation du package canvas
pour étendre tous les éléments <canvas>
avec l'API canvas. Pour que cela fonctionne, vous devez inclure canvas
en tant que dépendance dans votre projet, en tant que homologue de jsdom
. Si jsdom peut trouver le package canvas
, il l'utilisera, mais s'il n'est pas présent, alors les éléments <canvas>
se comporteront comme <div>
s. Depuis jsdom v13, la version 2.x de canvas
est requise ; la version 1.x n'est plus prise en charge.
En plus de fournir une chaîne, le constructeur JSDOM
peut également recevoir des données binaires, sous la forme d'un Buffer
Node.js ou d'un type de données binaires JavaScript standard comme ArrayBuffer
, Uint8Array
, DataView
, etc. Lorsque cela est fait, jsdom reniflera l'encodage à partir des octets fournis, en recherchant les balises <meta charset>
comme le fait un navigateur.
Si l'option contentType
fournie contient un paramètre charset
, cet encodage remplacera l'encodage reniflé, sauf si une nomenclature UTF-8 ou UTF-16 est présente, auquel cas ceux-ci sont prioritaires. (Encore une fois, c'est comme un navigateur.)
Ce reniflage d'encodage s'applique également à JSDOM.fromFile()
et JSDOM.fromURL()
. Dans ce dernier cas, tous les en-têtes Content-Type
envoyés avec la réponse seront prioritaires, de la même manière que l'option contentType
du constructeur.
Notez que dans de nombreux cas, fournir des octets de cette manière peut être préférable à fournir une chaîne. Par exemple, si vous essayez d'utiliser l'API buffer.toString("utf-8")
de Node.js, Node.js ne supprimera aucune nomenclature principale. Si vous donnez ensuite cette chaîne à jsdom, il l'interprétera textuellement, laissant la nomenclature intacte. Mais le code de décodage des données binaires de jsdom supprimera les principales nomenclatures, tout comme un navigateur ; dans de tels cas, fournir directement buffer
donnera le résultat souhaité.
Les minuteries du jsdom (définies par window.setTimeout()
ou window.setInterval()
) exécuteront, par définition, du code à l'avenir dans le contexte de la fenêtre. Puisqu'il n'y a aucun moyen d'exécuter du code à l'avenir sans maintenir le processus en vie, des minuteries jsdom exceptionnelles maintiendront votre processus Node.js en vie. De même, puisqu'il n'existe aucun moyen d'exécuter du code dans le contexte d'un objet sans garder cet objet en vie, les minuteurs jsdom en suspens empêcheront le garbage collection de la fenêtre sur laquelle ils sont planifiés.
Si vous voulez être sûr de fermer une fenêtre jsdom, utilisez window.close()
, qui mettra fin à tous les minuteurs en cours d'exécution (et supprimera également tous les écouteurs d'événements sur la fenêtre et le document).
Dans Node.js, vous pouvez déboguer des programmes à l'aide de Chrome DevTools. Consultez la documentation officielle pour savoir comment commencer.
Par défaut, les éléments jsdom sont formatés comme de simples anciens objets JS dans la console. Pour faciliter le débogage, vous pouvez utiliser jsdom-devtools-formatter, qui vous permet de les inspecter comme de vrais éléments DOM.
Les gens ont souvent des problèmes avec le chargement de scripts asynchrones lors de l'utilisation de jsdom. De nombreuses pages chargent des scripts de manière asynchrone, mais il n'existe aucun moyen de savoir quand elles ont terminé, et donc quand c'est le bon moment pour exécuter votre code et inspecter la structure DOM résultante. Il s’agit d’une limitation fondamentale ; nous ne pouvons pas prédire ce que feront les scripts sur la page Web, et nous ne pouvons donc pas vous dire quand ils auront fini de charger d'autres scripts.
Cela peut être contourné de plusieurs manières. La meilleure façon, si vous contrôlez la page en question, est d'utiliser les mécanismes fournis par le chargeur de script pour détecter le moment où le chargement est terminé. Par exemple, si vous utilisez un chargeur de module tel que RequireJS, le code pourrait ressembler à :
// Côté Node.js :const window = (new JSDOM(...)).window;window.onModulesLoaded = () => { console.log("prêt à démarrer !");};
<!-- Dans le code HTML que vous fournissez à jsdom --><script>requirejs(["entry-module"], () => { window.onModulesLoaded();});</script>
Si vous ne contrôlez pas la page, vous pouvez essayer des solutions de contournement telles que l'interrogation de la présence d'un élément spécifique.
Pour plus de détails, voir la discussion dans le #640, en particulier le commentaire perspicace de @matthewkastor.
Bien que nous aimions ajouter de nouvelles fonctionnalités à jsdom et le maintenir à jour avec les dernières spécifications Web, il manque de nombreuses API. N'hésitez pas à signaler un problème pour tout ce qui manque, mais nous sommes une petite équipe occupée, donc une pull request pourrait fonctionner encore mieux.
Certaines fonctionnalités de jsdom sont fournies par nos dépendances. Une documentation notable à cet égard inclut la liste des sélecteurs CSS pris en charge pour notre moteur de sélection CSS, nwsapi
.
Au-delà des fonctionnalités que nous n'avons pas encore abordées, il existe deux fonctionnalités majeures qui sortent actuellement du champ d'application de jsdom. Ce sont :
Navigation : la possibilité de modifier l'objet global et tous les autres objets en cliquant sur un lien ou en attribuant location.href
ou similaire.
Layout : la possibilité de calculer où les éléments seront visuellement disposés grâce au CSS, ce qui a un impact sur des méthodes comme getBoundingClientRects()
ou des propriétés comme offsetTop
.
Actuellement, jsdom a des comportements factices pour certains aspects de ces fonctionnalités, tels que l'envoi d'une "jsdomError"
à la console virtuelle pour la navigation, ou le renvoi de zéros pour de nombreuses propriétés liées à la mise en page. Souvent, vous pouvez contourner ces limitations dans votre code, par exemple en créant de nouvelles instances JSDOM
pour chaque page vers laquelle vous « naviguez » lors d'une analyse, ou en utilisant Object.defineProperty()
pour modifier ce que renvoient divers getters et méthodes liés à la mise en page.
Notez que d'autres outils dans le même espace, tels que PhantomJS, prennent en charge ces fonctionnalités. Sur le wiki, nous avons un article plus complet sur jsdom vs PhantomJS.
jsdom est un projet communautaire maintenu par une équipe de bénévoles. Vous pouvez soutenir jsdom en :
Obtenir une assistance professionnelle pour jsdom dans le cadre d'un abonnement Tidelift. Tidelift nous aide à rendre l'open source durable tout en donnant aux équipes des garanties en matière de maintenance, de licences et de sécurité.
Contribuer directement au projet.
Si vous avez besoin d'aide avec jsdom, n'hésitez pas à utiliser l'un des sites suivants :
La liste de diffusion (idéale pour les questions « comment faire »)
Le suivi des problèmes (idéal pour les rapports de bogues)
La salle Matrix : #jsdom:matrix.org