jsdom 是許多 Web 標準的純 JavaScript 實現,特別是 WHATWG DOM 和 HTML 標準,可與 Node.js 一起使用。一般來說,該專案的目標是模擬足夠多的 Web 瀏覽器子集,以用於測試和抓取真實世界的 Web 應用程式。
最新版本的 jsdom 需要 Node.js v18 或更高版本。 (低於 v23 的 jsdom 版本仍然可以與先前的 Node.js 版本一起使用,但不受支援。)
const jsdom = require("jsdom");const { JSDOM } = jsdom;
要使用 jsdom,您將主要使用JSDOM
建構函數,它是 jsdom 主模組的命名導出。向建構函數傳遞一個字串。您將得到一個JSDOM
對象,它具有許多有用的屬性,特別是window
:
const dom = new JSDOM(`<!DOCTYPE html><p>Hello world</p>`);console.log(dom.window.document.querySelector("p").textContent); // “你好世界”
(請注意,jsdom 將像瀏覽器一樣解析您傳遞給它的 HTML,包括隱含的<html>
、 <head>
和<body>
標籤。)
產生的物件是JSDOM
類別的實例,除了window
之外,它還包含許多有用的屬性和方法。一般來說,它可以用來從「外部」作用於 jsdom,做普通 DOM API 無法完成的事情。對於不需要任何此功能的簡單情況,我們建議使用以下編碼模式
const { window } = new JSDOM(`...`);// 或甚至 const { document } = (new JSDOM(`...`)).window;
有關JSDOM
類別可以執行的所有操作的完整文件位於下面的「 JSDOM
物件 API」部分中。
JSDOM
建構函式接受第二個參數,可用於透過以下方式自訂 jsdom。
const dom = new JSDOM(``, { 網址:“https://example.org/”, 引薦來源:“https://example.com/”, 內容類型:“文字/html”, includeNodeLocations:true, 儲存配額: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
。預設情況下,受 HTML 規範的啟發,每個來源設定為 5,000,000 個代碼單元。
請注意, url
和referrer
在使用之前都會被規範化,因此,例如,如果您傳入"https:example.com"
,jsdom 會將其解釋為就像您給出了"https://example.com/"
一樣。如果您傳遞無法解析的 URL,呼叫將會拋出。 (URL 根據 URL 標準進行解析和序列化。)
jsdom最強大的能力是它可以在jsdom內部執行腳本。這些腳本可以修改頁面的內容並存取jsdom實現的所有Web平台API。
然而,在處理不受信任的內容時,這也是非常危險的。 jsdom 沙箱並非萬無一失,在 DOM 的<script>
內運行的程式碼如果足夠努力,就可以存取 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 程式碼時才使用此選項。如果您在任意使用者提供的程式碼或來自 Internet 的程式碼上使用它,您實際上正在執行不受信任的 Node.js 程式碼,並且您的電腦可能會受到損害。
如果您想執行透過<script src="">
包含的外部腳本,您還需要確保它們載入它們。為此,請新增選項resources: "usable"
,如下所述。 (出於此處討論的原因,您可能還想設定url
選項。)
事件處理程序屬性,例如<div onclick="">
,也受此設定的控制;除非將runScripts
設為"dangerously"
否則它們將無法運行。 (但是,事件處理程序屬性,如div.onclick = ...
,無論runScripts
如何都會起作用。)
如果您只是嘗試「從外部」執行腳本,而不是讓<script>
元素和事件處理程序屬性「從內部」運行,您可以使用runScripts: "outside-only"
選項,該選項可以啟用新的腳本副本所有JavaScript 規範提供的全域變數都安裝在window
上。 window.eval
包括window.Array
、 window.Promise
window
。
const dom = new JSDOM(`<body> <div id="content"></div> <script>document.getElementById("content").append(document.createElement("hr"));</script> </body>`, { runScripts: "outside-only" });// 在JSDOM 之外執行腳本:dom.window.eval('document.getElementById("content").append(document.createElement("p "));');console.log(dom.window.document.getElementById("content").children.length); // 1console.log(dom.window.document.getElementsByTagName("hr").length); // 0console.log(dom.window.document.getElementsByTagName("p").length); // 1
出於效能原因,此功能預設為關閉,但啟用是安全的。
請注意,在預設配置中,如果不設定runScripts
,則window.Array
、 window.eval
等的值將與外部 Node.js 環境提供的值相同。也就是說, window.eval === eval
將成立,因此window.eval
將不會以有用的方式執行腳本。
我們強烈建議不要嘗試透過將 jsdom 和 Node 全域環境混合在一起(例如透過執行global.window = dom.window
)來“執行腳本”,然後在 Node 全域環境中執行腳本或測試程式碼。相反,您應該像對待瀏覽器一樣對待 jsdom,並使用window.eval
或runScripts: "dangerously"
運行需要存取 jsdom 環境內 DOM 的所有腳本和測試。例如,這可能需要建立一個 browserify 套件以作為<script>
元素執行 - 就像在瀏覽器中一樣。
最後,對於高階用例,您可以使用dom.getInternalVMContext()
方法,如下所述。
jsdom 沒有渲染視覺內容的能力,預設就像無頭瀏覽器一樣。它透過document.hidden
等 API 向網頁提供其內容不可見的提示。
當pretendToBeVisual
選項設為true
時,jsdom將假裝它正在渲染和顯示內容。它透過以下方式做到這一點:
更改document.hidden
以傳回false
而不是true
更改document.visibilityState
以返回"visible"
而不是"prerender"
啟用window.requestAnimationFrame()
和window.cancelAnimationFrame()
方法,否則這些方法不存在
const window = (new JSDOM(``, { fakeToBeVisual: true })).window;window.requestAnimationFrame(timestamp => { console.log(時間戳 > 0);});
請注意,jsdom 仍然不進行任何佈局或渲染,因此這實際上只是假裝可視化,而不是實現真正的可視化 Web 瀏覽器將實現的平台部分。
預設情況下,jsdom 不會載入任何子資源,例如腳本、樣式表、圖片或 iframe。如果你想讓 jsdom 載入此類資源,你可以傳遞resources: "usable"
選項,這將載入所有可用的資源。這些是:
框架和 iframe,透過<frame>
和<iframe>
樣式表,透過<link rel="stylesheet">
腳本,透過<script>
,但前提是也設定了runScripts: "dangerously"
圖像,透過<img>
,但前提是也安裝了canvas
npm 套件(請參閱下面的「Canvas 支援」)
嘗試載入資源時,請記住url
選項的預設值是"about:blank"
,這表示透過相對 URL 包含的任何資源都將無法載入。 (嘗試根據 URL about:blank
解析 URL /something
的結果是錯誤。)因此,在這些情況下,您可能想要為url
選項設定非預設值,或使用其中一種便利方法自動執行此操作的 API。
若要更全面地自訂 jsdom 的資源載入行為,您可以傳遞ResourceLoader
類別的實例作為resources
選項值:
const ResourceLoader = new jsdom.ResourceLoader({ 代理:“http://127.0.0.1:9001”, 嚴格SSL:假, userAgent: "Mellblomenator/9000",});const dom = new JSDOM(``, { resources: resourceLoader });
ResourceLoader
函式的三個選項是:
proxy
是要使用的 HTTP 代理的位址。
strictSSL
可以設定為 false 以停用 SSL 憑證有效的要求。
userAgent
影響發送的User-Agent
標頭,進而影響navigator.userAgent
的結果值。它預設為`Mozilla/5.0 (${process.platform || "unknown OS"}) AppleWebKit/537.36 (KHTML, like Gecko) jsdom/${jsdomVersion}`
。
您可以透過子類別化Res ResourceLoader
並重寫fetch()
方法來進一步自訂資源取得。例如,以下版本會覆寫為特定 URL 提供的回應:
類別 CustomResourceLoader 擴充 jsdom.ResourceLoader { fetch(url, options) {// 覆寫此腳本的內容以執行一些不尋常的操作。 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(`Element ${options.element.localName} 正在請求 url ${url}`);}return super.fetch(url, options); }}
與 Web 瀏覽器一樣,jsdom 也有「控制台」的概念。這記錄了透過在文件內執行的腳本直接從頁面發送的訊息,以及來自 jsdom 實作本身的訊息。我們將使用者可控制的控制台稱為“虛擬控制台”,以區別於 Node.js console
API 和頁面內部window.console
API。
預設情況下, JSDOM
建構函式將傳回一個帶有虛擬控制台的實例,該虛擬控制台將其所有輸出轉送到 Node.js 控制台。要建立自己的虛擬控制台並將其傳遞給 jsdom,您可以透過執行以下操作來覆寫此預設值
const virtualConsole = new jsdom.VirtualConsole();const dom = new JSDOM(``, { virtualConsole });
這樣的程式碼將會建立一個沒有任何行為的虛擬控制台。您可以透過為所有可能的控制台方法新增事件偵聽器來賦予它行為:
virtualConsole.on("錯誤", () => { ... });virtualConsole.on("警告", () => { ... });virtualConsole.on("訊息", () => { ... });virtualConsole.on("dir", () => { ... });// ... 等等。
(請注意,最好在呼叫new JSDOM()
之前設定這些事件偵聽器,因為在解析過程中可能會出現錯誤或控制台呼叫腳本。)
如果您只是想將虛擬控制台輸出重新導向到另一個控制台(例如預設的 Node.js 控制台),您可以這樣做
virtualConsole.sendTo(控制台);
還有一個特殊事件"jsdomError"
,它將觸發錯誤物件以報告 jsdom 本身的錯誤。這類似於錯誤訊息通常在 Web 瀏覽器控制台中顯示的方式,即使它們不是由console.error
啟動的。到目前為止,這樣輸出的錯誤如下:
載入或解析子資源(腳本、樣式表、框架和 iframe)時發生錯誤
傳回true
或呼叫event.preventDefault()
視窗onerror
事件處理程序未處理的腳本執行錯誤
由於呼叫方法而導致的未實作錯誤,例如window.alert
,jsdom 未實作該方法,但為了 Web 相容性仍會安裝
如果您使用sendTo(c)
將錯誤傳送到c
,預設情況下它將使用來自"jsdomError"
事件的資訊呼叫c.error(errorStack[, errorDetail])
。如果您希望維護事件到方法呼叫的嚴格一對一映射,並且可能自己處理"jsdomError"
,那麼您可以這樣做
virtualConsole.sendTo(c, { omitJSDOMErrors: true });
與 Web 瀏覽器一樣,jsdom 也有 cookie jar 的概念,用於儲存 HTTP cookie。 URL 與文件位於相同網域且未標記為僅 HTTP 的 Cookie 可以透過document.cookie
API 進行存取。此外,cookie jar 中的所有 cookie 都會影響子資源的取得。
預設情況下, JSDOM
建構函式將傳回一個帶有有空 cookie jar 的實例。要建立您自己的 cookie jar 並將其傳遞給 jsdom,您可以透過執行以下操作來覆寫此預設值
const cookieJar = new jsdom.CookieJar(store, options);const dom = new JSDOM(``, { cookieJar });
如果您想在多個 jsdom 之間共用同一個 cookie jar,或提前用某些值填入 cookie jar,那麼這非常有用。
Cookie 罐子由tough-cookie 包提供。 jsdom.CookieJar
建構子是tough-cookie cookie jar的子類,預設設定looseMode: true
選項,因為這會更好地匹配瀏覽器的行為。如果您想自己使用tough-cookie的實用程式和類,您可以使用jsdom.toughCookie
模組匯出來存取與jsdom打包的tough-cookie模組實例。
jsdom 允許您很早就幹預 jsdom 的創建:在創建Window
和Document
物件之後,但在解析任何 HTML 以使用節點填充文件之前:
const dom = new JSDOM(`<p>你好</p>`, { beforeParse(window) {window.document.childNodes.length === 0;window.someCoolAPI = () => { /* ... */ }; }});
如果您想以某種方式修改環境,例如為 jsdom 不支援的 Web 平台 API 新增填充程序,這尤其有用。
JSDOM
物件 API一旦建構了JSDOM
對象,它將具有以下有用的功能:
屬性window
會擷取為您建立的Window
物件。
virtualConsole
和cookieJar
屬性反映您傳入的選項,或者如果沒有為這些選項傳入任何內容,則反映為您建立的預設值。
serialize()
序列化文檔serialize()
方法將傳回文檔的 HTML 序列化,包括文檔類型:
const dom = new JSDOM(`<!DOCTYPE html>hello`);dom.serialize() === "<!DOCTYPE html><html><head></head><body>hello</body></ html>";//比較:dom.window.document.documentElement.outerHTML === "<html><head></head><body>hello</body></html>";
nodeLocation(node)
取得節點的來源位置nodeLocation()
方法將尋找 DOM 節點在來源文件中的位置,並傳回該節點的 parse5 位置資訊:
const dom = 新的 JSDOM( `<p>你好<img src="foo.jpg"></p>`, { includeNodeLocations: true });const document = dom.window.document;const bodyEl = document.body; // 隱式建立 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)); // { 起始偏移量:13,結束偏移量:32 }
請注意,此功能僅在您設定了includeNodeLocations
選項後才有效;出於效能原因,節點位置預設為關閉。
getInternalVMContext()
與 Node.js vm
模組交互Node.js 的內建vm
模組是 jsdom 腳本運行魔力的基礎。一些高級用例,例如預先編譯腳本然後多次運行它,可以受益於直接將vm
模組與 jsdom 創建的Window
一起使用。
若要存取適合與vm
API 一起使用的上下文全域對象,您可以使用getInternalVMContext()
方法:
const { Script } = require("vm");const dom = new JSDOM(``, { runScripts: "僅外部" });const script = new Script(` if (!this.ran) { this.ran = 0; } ++this.ran;`);const vmContext = dom.getInternalVMContext();script.runInContext(vmContext);script.runInContext(vmContext);script.runInContext(vmContext);console.assert(dom.window . ran === 3);
這是有點進階的功能,我們建議堅持使用普通的 DOM API(例如window.eval()
或document.createElement("script")
),除非您有非常具體的需求。
請注意,如果JSDOM
實例是在沒有設定runScripts
情況下建立的,或者如果您在 Web 瀏覽器中使用 jsdom,則此方法將引發異常。
reconfigure(settings)
重新設定 jsdom window
的top
屬性在規範中被標記為[Unforgeable]
,這意味著它是一個不可配置的自有屬性,因此不能被 jsdom 內運行的正常程式碼覆蓋或隱藏,即使使用Object.defineProperty
也是如此。
同樣,目前 jsdom 不處理導航(例如設定window.location.href = "https://example.com/"
);這樣做會導致虛擬控制台發出"jsdomError"
解釋該功能尚未實現,並且不會發生任何變化:不會有新的Window
或Document
對象,並且現有window
的location
對象仍將具有相同的內容屬性值。
但是,如果您從視窗外部進行操作,例如在某些建立 jsdom 的測試框架中,您可以使用特殊的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/";
請注意,更改 jsdom 的 URL 將影響所有傳回目前文件 URL 的 API,例如window.location
、 document.URL
和document.documentURI
,以及文件中相對 URL 的解析以及同源檢查和取得子資源時使用的引用者。但是,它不會執行對該 URL 內容的導航; DOM 的內容將保持不變,並且不會建立Window
、 Document
等的新實例。
fromURL()
除了JSDOM
建構函數本身之外,jsdom 還提供了一個承諾返回工廠方法,用於從 URL 建構 jsdom:
JSDOM.fromURL("https://example.com/", options).then(dom => { console.log(dom.serialize());});
如果 URL 有效且請求成功,則傳回的 Promise 將透過JSDOM
實例實現。任何重定向都將被追蹤到其最終目的地。
提供給fromURL()
選項與提供給JSDOM
建構函數的選項類似,但有以下附加限制和後果:
無法提供url
和contentType
選項。
referrer
選項用作初始請求的 HTTP Referer
請求標頭。
resources
選項也會影響初始請求;例如,如果您想要設定代理(請參見上文),這非常有用。
產生的 jsdom 的 URL、內容類型和引薦來源網址是根據回應確定的。
透過 HTTP Set-Cookie
回應標頭設定的任何 cookie 都會儲存在 jsdom 的 cookie jar 中。同樣,所提供的 cookie jar 中已有的任何 cookie 都會作為 HTTP Cookie
請求標頭發送。
fromFile()
與fromURL()
類似,jsdom 也提供了fromFile()
工廠方法,用於從檔案名稱建構 jsdom:
JSDOM.fromFile("stuff.html", options).then(dom => { console.log(dom.serialize());});
如果可以開啟給定的文件,則傳回的承諾將透過JSDOM
實例實現。與 Node.js API 中通常的情況一樣,檔案名稱是相對於目前工作目錄給出的。
提供給fromFile()
選項與提供給JSDOM
建構函數的選項類似,具有以下附加預設值:
url
選項預設為與給定檔案名稱相對應的檔案 URL,而不是"about:blank"
。
如果給定的檔案名稱以.xht
、 .xhtml
或.xml
結尾,則contentType
選項會預設為"application/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
將具有 null defaultView
屬性,資源將不會載入等。
對fragment()
工廠的所有呼叫都會產生共享相同範本所有者Document
的DocumentFragment
。這允許對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>
元素。要實現此功能,您需要將canvas
作為專案中的依賴項包含進來,作為jsdom
的同級。如果 jsdom 可以找到canvas
包,它將使用它,但如果它不存在,那麼<canvas>
元素的行為將類似於<div>
。從jsdom v13開始,需要canvas
2.x版本;不再支援 1.x 版本。
除了提供字串之外, JSDOM
建構函數還可以以 Node.js Buffer
或標準 JavaScript 二進位資料類型(如ArrayBuffer
、 Uint8Array
、 DataView
等)的形式提供二進位資料。位元組的編碼,像瀏覽器一樣掃描<meta charset>
標籤。
如果提供的contentType
選項包含charset
參數,則該編碼將覆蓋嗅探的編碼 - 除非存在 UTF-8 或 UTF-16 BOM,在這種情況下,這些編碼優先。 (再次強調,這就像瀏覽器一樣。)
這種編碼嗅探也適用於JSDOM.fromFile()
和JSDOM.fromURL()
。在後一種情況下,與回應一起傳送的任何Content-Type
標頭都會優先,與建構子的contentType
選項相同。
請注意,在許多情況下,以這種方式提供位元組可能比提供字串更好。例如,如果您嘗試使用 Node.js 的buffer.toString("utf-8")
API,Node.js 將不會刪除任何前導 BOM。如果您隨後將此字串提供給 jsdom,它將逐字解釋它,而 BOM 保持不變。但jsdom的二進位資料解碼程式碼會像瀏覽器一樣去掉前導BOM;在這種情況下,直接提供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("準備好了!");};
<!-- 在您提供給 jsdom 的 HTML 中 --><script>requirejs(["entry-module"], () => { window.onModulesLoaded();});</script>
如果您無法控制頁面,則可以嘗試解決方法,例如輪詢特定元素是否存在。
有關更多詳細信息,請參閱 #640 中的討論,尤其是 @matthewkastor 的富有洞察力的評論。
儘管我們喜歡為 jsdom 添加新功能並使其與最新的 Web 規範保持同步,但它仍然缺少許多 API。請隨時針對任何缺少的內容提出問題,但我們是一個小而繁忙的團隊,因此拉取請求可能會更好。
jsdom 的一些功能是由我們的依賴項提供的。這方面值得注意的文件包括我們的 CSS 選擇器引擎nwsapi
支援的 CSS 選擇器清單。
除了我們尚未了解的功能之外,還有兩個主要功能目前超出了 jsdom 的範圍。這些都是:
導航:點擊連結或分配location.href
或類似內容時更改全域物件和所有其他物件的能力。
Layout :計算元素在 CSS 中的視覺佈局位置的能力,這會影響getBoundingClientRects()
等方法或offsetTop
等屬性。
目前 jsdom 對於這些功能的某些方面具有虛擬行為,例如向虛擬控制台發送“未實現” "jsdomError"
以進行導航,或為許多與佈局相關的屬性返回零。通常,您可以在程式碼中解決這些限制,例如,透過為爬網期間「導航」到的每個頁面建立新的JSDOM
實例,或使用Object.defineProperty()
更改各種與佈局相關的getter 和方法返回的內容。
請注意,同一領域的其他工具(例如 PhantomJS)確實支援這些功能。在 wiki 上,我們有關於 jsdom 與 PhantomJS 的更完整的文章。
jsdom 是一個由志工團隊維護的社區驅動專案。您可以透過以下方式支援 jsdom:
作為 Tidelift 訂閱的一部分,獲得 jsdom 的專業支援。 Tidelift 協助我們實現開源的永續發展,同時為團隊提供維護、授權和安全的保證。
直接為該專案做出貢獻。
如果您需要 jsdom 方面的協助,請隨時使用以下任一場所:
郵件列表(最適合“我如何”問題)
問題追蹤器(最適合錯誤報告)
矩陣房間:#jsdom:matrix.org