有没有办法重新初始化 jQuery?

问题描述 投票:0回答:2

问题

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

./index.spec.js

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`),
  };
}

./template1.html

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>template1.html</title>
  </head>
  <body>
    <p>Hello world</p>
  </body>
</html>

./template2.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上下文中,我看不到解决办法。

javascript jquery dom
2个回答
1
投票

一切都已在您的代码中:

  // 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 工厂。然后,您将能够通过传递您关心的窗口对象,按照您需要的方式实例化它。

有关解决方案不起作用的更多详细信息

当您导入代码/模块时,导入文件的解析和执行仅完成一次(在第一次导入时),并且结果值保存在缓存中,因此以后的导入不需要那么多工作,但这会导致您的导入值始终相同。


0
投票

纸条上清楚地写着:

// 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);
})();
© www.soinside.com 2019 - 2024. All rights reserved.