Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JSDOM is not loading JavaScript included with <script> tag

Note: This question is not a duplicate of other existing questions because this question does not use jsdom.env() function call which older version of JSDOM use.

File bar.js:

console.log('bar says: hello')

File foo.js:

var jsdom = require('jsdom')
var html = '<!DOCTYPE html><head><script src="bar.js"></script></head><body><div>Foo</div></body>'
var window = new jsdom.JSDOM(html).window
window.onload = function () {
  console.log('window loaded')
}

When I run foo.js, I get this output.

$ node foo.js
window loaded

Why did bar says: hello output did not come? It looks like bar.js was not loaded. How can I make jsdom load the file in the script tag?

[EDIT/SOLUTION]: Problem solved after following a suggestion in the answer by Quentin. This code works:

var jsdom = require('jsdom')
var html = '<!DOCTYPE html><head><script src="bar.js"></script></head><body><div>Foo</div></body>'
var window = new jsdom.JSDOM(html, { runScripts: "dangerously", resources: "usable" }).window
window.onload = function () {
  console.log('window loaded')
}
like image 311
Lone Learner Avatar asked Oct 30 '18 17:10

Lone Learner


2 Answers

Go to the JSDOM homepage.

Skim the headings until you find one marked Executing scripts

To enable executing scripts inside the page, you can use the runScripts: "dangerously" option:

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

// The script will be executed and modify the DOM:
dom.window.document.body.children.length === 2;

Again we emphasize to only use this when feeding jsdom code you know is safe. If you use it on arbitrary user-supplied code, or code from the Internet, you are effectively running untrusted Node.js code, and your machine could be compromised.

If you want to execute external scripts, included via <script src="">, you'll also need to ensure that they load them. To do this, add the option resources: "usable" as described below.

like image 58
Quentin Avatar answered Oct 14 '22 15:10

Quentin


Given I was unable to reproduce the url-based solution from the code above...

Brutal bundle alternative : inline it all !

Read the various .js files, inject them as string into the html page. Then wait the page to load as in a normal navigator. These libraries are loaded into _window = new JSDOM(html, { options }).window; and therefor available to your node script.

This is likely to prevent you from doing xhr calls and therefore only partially solve the issue.

say-hello.js

// fired when loaded
console.log("say-hello.js says: hello!")
// defined and needing a call
var sayBye = function(name) { 
    var name = name ||'Hero!'; 
    console.log("say-hello.js says: Good bye! "+name)
}

main.js:

    const fs    = require("fs");
    const jsdom = require("jsdom");
    const { JSDOM } = jsdom;
    
    var NAME  = process.env.NAME; // variable from terminal

    var html = '<!DOCTYPE html><head></head><body><div>Foo</div></body>'
    var _window = new JSDOM(html, {
        runScripts: "dangerously",
        resources: "usable" }).window;
    /* ************************************************************************* */
    /* Add scripts to head ***************************************************** */
    var jsFiles = [
        'say-hello.js'
    ];
    var scriptsContent = ``;
    for(var i =0; i< jsFiles.length;i++){
        console.log(__dirname + '/'+ jsFiles[i])
      let scriptContent = fs.readFileSync( jsFiles[i], 'utf8');
      scriptsContent = scriptsContent + `
      /* ******************************************************************************************* */
      /* `+jsFiles[i]+` **************************************************************************** */
      `+scriptContent;
    };
    let scriptElement = _window.document.createElement('script');
    scriptElement.textContent = scriptsContent;
    _window.document.head.appendChild(scriptElement);


/* ************************************************************************* */
/* Run page **************************************************************** */
_window.document.addEventListener('DOMContentLoaded', () => {
    console.log('main says: DOMContentLoaded')
    // We need to delay one extra turn because we are the first DOMContentLoaded listener,
    // but we want to execute this code only after the second DOMContentLoaded listener
    // (added by external.js) fires.
    _window.sayBye(NAME); // prints "say-hello.js says: Good bye!"

  });

Run it:

NAME=John node main.js    # expects hello and good bye to john messages

Source:

  • https://github.com/jsdom/jsdom/issues/1914
  • https://github.com/jsdom/jsdom/issues/3023
like image 28
Hugolpz Avatar answered Oct 14 '22 16:10

Hugolpz