Open phryneas opened 1 year ago
For a related experiment, see https://github.com/apollographql/apollo-client/pull/10884
That are the savings we could have by replacing every mention of __DEV__
by typeof __DEV__ !== "undefined" ? Boolean(__DEV__) : typeof process !== "undefined" && process.env.NODE_ENV !== "production"
.
That's of course not a good solution in the long run :/
I feel the need to clarify: I never imagined bundlers would automatically understand __DEV__
, without configuration.
What I did imagine is: anyone can set up their bundlers to replace the __DEV__
identifier with a chosen constant, and that will unlock massive bundle size savings, as long as you (or your minifier) propagate the consequences by eliminating any resulting dead code.
While it may not be easy/possible for bundlers to automatically infer the value of __DEV__
in cases where it is not stripped, that is still a scenario that needs to work correctly (albeit with a larger bundle size), as when @apollo/client
is loaded from an ESM-aware CDN like https://cdn.jsdelivr.net/npm/@apollo/client/+esm
anyone can set up their bundlers to replace the DEV identifier with a chosen constant
I think that's actually not possible in many bundlers - in esbuild
, you could replace a global __DEV__
variable, but not one imported from another file, like we do it.
Due to our new bundle size check, I noticed that the invariant error messages do not get removed from production bundles with
esbuild
, so I started getting deeper into this.General findings:
maybe(() => process.env.NODE_ENV) !== "production"
- the bundlers have no concept ofmaybe
, so it is essentially reduced down tounknownFunction(() => "production") !== "production"
, not tofalse
- and as a result, prevents code from being stripped. Replacing it with something liketypeof process !== "undefined" && process.env && process.env.NODE_ENV !== "production"
works and results infalse
ortrue
.global
file for checking for__DEV__
has a similar problem. That file essentially exportsmaybe(() => globalThis) || maybe(() => window) || ...
, and neither will any of thosemaybes
resolve, even if we removed them, we still end up with something likeglobalThis || ... || maybe(function() { return maybe.constructor("return this")() })
- with that last one explicitly trying to trick static analysis tools. So as long asglobal
is used here, no bundler will ever be able to fill in__DEV__
with a correct value, and as a result it will not be able to strip out our invariant error messages.So, from those findings, I went to this:
Webpack
This does work in webpack using the
@size-limit/webpack
plugin, and manually addingconfig.plugins.push(new (require("webpack").DefinePlugin)({__DEV__: false}));
.It does not work in enviroments where the bundler is not aware that it has to define
__DEV__
, as the bundler will assume that this is a runtime thing, and will not optimize it away.There doesn't seem to be a way around this at this moment.
ESbuild
This does not seem to work in esbuild - even doing something like
in the
DEV.ts
doesn't help, as esbuild doesn't seem to do this kind of optimization over file boundaries. I'm still investigating this.The generated code essentially ends up as