Skip to content
This repository has been archived by the owner on Jun 3, 2019. It is now read-only.

[v13] Optimize flag isn't behaving as expected. #359

Closed
strues opened this issue Feb 19, 2017 · 21 comments
Closed

[v13] Optimize flag isn't behaving as expected. #359

strues opened this issue Feb 19, 2017 · 21 comments

Comments

@strues
Copy link
Collaborator

strues commented Feb 19, 2017

Something isnt quite right with building for production with the optimize flag. From the looks of it, and the way Webpack's Define Plugin works, process.env.NODE_ENV is required for Webpack to grab the correct React.

You are currently using minified code outside of NODE_ENV === 'production'. This means that you are running a slower development build of Redux. You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify or DefinePlugin for webpack (http://stackoverflow.com/questions/30030031) to ensure you have the correct code for your production build.

Warning: It looks like you're using a minified copy of the development build of React. When deploying React apps to production, make sure to use the production build which skips development warnings and is faster. See https://fb.me/react-minification for more details.

Webpack is grabbing the specifically targeted minified version of React.

chunk {0} offline.html 144 kB [entry] [rendered]
[0] .//react/dist/react.min.js 21.2 kB {0} [built]
[1] ./shared/components/Html/index.js 28 bytes {0} [built]
[2] ./
/react-dom/dist/react-dom-server.min.js 119 kB {0} [built]
[3] ./shared/components/Html/Html.js 2.69 kB {0} [built]
[4] ./~/babel-loader/lib!./internal/webpack/withServiceWorker/offlinePageTemplate.js 1.15 kB {0} [built]

@strues
Copy link
Collaborator Author

strues commented Feb 19, 2017

Solution is here:

      new webpack.DefinePlugin({
        'process.env.NODE_ENV': JSON.stringify(NODE_ENV),
        // Is this the "client" bundle?
        'process.env.BUILD_FLAG_IS_CLIENT': JSON.stringify(isClient),
       // Is this the "server" bundle?
        'process.env.BUILD_FLAG_IS_SERVER': JSON.stringify(isServer),
       // Is this a node bundle?
        'process.env.BUILD_FLAG_IS_NODE': JSON.stringify(isNode),
       // Is this a development build?
        'process.env.BUILD_FLAG_IS_DEV': JSON.stringify(isDev),
      }),

Adding process.env.NODE_ENV back to define plugin

@ctrlplusb
Copy link
Owner

ctrlplusb commented Feb 19, 2017

Hey @strues!

Ok, so a bit of backstory here. Personally I find the need to set NODE_ENV quite confusing in getting a production build of React/ReactDOM. I wouldn't mind it as much if only required NODE_ENV !== 'development', however, it requires NODE_ENV === 'production' which can be confusing if you are doing stuff like NODE_ENV=staging yarn run build.

In order to move away from the use of NODE_ENV I thought we could instead alias the "optimised / production ready" versions of our code to point directly to minified distribution code of React/ReactDOM. I did try running this and prop type warnings were not being thrown, whilst they still get thrown when I run the app in development mode. So I can only assume that all the development based react code isn't being included. I also run the analyze:client command to ensure that only the minified dist versions of React/ReactDOM were being included.

I may have missed something though, so would you mind giving me a bit more info? Are you trying to run the next branch and are for sure getting all the development related React code being executed even when doing the --optimize flag based build?

One thing is for sure I need to document my reasoning behind this more (well, if I decided to keep my changes that is 😀)

@ctrlplusb
Copy link
Owner

FYI, here is a deployed version of next as it stands:

https://react-universally-eapqzeduqo.now.sh

@strues
Copy link
Collaborator Author

strues commented Feb 20, 2017

Another update - completely wiped everything and started from scratch. So far so good. I might've had a rogue NODE_ENV somewhere leftover from a git merge.

@ctrlplusb
Copy link
Owner

Nice thanks for letting me know. I am going to put a proper docs section in that explains my decision, along with comments by the DefinePlugin.

@ctrlplusb
Copy link
Owner

Comments updated. 💖

@diondirza
Copy link
Contributor

diondirza commented Feb 24, 2017

@ctrlplusb @strues when I migrating my project to next branch, kinda face this error recently

https://facebook.github.io/react/docs/error-decoder.html?invariant=119

after investigating for a while my suspect is caused by multiple copies of react by this alias config

ifOptimize({
  react$: path.resolve(
    appRootDir.get(), './node_modules/react/dist/react.min.js',
  ),
  'react-dom$': path.resolve(
    appRootDir.get(), './node_modules/react-dom/dist/react-dom.min.js',
  ),
  'react-dom/server$': path.resolve(
    appRootDir.get(), './node_modules/react-dom/dist/react-dom-server.min.js',
  ),
}),

remove that config will solve the problem, it also make our bundle size increased by 5~8kb, instead using DefinePlugin to set variable
'process.env.NODE_ENV': JSON.stringify('production'),
make our bundle smaller.
You could try reproduce this in next branch and compare index.js file size with build by using alias config or just with flag of NODE_ENV in DefinePlugin.

@birkir
Copy link
Collaborator

birkir commented Feb 24, 2017

@diondirza We have no way of knowing beforehand which node modules use the NODE_ENV to optionally do something, therefore you have packages that use NODE_ENV that are not aliased and will result in a size difference after compile.

But here is the thing, @ctrlplusb's method is way more performant (I think), because if you look at the code of ./node_modules/react-dom/package.json you can see that main does not actually point to the dist build, but the raw source instead. So webpack needs to do a search / replace of the NODE_ENV variable all over the place, or 344 times (grep -r "NODE_ENV" . | wc -l), rather than just use a pre-built package for production.

On the other hand, other non-aliased packages will leak debug info in production so I'm not sure what to do.

@birkir
Copy link
Collaborator

birkir commented Mar 1, 2017

Update on this issue, it seem that we will have to use NODE_ENV after all.

One dev in my team was having issues with dupe react, one of them being dev build, some packages spitting out debugging info, etc. (around 100 third party deps).

We fixed it by adding 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV), and removing the aliases for now.

@ctrlplusb
Copy link
Owner

Yeah, I have been thinking on this for a bit. Using aliases is flaky as certain dependencies could refer to react addons package for example, so it's really easy to get duplicate dependencies bundles.

I'm happy for us to revert, however, something to be aware of: the React build optimisation only responds to NODE_ENV=production. So NODE_ENV=preprod results in a development version of React. :(

We could do the following:

'process.env.NODE_ENV': JSON.stringify(isDev ? 'development' : 'production')

But then we may have flags in our code that target specific environments.

Thoughts?

@birkir
Copy link
Collaborator

birkir commented Mar 1, 2017

Good stuff.

Only thing we should care about is what npm packages are generally using for reference. After some research they all seem to be doing if (process.env.NODE_ENV !== 'production') {.

Some packages are using if (process.env.NODE_ENV === 'test') { but seem to be used only in build/publish steps of the packages.

So if the user wants to do different stuff on different environments it's up to him to do so, IMO.

@ctrlplusb ctrlplusb reopened this Mar 1, 2017
@ctrlplusb
Copy link
Owner

Reopening this issue for transparency.

@ctrlplusb
Copy link
Owner

Another important point in why I went for alias approach. If you don't pass NODE_ENV=production to your server call then server rendering react runs in development mode. This is even bigger of a shitter to deal with as NODE_ENV is often used in a server context to say test, staging, etc.

Ideally you want your preprod/staging environment to emulate the production, but you may also want to load environment specific configuration based on the NODE_ENV. 💩

@birkir
Copy link
Collaborator

birkir commented Mar 2, 2017

Ok understood. Is there any way to inject NODE_ENV to only node_modules? If not, I may attempt to clone DefinePlugin and add a filter to it.

@ctrlplusb
Copy link
Owner

Yeah, it's a strange situ to handle without adding too much confusion.

My ideal would be that node_modules would do the following check for development builds:

if (NODE_ENV === 'development') {

Then anything but 'development' would result in optimised builds.

:(

@birkir
Copy link
Collaborator

birkir commented Mar 2, 2017

What do you think about this solution?

ueno-llc/starter-kit-universally@4644ba6

So we set NODE_ENV in build time with the define plugin, but export the real NODE_ENV via the EnvVars. I know it's confusing but the best solution I've come up with yet.

In server/index.js:

console.log(`> NODE_ENV is ${process.env.NODE_ENV}`);
console.log(`> config('NODE_ENV') is ${config('NODE_ENV')}`);
console.log(`> EnvVars.string('NODE_ENV') is "${EnvVars.string('NODE_ENV')}"`);

After build:

$ node build/server
> NODE_ENV is production
> config('NODE_ENV') is development
> EnvVars.string('NODE_ENV') is ""

$ NODE_ENV=staging node build/server
> NODE_ENV is production
> config('NODE_ENV') is staging
> EnvVars.string('NODE_ENV') is "staging"

$ NODE_ENV=production node build/server
> NODE_ENV is production
> config('NODE_ENV') is production
> EnvVars.string('NODE_ENV') is "production"

@ctrlplusb
Copy link
Owner

@birkir this is great thanks. I am going to experiment with this a bit myself and see how it feels to me.

I appreciate your help on this one. It feels like an awkward one to find a good solution for. 😀

@ctrlplusb ctrlplusb changed the title Optimize flag isn't behaving as expected. [v13] Optimize flag isn't behaving as expected. Mar 5, 2017
@saniko
Copy link

saniko commented Apr 2, 2017

Hi folks, have the same issue as @diondirza,
happens to me with velocity-react.

Is there any plan to mitigate this?
Or should I use @birkir or @diondirza solution for now?

Thanks.

@diondirza
Copy link
Contributor

@saniko choose what works for you right now.

@ctrlplusb
Copy link
Owner

Hey all;

A relevant discussion: facebook/react#6582

I have decided to roll back to not using aliases and instead relying on NODE_ENV=production for optimised React etc. I don't like it, but it's industry standard at the moment.

The environment configuration files can now be specified via the usage of a DEPLOYMENT env var. I've updated the docs accordingly.

On next.

@birkir
Copy link
Collaborator

birkir commented Apr 4, 2017

Great @ctrlplusb, I think it's actually not so confusing solution.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants