Skip to content
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

Hoist __DEV__'s process.env use out of hot code. #86

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

STRML
Copy link

@STRML STRML commented Dec 2, 2015

See react/#812.

Naive benchmarks show nearly 100% performance increase in server
rendering performance.

Benched and built on 0.14.3 tag.

Sizes:

   raw     gz Sizes
  1189    635 build/react-dom-server.js
   725    438 build/react-dom-server.min.js
  1170    627 build/react-dom.js
   706    431 build/react-dom.min.js
703188 160014 build/react-with-addons.js
147964  42965 build/react-with-addons.min.js
637385 145166 build/react.js
135574  39485 build/react.min.js

Benchmark (50000 creations of a small nested element):

50000 iterations of Baseline: 8457ms
50000 iterations of dist/react, unminified (dev): 6278ms
50000 iterations of Production: 3928ms
50000 iterations of Production (raw obj process.env): 2160ms
50000 iterations of Minified React (dist/react.min): 1446ms
50000 iterations of Proposed fix (hoist const): 1598ms
50000 iterations of Proposed fix (dist min.js): 1546ms

Repository for bench is at https://github.com/STRML/react-bench (soon to be updated).

In order for uglify to properly kill dead code for the browser builds, we need to blacklist es6.constants so the files compile with the const intact. UglifyJS will then optimize them away.

Re: non const-supporting browsers, we'll likely need to add a post-process step to the React grunt build in case of users browserifying React without varify. This could potentially cause some dead code to enter user's generated browserify/uglify builds as uglify will not properly optimize consts that aren't declared with const. This may be something that we could solve on the UglifyJS end as it makes library compatibility difficult when forcing the use of const. UglifyJS has a tracking issue.

@facebook-github-bot
Copy link

Thank you for your pull request and welcome to our community. We require contributors to sign our Contributor License Agreement, and we don't seem to have you on file. In order for us to review and merge your code, please sign up at https://code.facebook.com/cla - and if you have received this in error or have any questions, please drop us a line at cla@fb.com. Thanks!

@facebook-github-bot
Copy link

Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Facebook open source project. Thanks!

@sophiebits
Copy link
Contributor

I don't think it's practical for us to use const in what we ship unfortunately. This is interesting though…

@zpao
Copy link
Member

zpao commented Dec 2, 2015

This is an interesting idea… I think we thought about it before but jstransform made this really tricky & Babel is much nicer. Thanks for taking a stab at it.

cc @cpojer @voideanvalue who are much better at this whole transform stuff and can give a better review.

If we do go down this route we'll definitely want to make sure we update the big comment blocks describing how this works. We'll also have to do some more testing with minifiers. I don't think we want to use const yet - that would deviate from our generation of ES5 compatible code. Perhaps something with envify can work or even switching away from browserify to webpack for our browser builds. I'd like to make sure we remove dead code if we can.

@STRML
Copy link
Author

STRML commented Dec 2, 2015

Agreed re: shipping const.

IMO, there's not going to be any way, with current UglifyJS (perhaps Closure is better) to eliminate dead code only with envify (by setting NODE_ENV = 'production') without using const. UglifyJS, at least right now, cannot figure out that a var is not being reassigned because it would technically have to check eval and with.

Because of that, we'll risk an extra ~16kb in users' browser builds if they're using browserify or Webpack.

I have an idea, though, and I'll explore it in UglifyJS - we should be able to tag a var as a const so we get backwards-compatible consts that can be eliminated by Uglify. Then, all we'd have to do is update this PR to print a var instead of a const and we can :shipit:.

@zpao
Copy link
Member

zpao commented Dec 2, 2015

We could probably do another pass in the browserify step with another babel transform. Might not be ideal but it's workable.

@STRML
Copy link
Author

STRML commented Dec 2, 2015

Well, technically if we just changed our uglify step to replace __DEV__, we'd be golden. But the real problem is users installing the npm package and doing their own bundling/minification. We could issue a breaking change and say that you now have to add __DEV__ to global_defs, but that feels messy to me. If I can fix it in UglifyJS outright, all we have to say is that you should be on the latest version and it'll work.

@STRML
Copy link
Author

STRML commented Jan 19, 2016

UglifyJS PR is in, if it is merged I'll change this to use /** @const */ var __DEV__ instead of const __DEV__.

@jshow
Copy link

jshow commented Jan 24, 2016

UglifyJS PR has been merged

See react/#812.

Naive benchmarks show nearly 100% performance increase in server
rendering performance.
@STRML
Copy link
Author

STRML commented Mar 8, 2017

