JQuery 在
import
上初始化。如果 window
和 window.document
存在(而 module
不存在),JQuery 将保存引用并随后使用它们。
有没有办法在 JQuery 被
import
编辑后“重新初始化”或“重置”它,以给它一个对 window
和 document
不同的引用?
.
├── .eslintrc.json
├── .prettierrc
├── index.spec.js
├── package.json
├── README.md
├── spec/
│ └── support/
│ ├── jasmine-spec.json
│ ├── logger.js
│ ├── slow-spec-reporter.js
│ └── type-check.js
├── template1.html
└── template2.html
import { JSDOM } from 'jsdom';
describe('jquery', () => {
it('uses the currently available document', async () => {
const { document: template1Document, jquery: template1Jquery } = await parseHtml('template1.html');
expect(template1Document.querySelector('p').textContent).toEqual('Hello world');
expect(template1Jquery.find('p').text()).toEqual('Hello world');
const { document: template2Document, jquery: template2Jquery } = await parseHtml('template2.html');
expect(template2Document.querySelector('p').textContent).toEqual('Goodbye world');
expect(template2Jquery.find('p').text()).toEqual('Goodbye world'); // !!!
// Expected 'Hello world' to equal 'Goodbye world'.
});
});
async function parseHtml(fileName) {
const dom = await JSDOM.fromFile(fileName, {
url: 'http://localhost',
runScripts: 'dangerously',
resources: 'usable',
pretendToBeVisual: true,
});
const window = dom.window;
const document = window.document;
globalThis.window = window;
globalThis.document = document;
const dynamicImport = await import('jquery');
const $ = dynamicImport.default;
return {
document: document,
jquery: $(`html`),
};
}
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>template1.html</title>
</head>
<body>
<p>Hello world</p>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>template2.html</title>
</head>
<body>
<p>Goodbye world</p>
</body>
</html>
这是代码摘录,但您可以在这里找到完整的 github 存储库。
我正在开发一个仅前端的单页应用程序项目。尽管该项目仅在浏览器中运行,但自动化测试在 Node.JS 中运行。测试在 JSDOM 中加载 html 并执行部分生产代码。1
创建时,JSDOM 返回一个在 Node.JS 中工作的 DOM API,包括一个
window
对象。如果没有这个,jQuery 将在 import 时出错,因为现代版本有这样的代码:
(function(global, factory) {
"use strict";
if (typeof module === "object" && typeof module.exports === "object") {
// For CommonJS and CommonJS-like environments where a proper `window`
// is present, execute the factory and get jQuery.
// For environments that do not have a `window` with a `document`
// (such as Node.js), expose a factory as module.exports.
// This accentuates the need for the creation of a real `window`.
// e.g. var jQuery = require("jquery")(window);
// See ticket trac-14549 for more info.
module.exports = global.document ?
factory(global, true) :
function(w) {
if (!w.document) {
throw new Error("jQuery requires a window with a document");
}
return factory(w);
};
} else {
factory(global);
}
// Pass this if window is not defined yet
})(typeof window !== "undefined" ? window : this, function(window, noGlobal) {
console.log(`window`, window);
console.log(`noGlobal`, noGlobal);
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
如您所见,这是作为 importing jQuery 的副作用执行的。这引起了相当大的头痛,因为在导入 jQuery 之前创建 JSDOM 并不总是那么简单。
例如,如果生产代码导入 jQuery 并且测试导入生产代码,则 JSDOM(因此
window
)将不存在。这会引发错误。
但这里有一个更重要的用例:我希望能够在测试中使用不同的 HTML 文件,但这限制了我每次测试运行只能使用一个文件。
郑重声明,我使用的是 jQuery 3.7.1,但 stackoverflow 代码片段没有给我选择该版本的选项。我认为这很好,因为据我所知,这段代码在两者中是相同的。
1:不幸的是,这意味着违背官方 JSDOM 建议。但在this上下文中,我看不到解决办法。
一切都已在您的代码中:
// For CommonJS and CommonJS-like environments where a proper `window`
// is present, execute the factory and get jQuery.
// For environments that do not have a `window` with a `document`
// (such as Node.js), expose a factory as module.exports.
// This accentuates the need for the creation of a real `window`.
// e.g. var jQuery = require("jquery")(window);
// See ticket trac-14549 for more info.
这意味着,如果在全局范围内没有
window
和 document
可用,则导入将提供它使用的工厂,而不是使用可用的窗口对象执行它并返回实例化的 jQuery。
如果您更改 parseHTML 代码以利用此功能:
async function parseHtml(fileName) {
const dom = await JSDOM.fromFile(fileName, {
url: 'http://localhost',
runScripts: 'dangerously',
resources: 'usable',
pretendToBeVisual: true,
});
//const window = dom.window;
//const document = window.document;
//globalThis.window = window;
//globalThis.document = document;
const dynamicImport = await import('jquery');
//const $ = dynamicImport.default;
const $ = dynamicImport.default(dom.window);
return {
document: document,
jquery: $(`html`),
};
}
如果您没有使窗口对象在全局范围内可用,您将收到 jQuery 工厂。然后,您将能够通过传递您关心的窗口对象,按照您需要的方式实例化它。
有关解决方案不起作用的更多详细信息
当您导入代码/模块时,导入文件的解析和执行仅完成一次(在第一次导入时),并且结果值保存在缓存中,因此以后的导入不需要那么多工作,但这会导致您的导入值始终相同。
纸条上清楚地写着:
// For environments that do not have a `window` with a `document`
// (such as Node.js), expose a factory as module.exports.
// This accentuates the need for the creation of a real `window`.
// e.g. var jQuery = require("jquery")(window);
JSDOM 使您可以访问
window
,将窗口传递到 jQuery 工厂 以创建附加到该窗口的 jQuery 对象。这是一个最小的例子:
const { JSDOM } = require("jsdom");
const jqFactory = require("jquery");
async function parseHtml(fileName) {
const dom = await JSDOM.fromFile(fileName, {
url: 'http://localhost'
});
const jQuery = jqFactory(dom.window);
return {
jQuery: jQuery,
window: dom.window
};
};
(async function() {
const doc1 = await parseHtml('template1.html');
const doc2 = await parseHtml('template2.html');
// works
console.log(doc1.jQuery('p').text());
console.log(doc2.jQuery('p').text());
// works
console.log(doc1.window.$('p').text());
console.log(doc2.window.$('p').text());
// works
console.log(doc1.window.$.fn.jquery);
console.log(doc2.window.$.fn.jquery);
})();