ctrlplusb / react-universally

A starter kit for universal react applications.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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

strues opened this issue ยท comments

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]

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

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 ๐Ÿ˜€)

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

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

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.

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.

Comments updated. ๐Ÿ’–

@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.

@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.

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.

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?

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.

Reopening this issue for transparency.

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. ๐Ÿ’ฉ

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.

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.

:(

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"

@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. ๐Ÿ˜€

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.

@saniko choose what works for you right now.

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.

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