You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I ran a few benchmarks on the server (modifying https://github.com/paulshen/react-bench to not use jsdom). The results were surprising, as the browserified react.js was about 30% faster than the npm version, even with NODE_ENV=production.
The performance ranking (test run time) was react.min.js < react.js < NODE_ENV=production node react < node react.
I suspect process.env is not a regular js object but perhaps a getter and thus carries a penalty when you test for "production" !== process.env.NODE_ENV everywhere.
Also the minified version might still perform best of all, as at least some time ago V8 used function source length (including comments) as a heuristic for function complexity / compilation time and thus affecting chances for optimization in some cases, but the effect might be negligible.
My first attempt was to just add var __DEV__ = process.env.NODE_ENV !== 'production'; to the top of every file, but unfortunately uglify isn't smart enough to do dead code removal then. I wonder if we should have vendor/constants.js essentially make two copies of the code -- one for dev, one for prod, so
I think eventually we will want people to do a build step for server rendering, i.e. webpack's --target node mode. Especially if people start expressing static asset dependencies as require() statements.
I'm using webpack and new webpack.DefinePlugin({'process.env.NODE_ENV': '"production"'}) was enough to solve it for me. Is there no such thing in browserify?
Call me all crazy, just had a left field idea. Could the transformer that builds the npm module create 2 complete copies of react? one for dev and one for production?
In the the normal version of react var React = require('react') could you there look at the NODE_ENV and swap which module is returned? This way on server you only get the performance hit once;
That's not too crazy. It's a bit annoying from a build process though. I'd love to know what's happening at a larger scale in the node community. It seems like we shouldn't be the first people to encounter this in a larger scale.
A couple other ideas…
What if we set global.__DEV__ = process.env.NODE_ENV in our main module and leave __DEV__ in our code everywhere else. There's some obvious bad behavior there (setting globals). So we could do variants. Only set it when it's not already set. Change __DEV__ to something more unique at build time (__REACT__DEV__) and then set that. If we switch to webpack for building the browser bundles, then the DefinePlugin solution is super easy, and I'm sure there's a way to do this in browserify too.
Another idea: Have a RuntimeEnvironment modules which memoizes process.env.NODE_ENV. This is sounding like something node should do itself, but the code @plievone linked to uses ObjectTemplate (which if I'm understand a bit of V8 right doesn't do any caching because values can change). There would still be a lookup cost, but it would just be a property access, not a getter and not a call out to get current environment variables (getenv). This one is trickier because it would mean more code and we won't do this internally. I'd prefer a different solution, even if it's more hacky.
If all you're using is React and nothing from react/lib/*, then you could do something like @plievone mentioned above. We started shipping the browerified bundles in the npm package, so require('react/dist/react') and require('react/dist/react.min') will end up giving you the bundles which don't access process.env.
I don't think we're going to do anything specifically for server rendering other than bundling the builds in dist like @zpao mentioned, but we might change the npm package eventually to be prebuilt using closure or similar.
Just so I'm clear, there are a few users here that I can think of. Those, on the browser:
Using require('react/dist/...') everywhere already, or as a global <script>
Using browserify/webpack + envify/DefinePlugin
Using browserify/webpack + envify/DefinePlugin + UglifyJS
Using browserify/webpack + plugin to transform require('react') to require('react/dist/...') or window.React
So 1 and 4 are taken care of by the compilation in dist. What's the main reason for even supporting 2 and 3? Is it because of things like require('react/lib/...')?
2 and 3 are the most natural for many people. If you are using browserify then envify gets used automatically because of our config in package.json. Almost everyone minifies their code in prod.
It is true that we could recommend #4 instead for many cases, but it does fall apart in the case of requiring submodules. We don't support this for external users because we consider the modules private but the addons packages use this pattern. Various third-party projects (unsupported by us) also make use of this.
I think the only reason 2 and 3 are "most natural" is just because process.env is used everywhere so you have to use something just to get it to even work out of the box. It would be equally easy to setup the config in package.json to use literalify or browserify-shim or whatever.
I use 4 whenever I can and it's no more complicated than 2 or 3 for normal usage – I guess it's just a pity that users are encouraged to "reach in" to react/lib/ for various addons.
Thanks for the clarification.
So it's those doing 3 who are expecting their current UglifyJS setup to eliminate any code using __DEV__ that you're concerned about?
I guess it's just a pity that users are encouraged to "reach in" to react/lib/ for various addons.
To clarify: we recommend users require react-addons-transition-group or similar, which (currently) reaches into react but that's an implementation detail.
So it's those doing 3 who are expecting their current UglifyJS setup to eliminate any code using __DEV__ that you're concerned about?
We have been bitten by this one too. Would be great if some note about this behavior was present on https://facebook.github.io/react/docs/environments.html , it's a large performance impact, and is hard to find this issue unless you have already done all the investigation work.
I would like to add that this issue also affects Electron applications.
Now the work arounds seem to be:
Include the minified version of React globally via <script> tag, it would only work in Electron and it isn't the node way, where one should be able to track every used object just seeing the requires. It also makes it difficult to switch between dev/prod.
Use the proposed hack to replace process.env with a regular object. Not good, as we are forced to overwrite node api just because we want to use React.
If using babel: transform-inline-environment-variables... by default babel doesn't transpile node_modules, this can be configured but doesn't look like a work around.
Note that replace every require() to react/dist/react.min.js isn't a solution because react-dom automatically requires from react root, not from react/dist.
Why is this issue closed?
It seems to me that React should be able to initialize its configuration using NODE_ENV, then rely on its own objects, and optionally offer a React.setProduction(true/false) function to change the value later.
Is there any reason why this wouldn't work?
Technically this could be fixed now as UglifyJS supports /* @const */ annotations, and it may be easier still as the team moves the build to Rollup in #7178.
In combination with Rollup, it would be simple to export a __DEV__ var from a constants file and have it effectively tagged by Uglify for dead code elimination in production mode.
I had previously wrapped React using local modules file://... as was suggested above, but this caused a lot of issues with npm shrinkwrap. This seems to get the job done without too much overhead or complexity.
Referencing facebook/fbjs#86 which will fix this now that UglifyJS can properly eliminate dev code (even if it references a var that never changes) with its new reduce_vars option, which is a default.
Activity
plievone commentedon Jan 5, 2014
process.envis indeed not a regular object, but defined from the native side, so that may be the culprit for server rendering regression:https://github.com/joyent/node/blob/b922b5e/src/node.cc#L2326-L2335 and https://github.com/joyent/node/blob/b922b5e/src/node.cc#L2030-L2055
So this introduced a getter in quite time-sensitive places (around invariants etc) in your otherwise clean codebase. Can you reproduce the issue? /cc @petehunt @benjamn
plievone commentedon Apr 6, 2014
Lately I have been simply using a wrapped module
Works great. You could consider switching npm package to some such scheme if there are no downsides.
sophiebits commentedon Apr 7, 2014
My first attempt was to just add
var __DEV__ = process.env.NODE_ENV !== 'production';to the top of every file, but unfortunately uglify isn't smart enough to do dead code removal then. I wonder if we should have vendor/constants.js essentially make two copies of the code -- one for dev, one for prod, sotransforms into
so we only pay the getter cost once (per module) at require time.
@benjamn Is doing this easy with recast? It's not obvious to me if
this.replaceinteracts well with cloning the AST.mridgway commentedon May 16, 2014
I would love to see this get some attention. 30% improvement out of the box would be awesome. Is there a lot of work to do here?
petehunt commentedon May 16, 2014
I think eventually we will want people to do a build step for server rendering, i.e. webpack's
--target nodemode. Especially if people start expressing static asset dependencies asrequire()statements.syranide commentedon May 17, 2014
I'm using webpack and
new webpack.DefinePlugin({'process.env.NODE_ENV': '"production"'})was enough to solve it for me. Is there no such thing in browserify?andreypopp commentedon May 17, 2014
For browserify there's
envifytransform which automatically activates forreactpackage. You just need to setNODE_ENVbefore bundling:Thus browserify also can be used for bundling for server rendering.
karlmikko commentedon Aug 6, 2014
Call me all crazy, just had a left field idea. Could the transformer that builds the npm module create 2 complete copies of react? one for dev and one for production?
In the the normal version of react
var React = require('react')could you there look at theNODE_ENVand swap which module is returned? This way on server you only get the performance hit once;zpao commentedon Aug 6, 2014
That's not too crazy. It's a bit annoying from a build process though. I'd love to know what's happening at a larger scale in the node community. It seems like we shouldn't be the first people to encounter this in a larger scale.
A couple other ideas…
What if we set
global.__DEV__ = process.env.NODE_ENVin our main module and leave__DEV__in our code everywhere else. There's some obvious bad behavior there (setting globals). So we could do variants. Only set it when it's not already set. Change__DEV__to something more unique at build time (__REACT__DEV__) and then set that. If we switch to webpack for building the browser bundles, then theDefinePluginsolution is super easy, and I'm sure there's a way to do this in browserify too.Another idea: Have a
RuntimeEnvironmentmodules which memoizesprocess.env.NODE_ENV. This is sounding like something node should do itself, but the code @plievone linked to uses ObjectTemplate (which if I'm understand a bit of V8 right doesn't do any caching because values can change). There would still be a lookup cost, but it would just be a property access, not a getter and not a call out to get current environment variables (getenv). This one is trickier because it would mean more code and we won't do this internally. I'd prefer a different solution, even if it's more hacky.If all you're using is React and nothing from
react/lib/*, then you could do something like @plievone mentioned above. We started shipping the browerified bundles in the npm package, sorequire('react/dist/react')andrequire('react/dist/react.min')will end up giving you the bundles which don't accessprocess.env.sophiebits commentedon Apr 1, 2015
I don't think we're going to do anything specifically for server rendering other than bundling the builds in
distlike @zpao mentioned, but we might change the npm package eventually to be prebuilt using closure or similar.37 remaining items
STRML commentedon Jan 19, 2016
Just everyone using React in combination with webpack or browserify and not using the
dist/build.Alternatively they could use the DefinePlugin to set
__DEV__to false to get the dead code elimination.mhart commentedon Jan 19, 2016
@spicyj just to clarify, do you mean "everyone using React who also currently uses UglifyJS with it"?
mhart commentedon Jan 19, 2016
Just so I'm clear, there are a few users here that I can think of. Those, on the browser:
require('react/dist/...')everywhere already, or as a global<script>require('react')torequire('react/dist/...')orwindow.ReactSo 1 and 4 are taken care of by the compilation in
dist. What's the main reason for even supporting 2 and 3? Is it because of things likerequire('react/lib/...')?sophiebits commentedon Jan 19, 2016
2 and 3 are the most natural for many people. If you are using browserify then envify gets used automatically because of our config in package.json. Almost everyone minifies their code in prod.
It is true that we could recommend #4 instead for many cases, but it does fall apart in the case of requiring submodules. We don't support this for external users because we consider the modules private but the addons packages use this pattern. Various third-party projects (unsupported by us) also make use of this.
mhart commentedon Jan 19, 2016
I think the only reason 2 and 3 are "most natural" is just because
process.envis used everywhere so you have to use something just to get it to even work out of the box. It would be equally easy to setup the config in package.json to useliteralifyorbrowserify-shimor whatever.I use 4 whenever I can and it's no more complicated than 2 or 3 for normal usage – I guess it's just a pity that users are encouraged to "reach in" to
react/lib/for various addons.Thanks for the clarification.
So it's those doing 3 who are expecting their current UglifyJS setup to eliminate any code using
__DEV__that you're concerned about?sophiebits commentedon Jan 19, 2016
To clarify: we recommend users require
react-addons-transition-groupor similar, which (currently) reaches into react but that's an implementation detail.Yes.
mhart commentedon Jan 19, 2016
Ah cool, good to know.
uglify server build
santiagoaguiar commentedon Jul 29, 2016
We have been bitten by this one too. Would be great if some note about this behavior was present on https://facebook.github.io/react/docs/environments.html , it's a large performance impact, and is hard to find this issue unless you have already done all the investigation work.
pmoleri commentedon Jul 29, 2016
I would like to add that this issue also affects Electron applications.
Now the work arounds seem to be:
<script>tag, it would only work in Electron and it isn't the node way, where one should be able to track every used object just seeing therequires. It also makes it difficult to switch between dev/prod.process.envwith a regular object. Not good, as we are forced to overwrite node api just because we want to use React.Note that replace every
require()toreact/dist/react.min.jsisn't a solution becausereact-domautomatically requires fromreactroot, not fromreact/dist.Why is this issue closed?
It seems to me that React should be able to initialize its configuration using
NODE_ENV, then rely on its own objects, and optionally offer aReact.setProduction(true/false)function to change the value later.Is there any reason why this wouldn't work?
STRML commentedon Jul 29, 2016
Technically this could be fixed now as UglifyJS supports
/* @const */annotations, and it may be easier still as the team moves the build to Rollup in #7178.In combination with Rollup, it would be simple to export a
__DEV__var from a constants file and have it effectively tagged by Uglify for dead code elimination in production mode.jknight12882 commentedon Dec 23, 2016
Adding my two cents here, I was able to work around this by adding the following to the top of my
server.jsI had previously wrapped React using local modules
file://...as was suggested above, but this caused a lot of issues withnpm shrinkwrap. This seems to get the job done without too much overhead or complexity.rtsao commentedon Feb 10, 2017
For future reference, the above snippet is broken per: #8788
STRML commentedon Mar 8, 2017
Referencing facebook/fbjs#86 which will fix this now that UglifyJS can properly eliminate dev code (even if it references a
varthat never changes) with its newreduce_varsoption, which is a default.See facebook/fbjs#86 (comment) for more context.
[test] Print HIR before leaving SSA