jsdom
jsdom là một hiện thực toàn-JavaScript của nhiều tiêu chuẩn web, đặc biệt là Tiêu chuẩn WHATWG DOM và HTML, để sử dụng với Node.js. Nói chung, mục tiêu của dự án là bắt chước đủ phần của một tập hợp con của trình duyệt web để có ích cho việc kiểm thử và trích xuất ứng dụng web thực tế.
Các phiên bản mới nhất của jsdom yêu cầu Node.js phiên bản 16 trở lên. (Các phiên bản jsdom dưới v22 vẫn hoạt động với các phiên bản Node.js trước đó, nhưng không được hỗ trợ.)
Sử dụng cơ bản
const jsdom = require("jsdom");
const { JSDOM } = jsdom;
Để sử dụng jsdom, bạn chủ yếu sẽ sử dụng hàm tạo JSDOM
, là một trong các hàm xuất khẩu có tên của mô-đun chính của jsdom. Truyền vào hàm tạo một chuỗi. Bạn sẽ nhận lại một đối tượng JSDOM
, có một số thuộc tính hữu ích, đặc biệt là window
:
const dom = new JSDOM(`<!DOCTYPE html><p>Hello world</p>`);
console.log(dom.window.document.querySelector("p").textContent); // "Hello world"
(Lưu ý rằng jsdom sẽ phân tích cú pháp HTML bạn truyền vào giống như một trình duyệt làm, bao gồm cả thẻ <html>
, <head>
, và <body>
được ngầm định.)
Đối tượng kết quả là một phiên bản của lớp JSDOM
, chứa một số thuộc tính và phương thức hữu ích ngoài window
. Nói chung, nó có thể được sử dụng để tác động lên jsdom từ “bên ngoài”, thực hiện những việc không thể thực hiện được bằng các API DOM bình thường. Đối với các trường hợp đơn giản, khi bạn không cần bất kỳ chức năng nào trong số này, chúng tôi khuyến nghị một mẫu mã lập trình như sau:
const { window } = new JSDOM(`...`);
// or even
const { document } = (new JSDOM(`...`)).window;
Tài liệu đầy đủ về mọi thứ bạn có thể thực hiện với lớp JSDOM
nằm ở dưới, trong phần “Giao diện lập trình đối tượng JSDOM
“.
Tùy chỉnh jsdom
Hàm tạo JSDOM
có thể chấp nhận một tham số thứ hai, được sử dụng để tùy chỉnh jsdom của bạn theo các cách sau đây.
Các tùy chọn đơn giản
const dom = new JSDOM(``, {
url: "https://example.org/",
referrer: "https://example.com/",
contentType: "text/html",
includeNodeLocations: true,
storageQuota: 10000000
});
url
thiết lập giá trị được trả về bởiwindow.location
,document.URL
, vàdocument.documentURI
, và ảnh hưởng đến việc giải quyết các URL tương đối trong tài liệu và các hạn chế cùng nguồn gốc và nguồn gốc trình trợ giúp được sử dụng khi truy xuất các tài nguyên con. Mặc định là"about:blank"
.referrer
chỉ ảnh hưởng đến giá trị đọc từdocument.referrer
. Mặc định là không có nguồn trỏ (hiển thị như chuỗi trống).contentType
ảnh hưởng đến giá trị đọc từdocument.contentType
, cũng như cách tài liệu được phân tích cú pháp: là HTML hoặc XML. Giá trị không phải là HTML MIME type hoặc XML MIME type sẽ gây ra lỗi. Mặc định là"text/html"
. Nếu có tham sốcharset
, nó có thể ảnh hưởng đến việc xử lý dữ liệu nhị phân.includeNodeLocations
bảo tồn thông tin vị trí do trình phân tích cú pháp HTML tạo ra, cho phép bạn lấy nó bằng phương thứcnodeLocation()
(được mô tả ở dưới). Nó cũng đảm bảo rằng số dòng được báo cáo trong các dãy stack trace ngoại lệ cho mã chạy bên trong các phần tử<script>
là chính xác. Mặc định làfalse
để cung cấp hiệu suất tốt nhất, và không thể sử dụng với kiểu nội dung XML vì trình phân tích cú pháp XML của chúng tôi không hỗ trợ thông tin vị trí.storageQuota
là kích thước tối đa trong đơn vị mã cho các khu vực lưu trữ riêng biệt được sử dụng bởilocalStorage
vàsessionStorage
. Cố gắng lưu trữ dữ liệu lớn hơn giới hạn này sẽ gây ra mộtDOMException
được ném ra. Theo mặc định, nó được đặt là 5,000,000 đơn vị mã mỗi nguồn gốc, theo nguồn cảm hứng từ các quy định HTML.
Lưu ý rằng cả url
và referrer
đều được chuẩn hóa trước khi sử dụng, vì vậy ví dụ, nếu bạn truyền vào "https:example.com"
, jsdom sẽ hiểu rằng như bạn đã cung cấp "https://example.com/"
. Nếu bạn truyền một URL không thể phân tích được, cuộc gọi sẽ gây ra lỗi. (URL được phân tích và chuỗi hóa theo URL Standard.)
Thực thi các đoạn mã
Khả năng mạnh mẽ nhất của jsdom là khả năng thực thi các đoạn mã bên trong jsdom. Những đoạn mã này có thể sửa đổi nội dung của trang và truy cập vào tất cả các API nền tảng web mà jsdom triển khai.
Tuy nhiên, điều này cũng rất nguy hiểm khi làm việc với nội dung không đáng tin cậy. Lược đồ an toàn của jsdom không hoàn hảo, và mã chạy bên trong các phần tử <script>
của DOM có thể, nếu cố gắng đủ, truy cập vào môi trường Node.js, và do đó là máy của bạn. Vì vậy, khả năng thực thi các đoạn mã được nhúng trong HTML bị tắt theo mặc định:
const dom = new JSDOM(`<body>
<script>document.body.appendChild(document.createElement("hr"));</script>
</body>`);
// The script will not be executed, by default:
dom.window.document.body.children.length === 1;
Để kích hoạt việc thực thi các đoạn mã bên trong trang, bạn có thể sử dụng tùy chọn runScripts: "dangerously"
:
const dom = new JSDOM(`<body>
<script>document.body.appendChild(document.createElement("hr"));</script>
</body>`, { runScripts: "dangerously" });
// The script will be executed and modify the DOM:
dom.window.document.body.children.length === 2;
Một lần nữa, chúng tôi nhấn mạnh chỉ nên sử dụng điều này khi bạn đưa mã jsdom mà bạn biết là an toàn. Nếu bạn sử dụng nó trên mã do người dùng cung cấp một cách tùy ý, hoặc mã từ Internet, bạn đang chạy mã Node.js không đáng tin cậy, và máy của bạn có thể bị tấn công.
Nếu bạn muốn thực thi các đoạn mã bên ngoài, được bao gồm qua <script src="">
, bạn cũng cần đảm bảo rằng chúng tải chúng. Để làm điều này, thêm tùy chọn resources: "usable"
như mô tả dưới đây. (Bạn cũng có thể muốn đặt tùy chọn url
, vì lý do đã thảo luận ở đó.)
Các thuộc tính bộ xử lý sự kiện, như <div onclick="">
, cũng tuân theo cài đặt này; chúng sẽ không hoạt động trừ khi runScripts
được đặt thành "dangerously"
. (Tuy nhiên, các thuộc tính bộ xử lý sự kiện, như div.onclick = ...
, sẽ hoạt động bất kể runScripts
.)
Nếu bạn đơn giản chỉ muốn thực thi mã “từ bên ngoài”, thay vì để các phần tử <script>
và các thuộc tính bộ xử lý sự kiện chạy “từ bên trong”, bạn có thể sử dụng tùy chọn runScripts: "outside-only"
, cho phép các bản sao tươi của tất cả các toàn cục do chuẩn JavaScript cung cấp được cài đặt trên window
. Điều này bao gồm các thứ như window.Array
, window.Promise
, v.v. Đặc biệt, nó bao gồm cả window.eval
, cho phép thực thi các đoạn mã, nhưng với window
của jsdom làm toàn cục:
const { window } = new JSDOM(``, { runScripts: "outside-only" });
window.eval(`document.body.innerHTML = "<p>Hello, world!</p>";`);
window.document.body.children.length === 1;
Điều này được tắt mặc định vì lý do hiệu suất, nhưng có thể được bật để an toàn sử dụng.
(Lưu ý rằng trong cấu hình mặc định, mà không có cài đặt runScripts
, các giá trị của window.Array
, window.eval
, v.v. sẽ giống như những gì được cung cấp bởi môi trường Node.js bên ngoài. Điều đó có nghĩa là window.eval === eval
sẽ thỏa mãn, vì vậy window.eval
sẽ không chạy các đoạn mã một cách hữu ích.)
Chúng tôi mạnh mẽ khuyến nghị không nên cố gắng “thực thi các đoạn mã” bằng cách kết hợp môi trường toàn cục của jsdom và Node (ví dụ, bằng cách thực hiện global.window = dom.window
), và sau đó thực thi mã hoặc mã kiểm tra trong môi trường toàn cục của Node. Thay vào đó, bạn nên xem xét jsdom giống như bạn làm với một trình duyệt và chạy tất cả các đoạn mã và kiểm tra cần truy cập vào DOM trong môi trường jsdom, sử dụng window.eval
hoặc runScripts: "dangerously"
. Điều này có thể đòi hỏi, ví dụ, tạo một gói browserify để thực thi như một phần tử <script>
– giống như bạn thường làm trong một trình duyệt.
Cuối cùng, đối với các trường hợp sử dụng nâng cao, bạn có thể sử dụng phương thức dom.getInternalVMContext()
, được mô tả dưới đây.
Giả vời như một trình duyệt có giao diện
jsdom không có khả năng hiển thị nội dung hình ảnh và sẽ hoạt động giống như một trình duyệt không có giao diện mặc định. Nó cung cấp gợi ý cho các trang web thông qua các API như document.hidden
rằng nội dung của họ không hiển thị.
Khi tùy chọn pretendToBeVisual
được đặt thành true
, jsdom sẽ giả vờ rằng nó đang hiển thị và hiển thị nội dung. Điều này được thực hiện bằng cách:
- Thay đổi
document.hidden
để trả vềfalse
thay vìtrue
- Thay đổi
document.visibilityState
để trả về"visible"
thay vì"prerender"
-
Kích hoạt các phương thức
window.requestAnimationFrame()
vàwindow.cancelAnimationFrame()
, mà thông thường không tồn tạiconst window = (new JSDOM(“, { pretendToBeVisual: true })).window;
window.requestAnimationFrame(timestamp => {
console.log(timestamp > 0);
});
Lưu ý rằng jsdom vẫn không thực hiện bất kỳ bố cục hoặc hiển thị nào, vì vậy điều này thực sự chỉ là về việc giả vờ như một giao diện thị giác, không phải về việc triển khai các phần của nền tảng mà một trình duyệt web thị giác thực sự triển khai.
Tải các tài nguyên con
Tùy chọn cơ bản
Theo mặc định, jsdom sẽ không tải bất kỳ tài nguyên con nào như scripts, stylesheets, hình ảnh hoặc iframes. Nếu bạn muốn jsdom tải các tài nguyên như vậy, bạn có thể truyền tùy chọn resources: "usable"
, điều này sẽ tải tất cả các tài nguyên có thể sử dụng. Các tài nguyên đó bao gồm:
- Frames và iframes, thông qua
<frame>
và<iframe>
- Stylesheets, thông qua
<link rel="stylesheet">
- Scripts, thông qua
<script>
, nhưng chỉ khirunScripts: "dangerously"
cũng được đặt - Hình ảnh, thông qua
<img>
, nhưng chỉ khi gói npmcanvas
cũng được cài đặt (xem phần “Hỗ trợ Canvas” dưới đây)
Khi cố gắng tải các tài nguyên, hãy nhớ rằng giá trị mặc định cho tùy chọn url
là "about:blank"
, điều này có nghĩa là bất kỳ tài nguyên nào được bao gồm qua các URL tương đối sẽ thất bại trong việc tải. (Kết quả của việc cố gắng phân tích URL /something
so với URL about:blank
là một lỗi.) Vì vậy, có lẽ bạn muốn đặt một giá trị không mặc định cho tùy chọn url
trong những trường hợp đó, hoặc sử dụng một trong các API tiện ích mà làm điều này tự động.
Cấu hình nâng cao
Để tùy chỉnh hoàn toàn hành vi tải tài nguyên của jsdom, bạn có thể truyền một phiên bản của lớp ResourceLoader
như giá trị của tùy chọn resources
:
const resourceLoader = new jsdom.ResourceLoader({
proxy: "http://127.0.0.1:9001",
strictSSL: false,
userAgent: "Mellblomenator/9000",
});
const dom = new JSDOM(``, { resources: resourceLoader });
Ba tùy chọn cho hàm tạo ResourceLoader
là:
proxy
là địa chỉ của một proxy HTTP để sử dụng.strictSSL
có thể được đặt thành false để vô hiệu hóa yêu cầu chứng chỉ SSL hợp lệ.userAgent
ảnh hưởng đến tiêu đềUser-Agent
được gửi đi, và do đó giá trị kết quả chonavigator.userAgent
. Mặc định làMozilla/5.0 (${process.platform || "unknown OS"}) AppleWebKit/537.36 (KHTML, like Gecko) jsdom/${jsdomVersion}
.
Bạn có thể tùy chỉnh thêm việc tải tài nguyên bằng cách tạo lớp con của ResourceLoader
và ghi đè phương thức fetch()
. Ví dụ, đây là một phiên bản ghi đè phản hồi được cung cấp cho một URL cụ thể:
class CustomResourceLoader extends jsdom.ResourceLoader {
fetch(url, options) {
// Override the contents of this script to do something unusual.
if (url === "https://example.com/some-specific-script.js") {
return Promise.resolve(Buffer.from("window.someGlobal = 5;"));
}
return super.fetch(url, options);
}
}
jsdom sẽ gọi phương thức fetch()
của trình tải tài nguyên tùy chỉnh của bạn mỗi khi nó gặp một tài nguyên “có thể sử dụng”, như được trình bày ở trên. Phương thức này có tham số là một chuỗi URL, cũng như một vài tùy chọn bạn nên truyền qua một cách không thay đổi nếu gọi super.fetch()
. Nó phải trả về một promise cho một đối tượng Buffer
của Node.js hoặc trả về null
nếu tài nguyên không được tải cố ý. Nói chung, hầu hết các trường hợp sẽ muốn ủy quyền cho super.fetch()
, như đã hiển thị.
Một trong các tùy chọn bạn sẽ nhận được trong fetch()
sẽ là phần tử (nếu áp dụng) đang tải tài nguyên.
class CustomResourceLoader extends jsdom.ResourceLoader {
fetch(url, options) {
if (options.element) {
console.log(`Element ${options.element.localName} is requesting the url ${url}`);
}
return super.fetch(url, options);
}
}
Bảng điều khiển ảo
Giống như các trình duyệt web, jsdom có khái niệm về “console”. Nó ghi lại cả thông tin được gửi trực tiếp từ trang thông qua các đoạn mã chạy trong tài liệu, cũng như thông tin từ việc thực hiện jsdom. Chúng ta gọi bảng điều khiển do người dùng kiểm soát là “bảng điều khiển ảo”, để phân biệt nó với API console
của Node.js và API window.console
bên trong trang.
Mặc định, hàm tạo JSDOM
sẽ trả về một phiên bản với một bảng điều khiển ảo chuyển tiếp toàn bộ đầu ra của nó đến bảng điều khiển của Node.js. Để tạo bảng điều khiển ảo riêng của bạn và truyền nó cho jsdom, bạn có thể ghi đè lên mặc định bằng cách thực hiện
const virtualConsole = new jsdom.VirtualConsole();
const dom = new JSDOM(``, { virtualConsole });
Mã như thế này sẽ tạo ra một bảng điều khiển ảo mà không có hành vi. Bạn có thể thêm hành vi cho nó bằng cách thêm bộ lắng nghe sự kiện cho tất cả các phương thức bảng điều khiển có thể:
virtualConsole.on("error", () => { ... });
virtualConsole.on("warn", () => { ... });
virtualConsole.on("info", () => { ... });
virtualConsole.on("dir", () => { ... });
// ... etc. See https://console.spec.whatwg.org/#logging
(Lưu ý rằng có lẽ tốt nhất là thiết lập bộ lắng nghe sự kiện này trước khi gọi new JSDOM()
, vì lỗi hoặc đoạn mã gọi bảng điều khiển có thể xảy ra trong quá trình phân tích cú pháp.)
Nếu bạn chỉ đơn giản muốn chuyển hướng đầu ra của bảng điều khiển ảo đến một bảng điều khiển khác, giống như bảng điều khiển mặc định của Node.js, bạn có thể thực hiện
virtualConsole.sendTo(console);
Cũng có một sự kiện đặc biệt, "jsdomError"
, sẽ kích hoạt với đối tượng lỗi để báo cáo các lỗi từ jsdom chính. Điều này tương tự như cách thông báo lỗi thường xuất hiện trong bảng điều khiển trình duyệt web, ngay cả khi chúng không được khởi tạo bởi console.error
. Cho đến nay, các lỗi sau được xuất bằng cách này:
- Lỗi tải hoặc phân tích cú pháp các tài nguyên con (scripts, stylesheets, frames, và iframes)
- Lỗi thực thi đoạn mã không được xử lý bởi bộ xử lý sự kiện
onerror
của cửa sổ mà trả vềtrue
hoặc gọievent.preventDefault()
- Lỗi chưa được thực hiện từ các cuộc gọi phương thức, như
window.alert
, mà jsdom không thực hiện, nhưng vẫn cài đặt để tương thích với web
Nếu bạn đang sử dụng sendTo(c)
để gửi lỗi đến c
, theo mặc định, nó sẽ gọi c.error(errorStack[, errorDetail])
với thông tin từ các sự kiện "jsdomError"
. Nếu bạn muốn duy trì một ánh xạ một-một nghiêm ngặt từ sự kiện đến các cuộc gọi phương thức, và có thể xử lý các sự kiện "jsdomError"
của chính bạn, bạn có thể thực hiện
virtualConsole.sendTo(c, { omitJSDOMErrors: true });
Cookie jars
Tương tự như các trình duyệt web, jsdom có khái niệm về hộp cookie, lưu trữ các cookie HTTP. Các cookie có URL trên cùng miền như tài liệu và không được đánh dấu là HTTP-only có thể truy cập thông qua API document.cookie
. Ngoài ra, tất cả các cookie trong hộp cookie sẽ ảnh hưởng đến việc tải các tài nguyên con.
Theo mặc định, hàm tạo JSDOM
sẽ trả về một phiên bản với một hộp cookie trống. Để tạo hộp cookie riêng của bạn và truyền nó cho jsdom, bạn có thể ghi đè lên mặc định bằng cách thực hiện
const cookieJar = new jsdom.CookieJar(store, options);
const dom = new JSDOM(``, { cookieJar });
Điều này thường hữu ích nếu bạn muốn chia sẻ cùng một hộp cookie cho nhiều jsdom, hoặc chuẩn bị hộp cookie với một số giá trị cụ thể trước.
Hộp cookie được cung cấp bởi gói tough-cookie. Hàm tạo jsdom.CookieJar
là một lớp con của hộp cookie tough-cookie, mặc định đặt tùy chọn looseMode: true
, vì điều đó matches better how browsers behave. Nếu bạn muốn sử dụng các tiện ích và lớp của tough-cookie mà bạn tự mình, bạn có thể sử dụng mô-đun xuất jsdom.toughCookie
để truy cập vào phiên bản của mô-đun tough-cookie được đóng gói cùng jsdom.
Can thiệp trước khi phân tích cú pháp
jsdom cho phép bạn can thiệp vào việc tạo ra một đối tượng jsdom rất sớm: sau khi các đối tượng Window
và Document
được tạo ra, nhưng trước khi bất kỳ HTML nào được phân tích để điền các nút vào tài liệu:
const dom = new JSDOM(`<p>Hello</p>`, {
beforeParse(window) {
window.document.childNodes.length === 0;
window.someCoolAPI = () => { /* ... */ };
}
});
Điều này đặc biệt hữu ích nếu bạn muốn sửa đổi môi trường một cách nào đó, ví dụ như thêm các shims cho các API nền tảng web mà jsdom không hỗ trợ.
JSDOM
object API
Khi bạn đã tạo một đối tượng JSDOM
, nó sẽ có những khả năng hữu ích sau đây:
Thuộc tính
Thuộc tính window
trả về đối tượng Window
đã được tạo ra cho bạn.
Các thuộc tính virtualConsole
và cookieJar
phản ánh các tùy chọn bạn truyền vào hoặc các giá trị mặc định được tạo ra cho bạn nếu không có gì được truyền vào cho những tùy chọn đó.
Chuyển đổi tài liệu thành chuỗi bằng cách sử dụng serialize()
Phương thức serialize()
sẽ trả về chuỗi HTML serialization của tài liệu, bao gồm cả doctype:
const dom = new JSDOM(`<!DOCTYPE html>hello`);
dom.serialize() === "<!DOCTYPE html><html><head></head><body>hello</body></html>";
// Contrast with:
dom.window.document.documentElement.outerHTML === "<html><head></head><body>hello</body></html>";
Lấy vị trí nguồn của một nút bằng cách sử dụng nodeLocation(node)
Phương thức nodeLocation()
sẽ tìm vị trí một nút DOM trong tài liệu nguồn và trả về chuỗi parse5 location info cho nút đó:
const dom = new JSDOM(
`<p>Hello
<img src="foo.jpg">
</p>`,
{ includeNodeLocations: true }
);
const document = dom.window.document;
const bodyEl = document.body; // implicitly created
const pEl = document.querySelector("p");
const textNode = pEl.firstChild;
const imgEl = document.querySelector("img");
console.log(dom.nodeLocation(bodyEl)); // null; it's not in the source
console.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 }
Lưu ý rằng tính năng này chỉ hoạt động nếu bạn đã đặt tùy chọn includeNodeLocations
; vị trí nút mặc định sẽ bị tắt vì lý do hiệu suất.
Tương tác với mô-đun vm
của Node.js bằng cách sử dụng getInternalVMContext()
Mô-đun tích hợp sẵn vm của Node.js là cơ sở cho phép chạy mã kỳ diệu của jsdom. Một số trường hợp sử dụng nâng cao, như tiền biên dịch một đoạn mã và sau đó chạy nó nhiều lần, có lợi từ việc sử dụng mô-đun vm
trực tiếp với một Window
được tạo ra bởi jsdom.
Để truy cập vào contextified global object, phù hợp để sử dụng với các API của vm
, bạn có thể sử dụng phương thức 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);
Đây là chức năng một chút nâng cao và chúng tôi khuyên bạn nên tuân theo các API thông thường của DOM (như window.eval()
hoặc document.createElement("script")
) trừ khi bạn có nhu cầu cụ thể rất riêng.
Lưu ý rằng phương thức này sẽ ném một ngoại lệ nếu phiên bản JSDOM
được tạo mà không có runScripts
được đặt hoặc nếu bạn đang sử dụng jsdom trong trình duyệt web.
Cấu hình lại jsdom bằng cách sử dụng reconfigure(settings)
Thuộc tính top
trên window
được đánh dấu [Unforgeable]
trong đặc tả, có nghĩa là đó là một thuộc tính riêng không thể cấu hình lại và do đó không thể bị ghi đè hoặc che khuất bởi mã thông thường chạy bên trong jsdom, ngay cả khi sử dụng Object.defineProperty
.
Tương tự như vậy, hiện tại jsdom không xử lý việc điều hướng (như thiết lập window.location.href = "https://example.com/"
); việc này sẽ khiến cho bảng điều khiển ảo phát ra một sự kiện "jsdomError"
để giải thích rằng tính năng này chưa được thực hiện, và không có gì thay đổi: sẽ không có đối tượng Window
hoặc Document
mới được tạo ra, và đối tượng location
của window
hiện tại vẫn giữ nguyên các giá trị thuộc tính.
Tuy nhiên, nếu bạn đang thực hiện từ bên ngoài cửa sổ, ví dụ như trong một framework kiểm tra tạo jsdoms, bạn có thể ghi đè lên một hoặc cả hai bằng cách sử dụng phương thức đặc biệt 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/";
Lưu ý rằng việc thay đổi URL của jsdom sẽ ảnh hưởng đến tất cả các API trả về URL tài liệu hiện tại, như window.location
, document.URL
, và document.documentURI
, cũng như việc giải quyết các URL tương đối trong tài liệu, và các kiểm tra cùng nguồn gốc và nguồn gốc nhưng nguồn đã sử dụng trong việc tải các tài nguyên con. Tuy nhiên, nó sẽ không thực hiện việc điều hướng đến nội dung của URL đó; nội dung của DOM sẽ không thay đổi và không có thể hiện mới của Window
, Document
, v.v. sẽ được tạo ra.
Các API tiện ích
fromURL()
Ngoài hàm tạo JSDOM
chính, jsdom cung cấp một phương thức nhà máy trả về hứa hẹn để xây dựng một đối tượng jsdom từ một URL:
JSDOM.fromURL("https://example.com/", options).then(dom => {
console.log(dom.serialize());
});
Promise trả về sẽ thực hiện nhiệm vụ với một phiên bản JSDOM
nếu URL hợp lệ và yêu cầu thành công. Bất kỳ chuyển hướng nào sẽ được theo dõi đến điểm đến cuối cùng của chúng.
Các tùy chọn được cung cấp cho fromURL()
tương tự như những gì được cung cấp cho hàm tạo JSDOM
, với các hạn chế và hậu quả bổ sung sau đây:
- Các tùy chọn
url
vàcontentType
không thể được cung cấp. - Tùy chọn
referrer
được sử dụng như tiêu đề yêu cầu HTTPReferer
của yêu cầu ban đầu. - Tùy chọn
resources
cũng ảnh hưởng đến yêu cầu ban đầu; điều này hữu ích nếu bạn muốn, ví dụ, cấu hình một proxy (xem ở trên). - URL, loại nội dung và referrer của jsdom kết quả được xác định từ phản hồi.
- Bất kỳ cookie nào được đặt thông qua tiêu đề phản hồi
Set-Cookie
HTTP được lưu trữ trong hũ đựng cookie của jsdom. Tương tự, bất kỳ cookie nào đã có trong hũ đựng cookie được cung cấp đã được gửi dưới dạng tiêu đề yêu cầuCookie
HTTP.
fromFile()
Tương tự như fromURL()
, jsdom cũng cung cấp một phương thức nhà máy fromFile()
để xây dựng một đối tượng jsdom từ một tên tập tin:
JSDOM.fromFile("stuff.html", options).then(dom => {
console.log(dom.serialize());
});
Promise trả về sẽ thực hiện nhiệm vụ với một phiên bản JSDOM
nếu tập tin cụ thể có thể mở được. Như thường lệ trong các API của Node.js, tên tập tin được cung cấp liên quan đến thư mục làm việc hiện tại.
Các tùy chọn được cung cấp cho fromFile()
tương tự như những gì được cung cấp cho hàm tạo JSDOM
, với các giá trị mặc định bổ sung sau đây:
- Tùy chọn
url
sẽ mặc định thành một URL tệp tương ứng với tên tập tin cụ thể, thay vì"about:blank"
. - Tùy chọn
contentType
sẽ mặc định thành"application/xhtml+xml"
nếu tên tập tin cụ thể kết thúc bằng.xht
,.xhtml
, hoặc.xml
; nếu không, nó sẽ tiếp tục mặc định thành"text/html"
.
fragment()
Đối với những trường hợp rất đơn giản, bạn có thể không cần một phiên bản JSDOM
hoàn chỉnh với tất cả sức mạnh liên quan của nó. Thậm chí bạn có thể không cần một Window
hoặc Document
! Thay vào đó, bạn chỉ cần phân tích một số mã HTML và có được một đối tượng DOM bạn có thể điều chỉnh. Đối với việc này, chúng ta có fragment()
, tạo ra một DocumentFragment
từ một chuỗi cụ thể:
const frag = JSDOM.fragment(`<p>Hello</p><p><strong>Hi!</strong>`);
frag.childNodes.length === 2;
frag.querySelector("strong").textContent === "Hi!";
// etc.
Ở đây, frag
là một phiên bản DocumentFragment, nội dung của nó được tạo ra bằng cách phân tích cú pháp chuỗi được cung cấp. Việc phân tích cú pháp được thực hiện bằng cách sử dụng một phần tử <template>
, vì vậy bạn có thể bao gồm bất kỳ phần tử nào ở đó (bao gồm cả những phần tử có luật phân tích cú pháp kỳ lạ như <td>
). Điều quan trọng khác cần lưu ý là DocumentFragment
kết quả sẽ không có an associated browsing context: tức là, thuộc tính ownerDocument
của các phần tử sẽ có thuộc tính defaultView
với giá trị null, tài nguyên sẽ không tải, v.v.
Tất cả các lời gọi đến nhà máy fragment()
đều dẫn đến các DocumentFragment
chia sẻ cùng một Document
chủ sở hữu mẫu. Điều này cho phép nhiều cuộc gọi đến fragment()
mà không có thêm chi phí. Tuy nhiên, điều này cũng có nghĩa là cuộc gọi đến fragment()
không thể được tùy chỉnh bằng bất kỳ tùy chọn nào.
Lưu ý rằng việc xuất dữ liệu không dễ dàng như việc làm với các đối tượng DocumentFragment
như việc làm với các đối tượng JSDOM
đầy đủ. Nếu bạn cần xuất dữ liệu DOM của bạn, bạn có thể sử dụng trực tiếp hàm tạo JSDOM
. Tuy nhiên, đối với trường hợp đặc biệt của một fragment chứa một phần tử duy nhất, việc làm này khá dễ dàng thông qua các phương tiện bình thường:
const frag = JSDOM.fragment(`<p>Hello</p>`);
console.log(frag.firstChild.outerHTML); // logs "<p>Hello</p>"
Những tính năng đáng chú ý khác
Hỗ trợ Canvas
jsdom bao gồm hỗ trợ sử dụng gói canvas để mở rộng bất kỳ phần tử <canvas>
nào với API canvas. Để làm việc này, bạn cần bao gồm canvas
làm phụ thuộc trong dự án của bạn, như là một phần hàng đồng của jsdom
. Nếu jsdom có thể tìm thấy gói canvas
, nó sẽ sử dụng nó, nhưng nếu nó không tồn tại, thì các phần tử <canvas>
sẽ hoạt động giống như các phần tử <div>
. Kể từ phiên bản jsdom v13, phiên bản 2.x của canvas
được yêu cầu; phiên bản 1.x không được hỗ trợ nữa.
Phân tích mã hóa
Ngoài việc cung cấp một chuỗi, hàm tạo JSDOM
cũng có thể được cung cấp dữ liệu nhị phân dưới dạng một đối tượng Node.js Buffer hoặc một loại dữ liệu nhị phân chuẩn của JavaScript như ArrayBuffer
, Uint8Array
, DataView
, v.v. Khi điều này được thực hiện, jsdom sẽ sniff the encoding từ các byte được cung cấp, quét để tìm các thẻ <meta charset>
giống như trình duyệt.
Nếu tùy chọn contentType
được cung cấp chứa một tham số charset
, mã hóa đó sẽ ghi đè lên mã hóa được phát hiện—trừ khi có một BOM UTF-8 hoặc UTF-16 hiện diện, trong trường hợp đó thì chúng sẽ được ưu tiên. (Một lần nữa, điều này giống như trình duyệt.)
Việc phát hiện mã hóa này cũng áp dụng cho JSDOM.fromFile()
và JSDOM.fromURL()
. Trong trường hợp sau, bất kỳ tiêu đề Content-Type
nào được gửi cùng với phản hồi sẽ được ưu tiên, theo cùng cách như tùy chọn contentType
của hàm tạo.
Lưu ý rằng trong nhiều trường hợp, việc cung cấp byte theo cách này có thể tốt hơn việc cung cấp một chuỗi. Ví dụ, nếu bạn cố gắng sử dụng API buffer.toString("utf-8")
của Node.js, Node.js sẽ không loại bỏ bất kỳ BOM nào ở đầu. Nếu sau đó bạn đưa chuỗi này cho jsdom, nó sẽ hiểu nó một cách đen trắng, để lại BOM nguyên vẹn. Nhưng mã giải mã dữ liệu nhị phân của jsdom sẽ loại bỏ BOM ở đầu, giống như trình duyệt; trong những trường hợp như vậy, việc cung cấp buffer
trực tiếp sẽ cho kết quả mong muốn.
Đóng một jsdom
Các bộ hẹn giờ trong jsdom (được đặt bởi window.setTimeout()
hoặc window.setInterval()
) sẽ, theo định nghĩa, thực thi mã trong tương lai trong ngữ cảnh của cửa sổ. Vì không có cách nào để thực thi mã trong tương lai mà không giữ cho quá trình sống, các bộ hẹn giờ jsdom chưa hoàn thành sẽ giữ cho quá trình Node.js của bạn sống. Tương tự, vì không có cách nào để thực thi mã trong ngữ cảnh của một đối tượng mà không giữ cho đối tượng đó sống, các bộ hẹn giờ jsdom chưa hoàn thành sẽ ngăn chặn việc thu gom rác của cửa sổ mà chúng được lập lịch trình.
Nếu bạn muốn đảm bảo đóng cửa sổ jsdom, hãy sử dụng window.close()
, điều này sẽ chấm dứt tất cả các bộ hẹn giờ đang chạy (và cũng loại bỏ bất kỳ trình lắng nghe sự kiện nào trên cửa sổ và tài liệu).
Gỡ lỗi DOM bằng Chrome DevTools
Trong Node.js, bạn có thể gỡ lỗi các chương trình bằng Chrome DevTools. Xem official documentation để biết cách bắt đầu.
Mặc định, các thành phần jsdom được định dạng dưới dạng các đối tượng JS thông thường trong console. Để dễ dàng hơn cho việc gỡ lỗi, bạn có thể sử dụng jsdom-devtools-formatter, cho phép bạn kiểm tra chúng như các thành phần DOM thực sự.
Lưu ý
Việc tải script bất đồng bộ
Nhiều người thường gặp vấn đề với việc tải script bất đồng bộ khi sử dụng jsdom. Nhiều trang tải các script bất đồng bộ, nhưng không có cách nào để biết khi nào chúng hoàn thành việc tải và do đó khi nào là thời điểm tốt để chạy mã của bạn và kiểm tra cấu trúc DOM kết quả. Đây là một giới hạn cơ bản; chúng ta không thể dự đoán script trên trang web sẽ làm gì, và do đó không thể cho bạn biết khi nào chúng hoàn thành việc tải thêm script.
Điều này có thể được giải quyết bằng một số cách. Cách tốt nhất, nếu bạn kiểm soát trang cụ thể, là sử dụng bất kỳ cơ chế nào được cung cấp bởi trình tải script để phát hiện khi tải xong. Ví dụ, nếu bạn đang sử dụng trình tải module như RequireJS, mã có thể trông như sau:
// On the Node.js side:
const window = (new JSDOM(...)).window;
window.onModulesLoaded = () => {
console.log("ready to roll!");
};
<!-- Inside the HTML you supply to jsdom -->
<script>
requirejs(["entry-module"], () => {
window.onModulesLoaded();
});
</script>
Nếu bạn không kiểm soát trang, bạn có thể thử các biện pháp tạm thời như kiểm tra sự có mặt của một phần tử cụ thể.
Để biết thêm chi tiết, xem cuộc thảo luận tại #640, đặc biệt là @matthewkastor‘s insightful comment.
Phần chưa được thực hiện của nền tảng web
Mặc dù chúng tôi thích thêm các tính năng mới vào jsdom và cập nhật nó với các quy chuẩn web mới nhất, nhưng nó vẫn thiếu nhiều API. Xin hãy tự do gửi một vấn đề cho bất cứ điều gì bị thiếu, nhưng chúng tôi là một nhóm nhỏ và bận rộn, vì vậy một yêu cầu “pull request” có thể hoạt động tốt hơn.
Một số tính năng của jsdom được cung cấp bởi các phụ thuộc của chúng tôi. Tài liệu đáng chú ý trong mối quan hệ đó bao gồm danh sách supported CSS selectors cho bộ máy chọn CSS của chúng tôi, nwsapi.
Ngoài những tính năng mà chúng tôi chưa thực hiện, có hai tính năng chính hiện đang nằm ngoài phạm vi của jsdom. Chúng là:
- Navigation : khả năng thay đổi đối tượng toàn cục và tất cả các đối tượng khác khi nhấp vào một liên kết hoặc gán
location.href
hoặc tương tự. - Layout : khả năng tính toán vị trí mà các phần tử sẽ được bố trí hình ảnh theo kết quả của CSS, ảnh hưởng đến các phương thức như
getBoundingClientRects()
hoặc các thuộc tính nhưoffsetTop
.
Hiện tại, jsdom có những hành vi giả mạo cho một số khía cạnh của các tính năng này, chẳng hạn như gửi một thông báo lỗi “chưa được thực hiện” "jsdomError"
đến bảng điều khiển ảo cho điều hướng, hoặc trả về giá trị không cho nhiều thuộc tính liên quan đến bố cục. Thường bạn có thể vượt qua những hạn chế này trong mã của bạn, ví dụ bằng cách tạo các trường hợp JSDOM
mới cho mỗi trang bạn “điều hướng” đến trong suốt một lần thu thập dữ liệu, hoặc sử dụng Object.defineProperty()
để thay đổi những phương thức và trình lấy liên quan đến bố cục khác nhau trả về gì.
Lưu ý rằng các công cụ khác trong cùng một không gian, chẳng hạn như PhantomJS, thực sự hỗ trợ những tính năng này. Trên wiki, chúng tôi có một bài viết viết đầy đủ hơn về jsdom vs. PhantomJS.
Hỗ trợ jsdom
jsdom là một dự án do cộng đồng thực hiện, được duy trì bởi một nhóm volunteers. Bạn có thể hỗ trợ jsdom bằng cách:
- Getting professional support for jsdom như một phần của gói đăng ký Tidelift. Tidelift giúp làm cho mã nguồn mở bền vững đối với chúng tôi trong khi đảm bảo cho các nhóm về bảo trì, cấp phép và bảo mật.
- Contributing trực tiếp đến dự án.
Được giúp đỡ
Nếu bạn cần trợ giúp về jsdom, xin đừng ngần ngại sử dụng bất kỳ kênh sau đây:
- Trang mailing list (tốt nhất cho câu hỏi “làm thế nào tôi có thể”)
- Trang issue tracker (tốt nhất cho báo cáo lỗi)
- Phòng trò chuyện Matrix: #jsdom:matrix.org
Thông tin tải xuống:
Tác giả: jsdom
Mã nguồn: https://github.com/jsdom/jsdom
Giấy phép: MIT license