Description
This is a writeup to discuss this problem in the GraphQL-WG and the GraphQL-JS-WG. Please give feedback in either of those meetings, or here in the issue, until the next GraphQL-JS-WG meeting, where we want to make a final call on this issue.
Last year, #3887 was released which changed the dev check in src/jsutils/instanceOf.ts
from
process.env.NODE_ENV === 'production'
to
globalThis.process?.env.NODE_ENV === 'production'
shortly followed by #3923, which changed it to
globalThis.process && globalThis.process.env.NODE_ENV === 'production'
as some bundlers were choking on the optional chaining syntax.
Since then, various issues and PRs have been opened to change into various other forms.
I'll try to give an overview over problems, potential solutions and their shortcomings here so we can decide on a way forward.
Problems:
1. accessing process.env.NODE_ENV
directly
There is a bunch of environments (e.g. ESM in the browser), where accessing process.env
directly just crashes, since process
is not a variable.
2. accessing globalThis.process.env.NODE_ENV
: bundler replacement
Some bundlers do a string replacement of process.env.NODE_ENV
, which lead to the code being replaced by invalid JavaScript like globalThis."production"
.
(Afaik, most of these have been reported in the upstream bundlers at this point in time and they have fixed their regular expressions)
3. accessing process.env.NODE_ENV
or globalThis.process.env.NODE_ENV
while testing for process
, without checking if the env
property is set: DOM elements with the id process
If a DOM element with the id
process
exists (e.g. <span id="process">...</span>
), this element will be registered as the global variable process
, so it will be accessible as process
and globalThis.process
- but not have a .env
property, so accessing process.env.FOO
will crash.
4. testing for process
to be present with typeof process
: cannot be tree-shaken
Some bundlers (e.g. esbuild) can only replace statements like foo.bar.baz
, but not statements like typeof foo
as they rely on AST-level replacement and just don't have support for that kind of replacement.
The do not plan to add this to ESBuild: evanw/esbuild#1954 (comment)
Potential solutions:
A) process.env.NODE_ENV === 'production'
This would be a rollback to the original code. It works fine with bundlers, but has problem 1.
B) globalThis.process && globalThis.process.env.NODE_ENV === 'production'
This is the current code. It has problems 2. and 3.
C) globalThis.process && globalThis.process.env && globalThis.process.env.NODE_ENV === 'production'
This is the current code with a fix for 3.
It still has problem 2., but that is mostly solved by bundlers after being out for one year.
D) typeof process && process.env.NODE_ENV === 'production'
A variation of the original code. Problems 3. and 4.
E) typeof process && process.env && process.env.NODE_ENV === 'production'
Another variation of the original. Problem 4.
F) const process = globalThis.process; if (process && process.env && process.env.NODE_ENV)
While this would probably replace fine in some bundlers, other bundlers would detect process
as a local variable and never be able to tree-shake it. A variation of problem 4.
A word on bundler code erasing
It should be noted that most bundlers at this point will automatically erase code inside of process.env.NODE_ENV === 'production'
, but not code inside of globalThis.process.env.NODE_ENV === 'production'
.
That said, every bundler can be configured for either of those (as long as we avoid typeof
), so I would propose not to take that into account too much.
Suggestion
My personal preference would be to go with C - globalThis.process && globalThis.process.env && globalThis.process.env.NODE_ENV === 'production'
.
It will never crash in any environment, and while it is not replaced by most bundlers by default, every bundler can be configured to replace it.
Some bundlers will create invalid JS code from this (blindly string-replacing in the middle of an expression), but that can be considered an upstream bug and has mostly been fixed upstream already.
Outlook
Looking into the future, we will hopefully be able to rely on the development
and production
exports conditions, so in a future version of graphql
, this whole discussion will be mostly obsolete.
This Issue is about finding a short-term fix, not a long-term solution.