Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

jsdom globals deleted when loading as node module #1044

Closed
wants to merge 2 commits into from

4 participants

@kriscarle

This was failing:

var d3 = require('d3');
d3.select("body").append("div"); //append fails because window.document is undefined

The loops in index.js are trying to prevent global conflicts and/or keep multiple calls to require('d3') from being destructive? If so it should be safe to just delete the delete global[g] line?

@mbostock
Owner

Sorry, but it's intentional that D3 does not expose the JSDOM globals. Calling require("d3") only returns the loaded D3 module, and does not pollute the global namespace. If you want to use a DOM inside Node, you’ll have to create that DOM yourself.

@mbostock mbostock closed this
@kriscarle

No problem, I was going off your post here: https://groups.google.com/d/msg/d3-js/JyldAkWkTvI/n8thanJeGvAJ

Can you point me to a working example? No matter what I try get a "wrong document" error from jsdom.

Here is my test code:
var jsdom = require("jsdom").jsdom; //had to install jsdom at myproject level for this to work
global.document = jsdom("<html><body></body></html>");
global.d3 = require('d3'); //have to make these globals so the d3 code can find them
d3.select("body").append("div");//causes error

Error Message:

      throw new core.DOMException(WRONG_DOCUMENT_ERR);
            ^
Error: Wrong document
    at Object.core.Node.insertBefore (/home/kris/Documents/dev/myproject/node_modules/d3/node_modules/jsdom/lib/jsdom/level1/core.js:501:13)```

@mbostock
Owner

OK, I see what's happening. The recent change to remove the globals has caused an inconsistency: in some places (such as the definition of d3_selectionRoot in selection-root.js) D3 captures a reference to the global document on initialization, while in other places (such as in selection.append) it depends on the current global document.

Your code is failing because d3.select("body") selects from the captured document, while selection.append attempts to attempt to the new document that you created. You can avoid this problem by selecting from your new document explicitly, rather than selecting from the default document that is captured when D3 is loaded:

var d3 = require("d3");

global.document = require("jsdom").jsdom("<html><body></body></html>");

d3.select(document).select("body").append("div");

The reason that D3 exists as a Node module is primarily so that you can use the non-DOM related features, such as d3.geo, d3.scale, and d3.layout. I don't want to restore the old behavior where D3 pollutes the global namespace with its dependent DOM functionality because that makes it incompatible with applications that already have their own (potentially different) functionality.

I think the solution here is probably to disable d3.select(string) and d3.selectAll(string) in the Node environment, and instead require d3.select(document) first. In other words, to cut off access to d3_selectionRoot, which only makes sense in a browser where there is a primary document visible to the JavaScript runtime.

@mbostock
Owner

It would probably be a good idea for D3 to refer to captured variables for the document and window, rather than referencing the globals, so that at least it’s always consistent. This would also mean that you could use D3’s DOM directly without needing to re-initializing JSDOM.

@mbostock mbostock referenced this pull request from a commit
@mbostock Use captured references for document and window.
This avoids an inconsistency (discussed in #1044) when using D3 inside Node.js,
where D3 internally creates a JSDOM document during initialization, but then
subsequently depends on the current global document, which is not exposed. D3
now always refers to the global document at the time of initialization, and
does not depend on the current global document or window.
0991f3b
@kriscarle

Thanks! That makes sense and v3.0.5 works for me now.

I probably have a special use case. I was processing huge SVGs in the browser turning things on/off, changing styles etc. To improve performance I'm moving some of it to the server, rendering PNGs with PhantomJS and overlaying everything in Leaflet but I still want the code to work in both places.

@arunkjn

I tried using global document object with d3.
up till d3.select(document).select('body') it works fine but breaks with append giving a wrong document error.
I am using d3 3.3.4

Update
I downgraded d3 to 3.0.5 and it seems to be working in node REPL. but if the code is inside a js file, then running it with node gives ReferenceError: d3 is not defined. This is caused at d3.js:1655 name = d3.ns.qualify(name);

@mbostock
Owner

@arunkjn I have filed a separate issue #1533 to track that.

@pjanik

I also ran into this problem while testing our code in node.js. We have to use both jQuery and D3, but the problem is that each creates its own document. Ugly workaround is to create manually window, document and finally include browser versions of libraries.

Take a look at node-jquery module:
https://github.com/coolaj86/node-jquery#examples
Default behaviour of .create() is to create own document, like in D3. However you can pass your own instance too.

What about implementing the same thing in D3? It would be more flexible and perhaps would solve some issues that were mentioned here (e.g. developer could decide whether he wants to keep document global or not).

Looking at the code in index.js:

module.exports = (new Function("window", "document",
  "return " + fs.readFileSync(path.join(__dirname, "d3.js"), "utf-8"))
)(window, document);

it seems that the solution can be trivial, the same function that is immediately invoked with predefined window and document can be provided as .create().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
Showing with 5 additions and 2 deletions.
  1. +5 −2 index.js
View
7 index.js
@@ -2,7 +2,10 @@ var globals = ["document", "window", "navigator", "CSSStyleDeclaration", "getCom
globalValues = {};
globals.forEach(function(g) {
- if (g in global) globalValues[g] = global[g];
+ if (g in global) {
+ console.log("Warning: found pre-existing global: " + g);
+ globalValues[g] = global[g];
+ }
});
require("./globals");
@@ -11,6 +14,6 @@ require("./d3");
module.exports = d3;
globals.forEach(function(g) {
+ //restore pre-existing globals
if (g in globalValues) global[g] = globalValues[g];
- else delete global[g];
});
Something went wrong with that request. Please try again.