-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
process.env
, globalThis
, and typeof process
#4075
Comments
For the sake of going with what's supported I would lean towards We could make a lot of documentation on how to support different bundlers, how to replace, ... but for the sake of releasing something in the 16 release line that benefits everyone I would very much lean on the side of safe failure. With safe failure I mean that the
The consumer of this library gets code that stays fast because it opts in to the production path by default and if their bundler supports it it gets tree-shaken and they get the size win. This solution does not address the Reflected the aforementioned changes in https://github.com/graphql/graphql-js/pull/4022/files#r1585993254 |
Let me throw another solution into the ring: typeof window === 'undefined' && typeof process === 'object' && process.env.NODE_ENV === 'development' Specifically this attempts to avoid problem 3. This solution uses the very traditional Node.js pattern of Drawbacks:
Either way, if I don't think this has to be a problem for bundlers/tree shaking; they don't need to replace any typeof window === 'undefined' && typeof process === 'object' && process.env.NODE_ENV === 'development' could, through substitution, become:
The expression The only concern that I see here would be that a minifier could argue that typeof window === 'undefined' && typeof process === 'object' && false
? void 0 // < Will never be used, so just replace it with a trivial value
: function instanceOf(value. constructor) { Whether or not bundlers actually do this I don't know; but I see no reason why they should refuse to on consistency grounds. |
The real change in both of your suggestions here is from As @benjie correctly has noted, this would solve most problems (almost doesn't matter which other notation it would be combined with at that point) as all other statements could be "logically eliminated" - but like @JoviDeCroock says, it would change the default behaviour from "development by default" to "production by default". I'm gonna be honest, I considered that change to be so breaking (users won't get warnings anymore, and we still have a very high risk of a real dual module hazard) that I didn't even entertain the thought of adding it here - but if we're all fine with that, and we're aware of that danger, it could be an additional consideration to add to the mix. In that case, we could also go with something like typeof process === 'object' && process.env && process.env.NODE_ENV === 'development' which would also solve problem 3, and wouldn't need to add additional behaviour based on If we're afraid of bundlers getting confused by side effect, we could still throw some additional magic comments into the mix. |
I am personally not a fan of adding |
As @benjie noted, it will end up as |
Technically we have the concern that typeof process === 'object' && process !== null && typeof process.env === 'object' && process.env !== null && process.env.NODE_ENV === 'development' You could drop the I've seen situations where checking |
At the point where it doesn't matter from a bundler perspective what everything but the final condition is (to be verified), we could also do globalThis.process && process.env && process.env.NODE_ENV === 'development' As for testing this - I think I still have a lot of bundlers set up from when we changed over to The base question is still: do we think it's a good choice to go from "dev by default" to "prod by default"? |
I hadn't intended to change it; thought I had copied it from one of the various PRs or implementations or something but maybe not 🤷 Changing the default would be a semver major change, so we should not put that into this version. |
Then we'd be back to the original suggestions - all the suggestions we had in comments swap the With |
Changing the default is for the best imho and it's also not breaking. Users who properly have configured Anyway, just my two cents, I basically have both of those in PR 😅 To clarify the PR also has the two things that I feel solve most of the issues
|
what about if we simplify to original behavior, but wrapped in a try/catch block a la function isProduction(): boolean {
try {
return process.env.NODE_ENV === 'production';
} catch {
return true;
}
} see #4079 |
Can bundlers tree-shake based on the result of function calls? |
Some, but not all. It's not just the bundle size - that check is placed in probably the hottest code path of the whole package (and as it is, it unfortunately only makes sense to place it there). Not having the chance to erase it in production means a serious performance impact. |
I created a set of experiments here, to highlight the conclusions:
My own conclusion from this is that
I really don't agree with this, a library should work by default for our users. Users should be able to choose to optimise the library they choose but having a great first experience is still the most important thing. I am personally a big fan of things that work with zero-config by default.
The fact that we do |
Doesn't that leave us with the same conclusion regarding You're saying you want it to work (so, be erased?) out of the box, and by your experiments I'm saying I want to avoid something that cannot even be erased with configuration, and Or am I missing the point? 😅 |
Well, I want everything to work by default without configuration, which leaves |
Oh, I might not have been clear: I want it to work (as in, in a production environment, pick "production" at runtime without dead code erasure) by default, too, I just would prefer a solution that would enable the possibility of dead code erasure for everyone, even if it meant potentially more configuration to get to that "perfect" state. What about this variation of C) that should avoid all the problems from the initial post? globalThis.process && process.env && process.env.NODE_ENV === 'production'
or, I believe this might actually make it a little bit easier for bundlers (but this is unverified gut feeling): !!globalThis.process && !!process.env && process.env.NODE_ENV === 'production' |
I have to admit, I feel a bit like there is still some reason why you focused on the I don't mean to offend, I probably really just missed a point somewhere and am sometimes a bit tone deaf😅 |
Mainly because it's a whole new standard, the ecosystem has quite settled on It's 580 results for globalThis.process.env and 6.6 mil results for process.env 😅 as an extra typeof process results in another 5.4 mil results. EDIT: I guess this basically comes down to fixing it for v16 with the proposed typeof and NODE_ENV check and for 17 digging deeper into whether |
Currently (not just in #4022), the check is done once on startup to decide what the graphql-js/src/jsutils/instanceOf.ts Lines 9 to 15 in d811c97
Line 9 is a bit tough to read, but it's not defining a function, it's saying the type of In general, @JoviDeCroock -- the experiments are super-helpful! In theory, they could be added to our integration-tests.
esbuild (and therefore wrangler) will apparently replace One general thought I had when looking at the experiments is that while it seemed to me at first blush that setting The bundler cannot assume that because it itself is running in "production" mode (as fast as possible) etc. that it should be producing a "production" bundle. So producing a "production" bundle is going to depend on the individual bundler's options, as well as how each of those dependencies set themselves up to detect |
#4022) As surfaced in [Discord](https://discord.com/channels/625400653321076807/862957336082645006/1206980831915282532) this currently is a breaking change in the 16.x.x release line which is preventing folks from upgrading towards a security fix. This PR should result in a patch release on the 16 release line. This change was originally introduced to support CFW and browser environments which should still be supported with the `typeof` check CC @n1ru4l This also adds a check whether `.env` is present as in the DOM using `id="process"` defines that as a global which we don't want to access on accident. as shown in #4017 Bundles also target `process.env.NODE_ENV` specifically which fails when it replaces `globalThis.process.env.NODE_ENV` as this becomes `globalThis."production"` which is invalid syntax. Fixes #3978 Fixes #3918 Fixes #3928 Fixes #3758 Fixes #3934 This purposefully does not account for #3925 as we can't address this without breaking CF/plain browsers so the small byte-size increase will be expected for bundled browser environments. As a middle ground we did optimise the performance here. We can revisit this for v17. Most bundlers will be able to tree-shake this with a little help, in #4075 (comment) you can find a conclusion with a repo where we discuss a few. - Next.JS by default replaces [`process.env.NODE_ENV`](https://github.com/vercel/next.js/blob/b0ab0fe85fe8c93792051b058e060724ff373cc2/packages/next/webpack.config.js#L182) you can add `typeof process` linearly - Vite allows you to specify [`config.define`](https://vitejs.dev/config/shared-options.html#define) - ESBuild by default will replace `process.env.NODE_ENV` but does not support replacing `typeof process` - Rollup has a plugin for this https://www.npmjs.com/package/@rollup/plugin-replace Supersedes #4021 Supersedes #4019 Supersedes #3927 > This now also adds a documentation page on how to remove all of these
For v16 we merged #4022 which is awaiting a patch release and website deploy. I have updated the minifier experiments to test what we have documented and that behaves well - the result is that we now can safely run in any bundler and have resolutions to optimise GraphQL for production. |
I believe we can close this for now then :) |
graphql#4022) As surfaced in [Discord](https://discord.com/channels/625400653321076807/862957336082645006/1206980831915282532) this currently is a breaking change in the 16.x.x release line which is preventing folks from upgrading towards a security fix. This PR should result in a patch release on the 16 release line. This change was originally introduced to support CFW and browser environments which should still be supported with the `typeof` check CC @n1ru4l This also adds a check whether `.env` is present as in the DOM using `id="process"` defines that as a global which we don't want to access on accident. as shown in graphql#4017 Bundles also target `process.env.NODE_ENV` specifically which fails when it replaces `globalThis.process.env.NODE_ENV` as this becomes `globalThis."production"` which is invalid syntax. Fixes graphql#3978 Fixes graphql#3918 Fixes graphql#3928 Fixes graphql#3758 Fixes graphql#3934 This purposefully does not account for graphql#3925 as we can't address this without breaking CF/plain browsers so the small byte-size increase will be expected for bundled browser environments. As a middle ground we did optimise the performance here. We can revisit this for v17. Most bundlers will be able to tree-shake this with a little help, in graphql#4075 (comment) you can find a conclusion with a repo where we discuss a few. - Next.JS by default replaces [`process.env.NODE_ENV`](https://github.com/vercel/next.js/blob/b0ab0fe85fe8c93792051b058e060724ff373cc2/packages/next/webpack.config.js#L182) you can add `typeof process` linearly - Vite allows you to specify [`config.define`](https://vitejs.dev/config/shared-options.html#define) - ESBuild by default will replace `process.env.NODE_ENV` but does not support replacing `typeof process` - Rollup has a plugin for this https://www.npmjs.com/package/@rollup/plugin-replace Supersedes graphql#4021 Supersedes graphql#4019 Supersedes graphql#3927 > This now also adds a documentation page on how to remove all of these
graphql#4022) As surfaced in [Discord](https://discord.com/channels/625400653321076807/862957336082645006/1206980831915282532) this currently is a breaking change in the 16.x.x release line which is preventing folks from upgrading towards a security fix. This PR should result in a patch release on the 16 release line. This change was originally introduced to support CFW and browser environments which should still be supported with the `typeof` check CC @n1ru4l This also adds a check whether `.env` is present as in the DOM using `id="process"` defines that as a global which we don't want to access on accident. as shown in graphql#4017 Bundles also target `process.env.NODE_ENV` specifically which fails when it replaces `globalThis.process.env.NODE_ENV` as this becomes `globalThis."production"` which is invalid syntax. Fixes graphql#3978 Fixes graphql#3918 Fixes graphql#3928 Fixes graphql#3758 Fixes graphql#3934 This purposefully does not account for graphql#3925 as we can't address this without breaking CF/plain browsers so the small byte-size increase will be expected for bundled browser environments. As a middle ground we did optimise the performance here. We can revisit this for v17. Most bundlers will be able to tree-shake this with a little help, in graphql#4075 (comment) you can find a conclusion with a repo where we discuss a few. - Next.JS by default replaces [`process.env.NODE_ENV`](https://github.com/vercel/next.js/blob/b0ab0fe85fe8c93792051b058e060724ff373cc2/packages/next/webpack.config.js#L182) you can add `typeof process` linearly - Vite allows you to specify [`config.define`](https://vitejs.dev/config/shared-options.html#define) - ESBuild by default will replace `process.env.NODE_ENV` but does not support replacing `typeof process` - Rollup has a plugin for this https://www.npmjs.com/package/@rollup/plugin-replace Supersedes graphql#4021 Supersedes graphql#4019 Supersedes graphql#3927 > This now also adds a documentation page on how to remove all of these
#4022) As surfaced in [Discord](https://discord.com/channels/625400653321076807/862957336082645006/1206980831915282532) this currently is a breaking change in the 16.x.x release line which is preventing folks from upgrading towards a security fix. This PR should result in a patch release on the 16 release line. This change was originally introduced to support CFW and browser environments which should still be supported with the `typeof` check CC @n1ru4l This also adds a check whether `.env` is present as in the DOM using `id="process"` defines that as a global which we don't want to access on accident. as shown in #4017 Bundles also target `process.env.NODE_ENV` specifically which fails when it replaces `globalThis.process.env.NODE_ENV` as this becomes `globalThis."production"` which is invalid syntax. Fixes #3978 Fixes #3918 Fixes #3928 Fixes #3758 Fixes #3934 This purposefully does not account for #3925 as we can't address this without breaking CF/plain browsers so the small byte-size increase will be expected for bundled browser environments. As a middle ground we did optimise the performance here. We can revisit this for v17. Most bundlers will be able to tree-shake this with a little help, in #4075 (comment) you can find a conclusion with a repo where we discuss a few. - Next.JS by default replaces [`process.env.NODE_ENV`](https://github.com/vercel/next.js/blob/b0ab0fe85fe8c93792051b058e060724ff373cc2/packages/next/webpack.config.js#L182) you can add `typeof process` linearly - Vite allows you to specify [`config.define`](https://vitejs.dev/config/shared-options.html#define) - ESBuild by default will replace `process.env.NODE_ENV` but does not support replacing `typeof process` - Rollup has a plugin for this https://www.npmjs.com/package/@rollup/plugin-replace Supersedes #4021 Supersedes #4019 Supersedes #3927 > This now also adds a documentation page on how to remove all of these
development behavior should be opt in, not opt out graphql#4075 (comment)
development behavior should be opt in, not opt out graphql#4075 (comment)
development behavior should be opt in, not opt out graphql#4075 (comment)
development behavior should be opt in, not opt out graphql#4075 (comment)
development behavior should be opt in, not opt out graphql#4075 (comment)
development behavior should be opt in, not opt out graphql#4075 (comment)
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
fromto
shortly followed by #3923, which changed it to
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
directlyThere is a bunch of environments (e.g. ESM in the browser), where accessing
process.env
directly just crashes, sinceprocess
is not a variable.2. accessing
globalThis.process.env.NODE_ENV
: bundler replacementSome bundlers do a string replacement of
process.env.NODE_ENV
, which lead to the code being replaced by invalid JavaScript likeglobalThis."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
orglobalThis.process.env.NODE_ENV
while testing forprocess
, without checking if theenv
property is set: DOM elements with the idprocess
If a DOM element with the
id
process
exists (e.g.<span id="process">...</span>
), this element will be registered as the global variableprocess
, so it will be accessible asprocess
andglobalThis.process
- but not have a.env
property, so accessingprocess.env.FOO
will crash.4. testing for
process
to be present withtypeof process
: cannot be tree-shakenSome bundlers (e.g. esbuild) can only replace statements like
foo.bar.baz
, but not statements liketypeof 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 ofglobalThis.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
andproduction
exports conditions, so in a future version ofgraphql
, this whole discussion will be mostly obsolete.This Issue is about finding a short-term fix, not a long-term solution.
The text was updated successfully, but these errors were encountered: