jquerynode.js

Mocking a global jQuery object in Node.js


Can someone please explain to me why the upper code works as expected but the lower one does not? Thanks a lot!

$ nvm exec 22 node
Running node v22.15.1 (npm v10.9.2)
Welcome to Node.js v22.15.1.
Type ".help" for more information.
> const { JSDOM } = require('jsdom');
undefined
> const dom = new JSDOM("");
undefined
> var $ = require('jquery')(dom.window);
undefined
> $.extend
[Function (anonymous)]
> $(dom.window.document)
jQuery {
  '0': Document {
    location: [Getter/Setter],
    [Symbol(SameObject caches)]: [Object: null prototype] {
      childNodes: Proxy [Array],
      implementation: DOMImplementation {}
    }
  },
  length: 1
}
$ nvm exec 22 node
Running node v22.15.1 (npm v10.9.2)
Welcome to Node.js v22.15.1.
Type ".help" for more information.
> const { JSDOM } = require('jsdom');
undefined
> function set_up_globals() {
...     // Set up a mock document:
...     const dom = new JSDOM("");
...     global.window = dom.window;
...     global.document = dom.window.document;
...     global.navigator = dom.window.navigator;
...     global.HTMLElement = dom.window.HTMLElement;
...     global.$ = require('jquery')(dom.window);
... }
undefined
> set_up_globals();
undefined
> $.extend
[Function (anonymous)]
> $(document)
Uncaught TypeError: $ is not a function

Why is there a TypeError, suddenly?

To explain my use case: I have a file containing client-side JavaScript code that uses jQuery, whose behavior I want to test using a Node.js script. To do so, I need to create a global mock $ object.


Solution

  • The jquery module does some checks on the availability of a global window variable (presumably some sort of "am I running inside a browser?" check) and if it's available, it will export something different then when it's not available.

    Since you declare global.window before loading jquery, window exists, jQuery thinks it's running inside a browser, and the code fails.

    If you move the import to the top of the function (or rather, before declaring global.window), it works as expected:

    function set_up_globals() {
      const jquery = require('jquery');
      const dom = new JSDOM("");
    
      global.window = dom.window;
      global.document = dom.window.document;
      global.navigator = dom.window.navigator;
      global.HTMLElement = dom.window.HTMLElement;
      global.$ = jquery(dom.window);
    }
    

    (FWIW, global has been deprecated in favor of globalThis, more info here).