有办法重新初始化jQuery吗?

6ioyuze2  于 9个月前  发布在  jQuery
关注(0)|答案(2)|浏览(110)

问题
JQuery在import上初始化。如果windowwindow.document存在(而module不存在),JQuery保存引用并在此后使用它们。
有没有一种方法可以“重新初始化”或“重置”JQuery后,它已经被import艾德给给予不同的引用windowdocument

A failing testcase part 1

项目结构

.
├── .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>


这是代码的摘录,但你可以在这里找到a complete github repository
重新初始化一个或几个import艾德jquery是不够的,所有的jquery引用都需要重新初始化。例如,如果一个生产文件以import jQuery from 'jquery'开头,那么jQuery需要是重新初始化的jquery的引用。这很难解释,所以我将用另一个失败的测试案例来说明我的意思:

一个失败的测试用例第二部分

项目结构

.
├── .eslintrc.json
├── .prettierrc
├── index.spec.js
├── package.json
├── prod-code.js
├── README.md
├── spec/
│   └── support/
│       ├── jasmine-spec.json
│       ├── logger.js
│       ├── slow-spec-reporter.js
│       └── type-check.js
├── template1.html
├── template2.html
└── test-setup.js

./index.spec.js

import * as testSetup from './test-setup.js';
import * as prodCode from './prod-code.js';

describe('jquery', () => {
  it('dynamic imports affect static imports template1', async () => {
    await testSetup.parseHtmlWithDynamicImport('template1.html');

    const { document: template1Document, jquery: template1Jquery } =
      await prodCode.parseHtmlWithJQueryStaticImport('template1.html'); // !!!
    // Error: jQuery requires a window with a document
    expect(template1Document.querySelector('p').textContent).toEqual(
      'Hello world'
    );
    expect(template1Jquery.find('p').text()).toEqual('Hello world');
  });

  it('dynamic imports affect static imports template2', async () => {
    await testSetup.parseHtmlWithDynamicImport('template2.html');

    const { document: template2Document, jquery: template2Jquery } =
      await prodCode.parseHtmlWithJQueryStaticImport('template2.html'); // !!!
    // Error: jQuery requires a window with a document
    expect(template2Document.querySelector('p').textContent).toEqual(
      'Goodbye world'
    );
    expect(template2Jquery.find('p').text()).toEqual('Goodbye world');
  });
});

./test-setup.js

import { JSDOM} from 'jsdom'

export async function parseHtmlWithDynamicImport(fileName) {
    const dom = await JSDOM.fromFile(fileName, {
      url: 'http://localhost',
      runScripts: 'dangerously',
      resources: 'usable',
      pretendToBeVisual: true,
    });

    const dynamicImport = await import('jquery');
    const $ = dynamicImport.default(dom.window);
    return {
      document: dom.window.document,
      jquery: $(`html`),
    };
  }

./prod-code.js

import { JSDOM } from 'jsdom';
import jQuery from 'jquery';

export async function parseHtmlWithJQueryStaticImport(fileName) {
  const dom = await JSDOM.fromFile(fileName, {
    url: 'http://localhost',
    runScripts: 'dangerously',
    resources: 'usable',
    pretendToBeVisual: true,
  });

  return {
    document: dom.window.document,
    jquery: 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>


这是代码的摘录,但you can find a complete github repository在这里。

上下文

我正在开发一个仅限前端的单页应用项目。尽管该项目仅在浏览器中运行,但自动化测试在Node.JS中运行。测试在JSDOM中加载html并执行部分生产代码。1
在创建时,JSDOM返回一个在Node.JS中工作的DOM API,包括一个window对象。如果没有这个,jQuery将在导入时出错,因为现代版本的have code like this

(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)还不存在。~~这会抛出一个错误。~~当以后使用它时,window不会被设置。
但这里有一个更重要的用例:我希望能够在测试中使用不同的HTML文件,但这限制了我每次测试只能使用一个。
总有一天,我想把这个测试代码作为一个测试框架向公众发布。有些人可能会显式地使用import jQuery from 'jquery'import $ from 'jquery',或者在全局范围内使用jQuery/$。理想情况下,他们不必修改他们的生产代码来使用我的框架。

注意事项

我使用的是jQuery 3.7.1,但是stackoverflow代码片段并没有给予我选择该版本的选项,我认为这很好,因为据我所知,这段代码在两个版本中是相同的。
1:不幸的是,这意味着违背了JSDOM的官方建议。但在 this 上下文中,我看不到解决方法。

o2gm4chl

o2gm4chl1#

一切都在你的代码中:

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

字符串
这意味着如果在全局作用域中没有document可用的window,则导入将提供它使用的工厂,而不是使用可用的窗口对象执行它并返回示例化的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`),
  };
}


如果你没有让window对象在全局作用域中可用,你将收到jQuery工厂。然后你就可以通过传递你关心的window对象,以你需要的方式示例化它。

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

当你导入代码/模块时,导入文件的解析和执行只进行一次(第一次导入时),结果值保存在缓存中,所以以后的导入不需要那么多工作,但这会导致导入值总是相同的。

vdzxcuhz

vdzxcuhz2#

再读一遍这张纸条:

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

字符串
在html解析函数中,您可以获得对 *jQuery工厂 * 的引用,您可以使用它来创建 *jQuery对象 *。
也就是说,你可以使用沿着以下几行的东西来初始化OP中描述的两种场景中的jQuery:

let jQuery_;
  if (typeof jQuery !== 'undefined') {
    // jQuery already imported statically using
    // import jQuery from 'jquery'
    jQuery_ = jQuery(dom.window);
  } else {
    // use dynamic import as described in
    // the comment inside jQuery code
    const dynamicImport = await import('jquery');
    jQuery_ = dynamicImport.default(dom.window);
  }
  // either way, we use jQuery factory to create jQuery
  // object bound to the document inside dom.window

  // PS: jQuery is different from jQuery('html')
  // you can do jQuery.ajax() and jQuery.browser
  // but you can't do jQuery("html").ajax() or jQuery.browser
  return {
    document: dom.window.document,
    jquery: jQuery_('html'),
  };

相关问题