I've rebased this in case anyone's still interested: a brief history of where this is at:

  1. In early 2016, I introduced /** @const */ syntax that UglifyJS2 could recognize, which would allow it to eliminate more dead code: Mark vars with /** @const */ pragma as consts so they can be eliminated. mishoo/UglifyJS#928
  2. In September 2016, UglifyJS2 added the reduce_vars option (introduce reduce_vars mishoo/UglifyJS#1301) which can find effectively-constant vars and do the same optimization.
  3. In late Feb 2017, UglifyJS2 made reduce_vars a default (enable collapse_vars & reduce_vars by default mishoo/UglifyJS#1498).
  4. In early March, evaluate and reduce_vars were consolidated.

This means that we no longer have to repeat a constant condition in order for UglifyJS to replace it, like so:

if (process.env.NODE_ENV !== 'production') {
  // dev code, this will be eliminated
}

We can now just make __DEV__ a real var:

var __DEV__ = process.env.NODE_ENV !== 'production';
if (__DEV__) {
  // dev code, this will be eliminated
}

Because this dead code elimination is now happening properly on browser builds, it's safe to hoist __DEV__, which means the slow React-Server bug is actually fixed with this.

@gaearon
Copy link
Contributor

gaearon commented Mar 21, 2017

FWIW it's probably not going to be important soon because with React 16 we plan to have separate precompiled bundles for development and production, and just one switch.

@STRML
Copy link
Author

STRML commented Mar 21, 2017

@gaearon And that includes SSR? Will all the files be individually compiled or will you be essentially require()ing a bundle?

@gaearon
Copy link
Contributor

gaearon commented Mar 21, 2017

We don't have Fiber-compatible implementation of SSR but the plan is to figure it out for 16 release. By that time, we intend to have react-dom-server.node-dev.js and react-dom-server.node-prod.min.js loaded depending on process.env.NODE_ENV, and same with react.node-dev.js and react.node-prod.min.js. Those will be single-file CommonJS bundles.

BerkeleyTrue pushed a commit to BerkeleyTrue/warning that referenced this pull request May 22, 2018
Ref #18

Since the problem with uglify is fixed there is not reason to keep
browser version which do not add any benefits. Actually it blocks us
from distributing `esm` version which is widely used these days and
supported by the most popular bundlers like webpack, rollup and parcel.

Here's a few links
facebook/fbjs#86 (comment)

Both uglify and babel minify support evaulation and eliminating

```js
(function () {
  function warning() {}

  var __DEV__ = 'production' !== 'production'

  if (__DEV__) {
    warning = function (msg) {
      console.log(msg)
    }
  }

  warning()
} ());
```

https://skalman.github.io/UglifyJS-online/

https://babeljs.io/repl#?babili=true&browsers=&build=&builtIns=false&code_lz=BQMwrgdgxgLglgewgAmASmQbwFDOeaeJZAdwEMAnCOCAc3SwF9tdkA3S5AfS4BEBRAGo9kAXmQByAA4UEAEzCxEECcgCEo8dNkKlSCSzxwQqHgOFcMOPHnJUatMfkh6UwALYBnWldY3kUEieCAA2AKYAdCEI9F4-fsjMeMysdtR06NiMqGhoANxAA&debug=false&forceAllTransforms=false&shippedProposals=false&circleciRepo=&evaluate=false&fileSize=false&lineWrap=false&presets=babili%2Cenv&prettier=false&targets=&version=6.26.0&envVersion=1.6.2
BerkeleyTrue pushed a commit to BerkeleyTrue/warning that referenced this pull request May 22, 2018
closes #18

Since the problem with uglify is fixed there is not reason to keep
browser version which do not add any benefits. Actually it blocks us
from distributing `esm` version which is widely used these days and
supported by the most popular bundlers like webpack, rollup and parcel.

Here's a few links
facebook/fbjs#86 (comment)

Both uglify and babel minify support evaulation and eliminating

```js
(function () {
  function warning() {}

  var __DEV__ = 'production' !== 'production'

  if (__DEV__) {
    warning = function (msg) {
      console.log(msg)
    }
  }

  warning()
} ());
```

https://skalman.github.io/UglifyJS-online/

https://babeljs.io/repl#?babili=true&browsers=&build=&builtIns=false&code_lz=BQMwrgdgxgLglgewgAmASmQbwFDOeaeJZAdwEMAnCOCAc3SwF9tdkA3S5AfS4BEBRAGo9kAXmQByAA4UEAEzCxEECcgCEo8dNkKlSCSzxwQqHgOFcMOPHnJUatMfkh6UwALYBnWldY3kUEieCAA2AKYAdCEI9F4-fsjMeMysdtR06NiMqGhoANxAA&debug=false&forceAllTransforms=false&shippedProposals=false&circleciRepo=&evaluate=false&fileSize=false&lineWrap=false&presets=babili%2Cenv&prettier=false&targets=&version=6.26.0&envVersion=1.6.2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants