Leahcim
Leahcim

Reputation: 42019

How to add global variable if not defined

When d3.js moved to version 4, there was a significant rewrite of the Api, and also a change to modular packaging.

I don't want to upgrade to version 4 of d3.js but I have to rewrite the react/redux web app portion of my project using Vite, and version 3 of d3.js and it's use of global is not compatible with the JavaScript modular system used by Vite.

The error I'm getting is that "global" is not defined, and version 3 of d3.js uses "global."

This is what it says it node_modules/d3/index.js (index.js:4:13)

var globals = {};

// Stash old global.
if ("d3" in global) globals.d3 = global.d3;

module.exports = require("./d3");

// Restore old global.
if ("d3" in globals) global.d3 = globals.d3; else delete global.d3;

Therefore, I commented out that reference to global in the d3/index.js file to see what would happen, and I got a similar property, "cannot read properties of undefined (document)" This is the source code

var d3_document = this.document;
function d3_documentElement(node) {
  return node && (node.ownerDocument || node.document || node).documentElement;
}

I'm guessing this has something to do with the shift to modular d3 in version 4, but d3.js version 3.5.5 works fine when I don't use it with Vite.js. For example, if I just source it in an index.html file and run it with an http-server.

Question: is there a way to configure Vite.js to make it work with d3.js version 3.5.5? or can I alter the d3.js version 3.5.5 source code to make it run on Vite.js?

Update

I added this to the Vite.config.js

  define: {
    global: {},
    document: {}
  }

Now Vite complains that the property document cannot be set on #<Window>.

3env.ts:24 Uncaught TypeError: Cannot set property document of #<Window> which has only a getter

This StackOverflow question helps but it doesn't explain when "document" is undefined and it can't be "set" on Window.

Probably irrelevant but here's the source code for d3 version 3.5.5 on unpkg.com d3 version 3.5.5`

Here's the source code for d3 version 4 on unpkg.com d3 version 4

Upvotes: 3

Views: 1401

Answers (1)

zoran404
zoran404

Reputation: 2956

The main issue is that node_modules/d3/d3.js file uses the this keyword.
Specifically it reads this.document, instead of window.document or simply directly accessing the global document variable.
So the actual question should be: Is it possible to define this for d3 in Vite.

You saw that d3 worked fine when you tested with your http-server where you load the script from a index.html file.

It worked because you loaded it using <script type="text/javascript">.
This is because in old script files the default value for top-level this is window.

If you tried loading it using <script type="module">, like Vite does, it wouldn't have worked, since for module scripts the top-level this variable is undefined.


Vite does have an option for replacing variables by defining replacements in vite.config.js, but it has a lot of limitations because of the way it is implemented:

Entries will be defined as globals during dev and statically replaced during build.

In dev mode this is useless for us, since you can not define this as a global variable. And implicitly we can't define a replacement for this.document either.

During build we have a similar problem for a different reason. Vite can replace this, but only in the top level. Unfortunately d3 is wrapped in a function(){} and Vite does not want to replace this inside of functions, since this is seen as a local variable and Vite doesn't want to replace local variables.
It doesn't matter if you tried defining this:'window' or if you try being specific by defining 'this.document':'window.document'.

I haven't found anything else in docs that might be useful to us, so it is likely not possible to solve the problem using Vite config.

I'm guessing they didn't want to go against the official default, so they didn't create an option for this.

And unfortunately this is not something you can polyfill yourself.


This leaves us only with the solution I mentioned yesterday in the comments, that is editing the d3 source. At least the change is very minor.

First copy node_modules/d3/d3.js file to your source folder.
(you should never edit files in node_modules since npm or yarn might overwrite those changes; and pnpm even uses symbolic links, so it shared the same file for all projects)

Then you'd have to replace the first and last lines from:

!function() {
  // ...
}()

to:

(function() {
  // ...
}).call(window)

And of course you have to replace your import statements to import this new file instead of importing d3 from node_modules.


As for node_modules/d3/index.js you can actually skip that file instead of adding polyfills for it.
In later patches like 3.5.17 it was even removed.
All it does is handle the global d3 variable.

Upvotes: 3

Related Questions