jonobr1 / two.js

A renderer agnostic two-dimensional drawing api for the web.
https://two.js.org
MIT License
8.29k stars 454 forks source link

[Question] How can we load an SVG file in a headless environment? #668

Closed laurentvd closed 1 year ago

laurentvd commented 1 year ago

Describe your question How can I load an SVG file in a headless environment? It seems like Two.js expects the DOM to be present as it parses the loaded SVG using innerHTML and then children. I've tried to make it work using jsdom, but that didn't help. Is it possible to make it work?

Your code (either pasted here, or a link to a hosted example)

const el = two.load(pathToMySvg, function() {
    console.log(el);
});

Screenshots The error:

for (i = 0; i < dom.temp.children.length; i++) {
                                  ^
TypeError: Cannot read properties of undefined (reading 'length')

Environment (please select one):

Additional context Probably not relevant, but we're using Node 16.

jonobr1 commented 1 year ago

Hmmm, great question. You are correct in how Two.js creates a virtual DOM with native browser DOM elements. It uses this to iterate through all the children and parse what would be text as nodes. This is how it expects that:

// src/utils/root.js
let root;

if (typeof window !== 'undefined') {
  root = window;
} else if (typeof global !== 'undefined') {
  root = global;
} else if (typeof self !== 'undefined') {
  root = self;
}

// src/utils/dom.js
dom.temp = (root.document ? root.document.createElement('div') : {});

Is there a way that jsdom can extend itself to the global node.js object? This way root.document could exist and have the createElement method.

laurentvd commented 1 year ago

Thanks for pointing me in the right direction! I managed to make it work using the following code.

global.window = new JSDOM().window;
global.document = window.document;

Note that it also needs a global document since for example src/effects/texture.js doesn't check for root properly:

// src/effects/texture.js
if (root.document) {
    anchor = document.createElement('a');
}

Also, it seems JSDOM has to be loaded and configured before Two.js is loaded to make it work. Additionally, JSDOM advises against this kind of setup. This is from their docs:

We strongly advise against trying to "execute scripts" by mashing together the jsdom and Node global environments (e.g. by doing global.window = dom.window), and then executing scripts or test code inside the Node global environment. Instead, you should treat jsdom like you would a browser, and run all scripts and tests that need access to a DOM inside the jsdom environment, using window.eval or runScripts: "dangerously". This might require, for example, creating a browserify bundle to execute as a Githubissues.

  • Githubissues is a development platform for aggregating issues.