jsdom 中文文档(纯翻译)

jsdom是一个纯粹由 javascript 实现的一系列 web标准,特别是 WHATWG 组织制定的DOMHTML 标准,用于在 nodejs 中使用。大致上来讲,该项目的目标是模拟足够的Web浏览器子集,以便用于测试和挖掘真实世界的Web应用程序。javascript

最新版本的 jsdom 运行环境须要 node.js v6或者更高的版本。(jsdom v10如下版本在 nodejs v4如下仍然可用,可是咱们已经不支持维护了)html

v10版本的 jsdom 拥有全新的 API(以下所述).旧的 API 如今仍然支持;详细的参照文档java

基本用法

const jsdom = require("jsdom");
const { JSDOM } = jsdom;

为了使用 jsdom,主要用到jsdom主模块的一个命名导出的 jsdom 构造函数。往构造器传递一个字符串,将会获得一个 jsdom 构造实例对象,这个对象有不少实用的属性,特别是 window 对象:node

const dom = new JSDOM(`<!DOCTYPE html><p>Hello world</p>`);
console.log(dom.window.document.querySelector("p").textContent); // "Hello world"

(请注意,jsdom会像浏览器同样解析您传递的HTML,包括隐含的<html><head><body>标记)git

生成的对象是JSDOM类的一个实例,其中包括 window 对象在内的许多有用的属性和方法。通常来讲,它能够用来从“外部”对jsdom进行操做,而这些操做对于普通DOM API来讲是不可能的。对于不须要任何功能的简单场景,咱们推荐使用相似的编码模式github

const { window } = new JSDOM(`...`);
// or even
const { document } = (new JSDOM(`...`)).window;

下面是关于JSDOM类所能作的一切的完整文档,在“JSDOM对象API”部分。web

定制 jsdom

JSDOM构造函数接受第二个参数,能够用如下方式定制您的jsdom。npm

简单选项

const dom = new JSDOM(``, {
  url: "https://example.org/",
  referrer: "https://example.com/",
  contentType: "text/html",
  userAgent: "Mellblomenator/9000",
  includeNodeLocations: true
});
  • url 设置的值能够经过window.locationdocument.URLdocument.documentURI来返回,并会影响文档中相关URL的解析以及获取子资源时使用的同源限制和referrer。默认值为"about:blank"
  • referrer 仅仅影响document.referrer的值。默认没有引用(即为空字符串)。
  • contentType 影响document.contentType的值,是按照HTML解析文档仍是 XML来解析。它的值若是不是text/htmlXML mime type 值的话将会抛出异常。默认值为"text/html"
  • userAgent 影响navigator.userAgent的值以及请求子资源时发送的User-Agent头。默认值为Mozilla / 5.0($ {process.platform})AppleWebKit / 537.36(KHTML,如Gecko)jsdom / $ {jsdomVersion}
  • includeNodeLocations 保留由HTML解析器生成的位置信息,容许您使用nodeLocation()方法(以下所述)检索它。

它还能确保在<script>元素内运行的代码的异常堆栈跟踪中报告的行号是正确的。
默认值为false以提供最佳性能,而且不能与XML内容类型一块儿使用,由于咱们的XML解析器不支持位置信息。canvas

请注意urlreferrer在使用以前已经被规范化了,例如
若是你传入"https:example.com",jsdom会自动规范化解释为"https://example.com/"
若是你传递了一个不可解析的URL,该调用将抛出错误。
(URL根据URL标准进行分析和序列化。)api

执行脚本

jsdom最强大的功能是它能够在jsdom中执行脚本。这些脚本能够修改页面的内容并访问jsdom实现的全部Web平台API。

可是,这在处理不可信内容时也很是危险。
jsdom沙箱并非万无一失的,在DOM的<script>内部运行的代码若是足够深刻,就能够访问Node.js环境,从而访问您的计算机。
所以,默认状况下,执行嵌入在HTML中的脚本的功能是禁用的:

const dom = new JSDOM(`<body>
  <script>document.body.appendChild(document.createElement("hr"));</script>
</body>`);

// 脚本默认将不能执行:
dom.window.document.body.children.length === 1;

要在页面内启用脚本,可使用runScripts:"dangerously"选项:

const dom = new JSDOM(`<body>
  <script>document.body.appendChild(document.createElement("hr"));</script>
</body>`, { runScripts: "dangerously" });

// 脚本将执行并修改 DOM:
dom.window.document.body.children.length === 2;

咱们再次强调只有在提供给jsdom的代码是你已知道是安全的代码时方可以使用它。若是您运行了任意用户提供的或Internet上的不可信的Node.js代码,可能会危及您的计算机。

假如你想经过<script src="">来执行外部脚本,你须要确保已经加载了它们。为此,请添加选项resources:"usable" 以下所述

请注意,除非runScripts设置为"dangerously",不然事件处理程序属性(如<div onclick =“”>)也将不起做用。(可是,事件处理函数属性,好比div.onclick = ...,将无视runScripts参数 而且会起做用)

若是您只是试图从“外部”执行脚本,而不是经过<script>元素(和内联事件处理程序)从内部运行“,则可使用runScripts: "outside-only"选项,该选项会启用window.eval

const window = (new JSDOM(``, { runScripts: "outside-only" })).window;

window.eval(`document.body.innerHTML = "<p>Hello, world!</p>";`);
window.document.body.children.length === 1;

因为性能缘由,默认状况下会关闭此功能,但能够安全启用。

请注意,咱们强烈建议不要试图经过将jsdom和Node全局环境混合在一块儿(例如,经过执行global.window = dom.window)来“执行脚本”,而后在Node全局环境中执行脚本或测试代码。相反,您应该像对待浏览器同样对待jsdom,并使用window.evalrunScripts: "dangerously"来运行须要访问jsdom环境内的DOM的全部脚本和测试。例如,这可能须要建立一个browserify包做为<script>元素执行 - 就像在浏览器中同样。

最后,对于高级用例,您可使用dom.runVMScript(脚本)方法,以下所述。

伪装成一个视觉浏览器

jsdom没有渲染可视内容的能力,而且默认状况下会像无头浏览器同样工做。它经过API(如document.hidden)向网页提供提示,代表其内容不可见。

pretendToBeVisual选项设置为true时,jsdom会伪装它正在呈现并显示内容。它是这样作的:

  • 更改document.hidden以返回false而不是true
  • 更改document.visibilityState以返回“visible”而不是“prerender”
  • 启用window.requestAnimationFrame()window.cancelAnimationFrame()方法,不然不存在
const window = (new JSDOM(``, { pretendToBeVisual: true })).window;

window.requestAnimationFrame(timestamp => {
  console.log(timestamp > 0);
});

请注意,jsdom仍然不作任何布局或渲染,所以这实际上只是伪装为可视化,而不是实现真正的可视化Web浏览器将实现的部分。

加载子资源

默认状况下,jsdom不会加载任何子资源,如脚本,样式表,图像或iframe。若是您但愿jsdom加载这些资源,则能够传递resources: "usable"选项,该选项将加载全部可用资源。资源列表以下:

  • frame 和 iframe,经过 <frame><iframe>实现
  • 样式,经过<link rel="stylesheet">
  • 脚本,经过<script>,可是前提是runScripts: "dangerously"设置了
  • 图片,经过<img>,可是前提是canvas(或者 canvas-prebuilt) npm 包已安装

将来,咱们计划经过此选项提供更多的资源加载定制,但如今只提供的两种模式:'default''usable'

虚拟控制台

像网页浏览器同样,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("error", () => { ... });
virtualConsole.on("warn", () => { ... });
virtualConsole.on("info", () => { ... });
virtualConsole.on("dir", () => { ... });
// ... etc. See https://console.spec.whatwg.org/#logging

请注意,最好在调用 new JSDOM()以前设置这些事件侦听器,由于在解析期间可能会发生错误或控制台调用脚本错误。)

若是你只是想将虚拟控制台输出重定向到另外一个控制台,好比默认的Node.js,你能够这样作

virtualConsole.sendTo(console);

还有一个特殊的事件,"jsdomError",它的触发将经过错误对象来记录jsdom自己的错误。这与错误消息在Web浏览器控制台中的显示方式相似,即便它们不是由console.error输出的。到目前为止,错误会按照下面的方式输出:

  • 加载或解析子资源时出错(脚本,样式表,frames和iframe)
  • 不是由window onerror事件处理程序处理的脚本执行错误,它将会返回true或调用event.preventDefault()
  • 因为调用jsdom没有实现的方法而致使的错误,例如window.alert,兼容性的 web 浏览器都实现了这些方法

若是您使用sendTo(c)将错误发送给c,则默认状况下,它将使用来自"jsdomError"事件的信息调用console.error。若是您但愿保持事件与方法调用的严格的一对一映射,而且可能本身处理"jsdomError",那么您能够执行

virtualConsole.sendTo(c, { omitJSDOMErrors: true });

Cookie jars(存储Cookie的容器)

像网页浏览器同样,jsdom也具备cookie jar的概念,存储HTTP cookie 。在文档的同一个域上一个URL,而且没有标记为HTTP only的cookies,能够经过document.cookie API来访问。此外,Cookie jar中的全部cookie都会影响子资源的http加载。

默认状况下,JSDOM构造函数将返回一个带有空cookie的实例。要建立本身的cookie jar并将其传递给jsdom,能够经过如下代码来覆盖默认值

const cookieJar = new jsdom.CookieJar(store, options);
const dom = new JSDOM(``, { cookieJar });

若是您想要在多个jsdoms中共享同一个cookie jar,或者提早使用特定的值来填充cookie jar,这将很是有用。

Cookie jar包由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>Hello</p>`, {
  beforeParse(window) {
    window.document.childNodes.length === 0;
    window.someCoolAPI = () => { /* ... */ };
  }
});

若是您但愿以某种方式修改环境,这尤为有用,例如添加jsdom不支持的Web API的填充程序。

JSDOM object API

一旦你构建了一个JSDOM对象,它将具备如下有用的功能:

Properties

window属性: window对象的key 从Window 对象检索而来
virtualConsolecookieJar:能够传入或者使用默认值

经过serialize()序列化document

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>";

经过nodeLocation(node)获取 dom 节点的源位置信息

nodeLocation()方法将查找DOM节点在源文档中的位置,并返回节点的parse5位置信息

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 }

请注意,只有您设置了includeNodeLocations选项才能使用此功能;因为性能缘由,节点位置默认为关闭。

使用runVMScript(script)运行vm建立的脚本

Node.js的内置vm模块容许您建立Script实例,这些脚本实例能够提早编译,而后在给定的“VM上下文”上运行屡次。在这个场景背后,jsdom Window是一个肯定的VM上下文。要访问此功能,请使用runVMScript()方法:

const { Script } = require("vm");

const dom = new JSDOM(``, { runScripts: "outside-only" });
const s = new Script(`
  if (!this.ran) {
    this.ran = 0;
  }

  ++this.ran;
`);

dom.runVMScript(s);
dom.runVMScript(s);
dom.runVMScript(s);

dom.window.ran === 3;

这是高级功能,除非您有特殊的需求,不然咱们建议坚持使用普通的DOM API(如window.eval()或document.createElement(“script”))。

经过reconfigure(settings)从新配置jsdom

window.top属性在规范中被标记为Unforgeable,这意味着它是一个不可配置的私有属性,所以在jsdom内运行的普通代码是不能覆盖或遮挡它的,即便使用Object.defineProperty

一样,目前在jsdom中是不可以处理navigation相关信息的(好比设置window.location.href ="https://example.com/");这样作会致使虚拟控制台发出"jsdomError",说明此功能未实现,而且没有任何变化,也将不会有新的WindowDocument对象,而且现有window.location对象仍保持当前全部相同的属性值。

可是,若是您从 jsdom 窗口以外进行演示,例如在一些建立jsdoms的测试框架中,可使用特殊的reconfigure()方法覆盖其中的一个或两个:

const dom = new JSDOM();

dom.window.top === dom.window;
dom.window.location.href === "about:blank";

dom.reconfigure({ windowTop: myFakeTopForTesting, url: "https://example.com/" });

dom.window.top === myFakeTopForTesting;
dom.window.location.href === "https://example.com/";

请注意,更改jsdom的URL将影响全部返回当前 document URL的API,例如window.locationdocument.URL`和document.documentURI,以及文档中相对URL的解析以及同源检查和提取子资源时使用的引用。可是,它不会执行导航到该URL的内容;DOM的内容将保持不变,而且不会建立WindowDocument`等新的实例。

便捷的 APIs

fromURL()

除了JSDOM构造函数自己以外,jsdom还提供了一个返回 Promise 的工厂方法,用于经过URL构建一个jsdom实例

JSDOM.fromURL("https://example.com/", options).then(dom => {
  console.log(dom.serialize());
});

若是URL有效且请求成功,则onFullfilled回调执行并返回JSDOM实例。任何URL重定向都将遵循其最终目的地。

fromURL()提供的参数选项与提供给JSDOM构造函数的选项相似,但具备如下额外的限制和后果:

  • urlcontentType 参数不能被提供
  • referrer 选项用做初始请求的HTTP Referer请求头
  • userAgent 选项用做任何请求的HTTP User-Agent请求头
  • 生成的jsdom的urlcontentTypereferrer是 由 http response来决定
  • 任何经过HTTP Set-Cookie响应头设置的cookie都存储在jsdom的cookie jar中。一样,已提供的cookie jar中的任何cookie都会做为HTTP Cookie请求标头发送。

初始的请求并不能无限定制到像request npm 包同样的程度;fromURL()旨在为大多数状况提供便利的API。若是您须要更好地控制初始请求,您应该本身执行它,而后手动使用JSDOM构造函数。

fromFile()

fromURL()相似,jsdom还提供了一个fromFile()工厂方法,用于从文件名构建jsdom

JSDOM.fromFile("stuff.html", options).then(dom => {
  console.log(dom.serialize());
});

若是能够打开给定的文件,则onFullfilled回调执行并返回JSDOM实例。和Node.js API同样,文件名是相对于当前工做目录的。

fromFile()提供的选项与提供给JSDOM构造函数的选项类似,但具备如下额外的默认值:

  • url选项将默认为给定文件名相对应的文件URL,而不是"about:blank"
  • 假如给定的文件名是以.xhtml或者.xml为后缀的话,contentType选项默认为"application/xhtml+xml";反之为"text/html"

fragment()

对于最简单的状况,你可能不须要一个完整的JSDOM实例及其全部相关的功能。您甚至可能不须要WindowDocument!相反,你只须要解析一些HTML片断,并得到一个你能够操做的DOM对象。为此,咱们提供了fragment(),它能够从给定的字符串中建立一个DocumentFragment

const frag = JSDOM.fragment(`<p>Hello</p><p><strong>Hi!</strong>`);

frag.childNodes.length === 2;
frag.querySelector("strong").textContent = "Why hello there!";
// etc.

fragDocumentFragment的实例对象,其内容是经过提供的字符串解析建立的。解析是经过使用<template>元素完成的,所以您能够在其中包含任何元素(包括具备奇怪解析规则的元素,如<td>)。

fragment()工厂函数的全部调用结果的DocumentFragments实例都会共享相同的DocumentWindow。这容许屡次调用fragment()而没有额外的开销。但这也意味着对fragment()的调用不能用任何选项自定义。

请注意,对DocumentFragments的序列化并不像使用JSDOM对象那样容易。若是你须要序列化你的DOM,你应该直接使用JSDOM构造函数。但对于包含单个元素的片断的特殊状况,经过常规方法就很容易作到。

const frag = JSDOM.fragment(`<p>Hello</p>`);
console.log(frag.firstChild.outerHTML); // logs "<p>Hello</p>"

其余值得注意的功能

支持 Canvas

jsdom支持使用canvascanvas-prebuilt包来扩展任何使用canvas API的<canvas>元素。为了作到这一点,您须要将canvas做为依赖项加入到您的项目中,和 jsdom包并列。若是jsdom能够找到canvas包,它将使用它,可是若是它不存在,那么<canvas>元素的行为就像<div>同样。

编码嗅探

除了提供一个字符串外,JSDOM构造函数还支持Node.js Buffer或标准JavaScript二进制数据类型(如ArrayBuffer,Uint8Array,DataView等)的形式提供二进制数据。当完成后,jsdom将从提供的字节进行嗅探编码,就像浏览器扫描<meta charset>标签同样。

这种编码嗅探也适用于JSDOM.fromFile()JSDOM.fromURL()。在后一种状况下,就像在浏览器中同样,任何与response响应一块儿发送的Content-Type头信息优先级更高。

请注意,在许多状况下,提供字节这种方式可能比提供字符串更好。例如,若是您试图使用Node.js的buffer.toString('utf-8')API,则Node.js将不会去除任何前导BOM。若是您将此字符串提供给jsdom,它会逐字解释,从而使BOM保持不变。但jsdom的二进制数据解码代码将剥离前导的BOM,就像浏览器同样;在这种状况下,直接提供buffer将会获得想要的结果。

关闭一个jsdom

jsdom中定义的定时器(经过window.setTimeoutwindow.setInterval设置)将在window上下文中执行代码。因为进程在不活跃的状况下没法执行将来的定时器代码,因此卓越的jsdom定时器将保持您的Node.js进程处于活动状态。一样,对象不活跃的状况下也没有办法在对象的上下文中执行代码,卓越的jsdom定时器将阻止垃圾回收调度它们的window。

若是你想确保关闭jsdom窗口,使用window.close(),它将终止全部正在运行的定时器(而且还会删除 windowdocument上的任何事件监听器)。

在Web浏览器中运行jsdom

使用browserify模块,jsdom某些方面也支持在Web浏览器中运行。也就是说,在Web浏览器中,您可使用被browserify模块编译过的jsdom去建立彻底独立的普通JavaScript对象集,其外观和行为与浏览器的现有DOM对象很是类似,但彻底独立于它们,也就是"虚拟DOM"!

jsdom的主要目标对象仍然是Node.js,所以咱们使用仅存在于最新Node.js版本(即Node.js v6 +)中的语言特性功能。所以,在旧版浏览器可能没法正常工做。(即便编译也不会有多大帮助:咱们计划在jsdom v10.x的整个过程当中普遍使用Proxy。)

值得注意的是,jsdom在web worker中能很好的运行。项目的开发者@lawnsea使这一功能点成为可能,他发表了一篇关于他的项目的论文,该论文就使用了这种能力。

在Web浏览器中运行jsdom时,并不是全部的工做都完美。有些状况下,这是因为基础的条件限制(好比没有文件系统访问),但有些状况下也是由于咱们没有花足够的时间去进行适当的小调整。欢迎你们来提BUG。

使用Chrome Devtools调试DOM

从Node.js v6开始,您可使用Chrome Devtools来调试程序。请参阅官方文档了解如何使用。

默认状况下,jsdom元素在控制台中被格式化为普通的旧JS对象。为了便于调试,可使用jsdom-devtools-formatter,它可让你像真正的DOM元素同样调试它们。

注意事项

异步脚本加载

使用jsdom时,开发者在加载异步脚本时常常遇到麻烦。许多页面异步加载脚本,但没法分辨脚本何时完成,所以没法知道什么时候是运行代码并检查生成的DOM结构的好时机。这是一个基本的限制;咱们没法预测网页上的哪些脚本会作什么,所以没法告诉您脚本什么时候加载完毕。

这个问题能够经过几种方法来解决。若是您能控制页面逻辑,最好的方法是使用脚本加载器提供的机制来检测什么时候加载完成。例如,若是您使用像RequireJS这样的模块加载器,代码可能以下所示:

// 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>

若是您不能控制该页面,则能够尝试其余解决方法,例如轮询检查特定元素是否存在。有关更多详细信息,请查看#640中的讨论,尤为是@ matthewkastor深入看法

共享的构造函数和原型

目前,对于大多数Web平台API,jsdom在多个看似独立的jsdoms之间共享相同的类定义。这将意味着,可能会出现如下状况

const dom1 = new JSDOM();
const dom2 = new JSDOM();

dom1.window.Element.prototype.expando = "blah";
console.log(dom2.window.document.createElement("frameset").expando); // logs "blah"

这主要是出于性能和内存的缘由:若是在Web平台上每次建立jsdom时,建立全部类的单独副本,开销将会至关昂贵。

尽管如此,咱们仍然有兴趣在有一天提供一个选项配置来建立一个“独立”的jsdom,但要牺牲一些性能。

新API中缺失的功能

与v9.x以前的旧版jsdom API相比,新API显然缺乏对资源加载的精细控制。先前版本的jsdom容许您设置request时使用的选项(既能够用于初始请求,也能够用于旧版本的JSDOM.fromURL()和子资源请求)。他们还容许您控制请求哪些子资源并将其应用于主文档,以便您能够下载样式表,但不下载脚本文件。最后,他们提供了一个可定制的资源加载器,能够拦截任何传出的请求并用彻底合成的response 响应来结束。

以上这些功能还没有在新的jsdom API中实现,尽管咱们也但愿尽快将它们添加回来,但不幸的是,这须要至关大的幕后工做去实施。

同时,请随时使用旧的jsdom API来访问此功能。它一直处于支持和维护中,但它不会得到新功能。旧的文档位于lib/old-api.md中。

未实现的Web平台部分

目前jsdom中有不少缺失的API,尽管咱们也想要在jsdom中添加新的功能并保持最新的Web规范。请随时为缺失的任何内容提交issue,但咱们是一个很小而且忙碌的团队,所以你们一块儿来提交 pull request可能会更好。

除了咱们还没有拥有的功能以外,还有两个主要功能目前超出了jsdom的范围。这些是:

  • Navigation:在点击连接或赋值location.href或相似操做时能够更改全局对象和全部其余的对象。
  • Layout:计算CSS元素的视觉布局的能力,这会影响诸如getBoundingClientRects()或者诸如offsetTop之类的属性

目前,jsdom对某些功能的某些方面具备虚拟行为,例如操做navigation 时向虚拟控制台发送“未实现的”"jsdomError",或者为许多与布局相关的属性返回0。您一般能够在代码中解决这些限制,例如经过在爬网过程当中为每一个页面建立新的JSDOM实例,或使用Object.defineProperty更改各类与布局相关的getter和方法的返回值

请注意,相同领域中的其余工具(如PhantomJS)确实支持这些功能。在wiki上,咱们有关于jsdom vs. PhantomJS的更完整的比较介绍。

获取帮助

若是您须要jsdom的帮助,请随时使用如下任何方式:

  • 邮件组(问题最好以"how do i"的形式)
  • 报iusse(最好用BUG 报告)
  • IRC频道:#jsdom on freenode

特别声明

以上文档翻译自开源项目 jsdom,若有翻译错误,欢迎指正。

jsdom 原文连接

jsdom 项目连接

jsdom 中文翻译wiki连接

原文博客地址

相关文章
相关标签/搜索