create-react-app icon indicating copy to clipboard operation
create-react-app copied to clipboard

Tree shaking only works with process.env.NODE_ENV?

Open NAllred91 opened this issue 4 years ago • 13 comments

I'm finding that tree shaking only works with process.env.NODE_ENV and won't work with process.env.REACT_APP_[my variable name]

For example, if I do the following myModule is not showing up in my production build.

import {myFunction} from './myModule'

if(process.env.NODE_ENV === 'development') {
    myFunction()
}

But, if I want to use a different environment variable myModule is always included.

import {myFunction} from './myModule'

if(process.env.REACT_APP_ENABLE_MY_FUNCTION === 'true') {
    myFunction()
}

Is it known that tree shaking only works with process.env.NODE_ENV? Is there any way to do tree shaking with a custom environment variable?

NAllred91 avatar Sep 21 '20 16:09 NAllred91

Now I see that REACT_APP_ environment variables are available at runtime, whereas NODE_ENV is not. So this makes sense that using REACT_APP_ environment variables wouldn't enable tree shaking.

I guess that means my question is, is there a way to add custom environment variables that behave like NODE_ENV, where process.env.NODE_ENV is replaced with a string at build time? It seems the answer is no?

I see here (https://github.com/facebook/create-react-app/issues/8626) someone suggested using a dynamic import, which ultimately works if there is no other solution. However, then I'll need to write a post-build script to delete these bundles if I want to make sure they aren't on my server. And I need to update my logic to handle asynchronously loading the module, which isn't ideal.

NAllred91 avatar Sep 21 '20 16:09 NAllred91

Here it is....

If you want to do this:

import {myFunction} from './myModule'

if(process.env.REACT_APP_ENABLE_MY_FUNCTION === 'true') {
    myFunction()
}

You must be sure to define REACT_APP_ENABLE_MY_FUNCTION. If you do not define it then the if(process.env.REACT_APP_ENABLE_MY_FUNCTION === 'true') doesn't get turned into if(false) at build time.

But it still evaluates to false at runtime.

So when using this command (REACT_APP_ENABLE_MY_FUNCTION=false npm run build), myModule is not included in the build. But if I just run npm run build without explicitly setting REACT_APP_ENABLE_MY_FUNCTION it is included.

I don't see this documented anywhere, but it probably should be. Although I realize this behavior comes from something create-react-app depends on and not create-react-app itself. The only way I figured this out was by taking the time to go through the generated bundle.

NAllred91 avatar Sep 21 '20 17:09 NAllred91

This issue has been automatically marked as stale because it has not had any recent activity. It will be closed in 5 days if no further activity occurs.

stale[bot] avatar Dec 25 '20 20:12 stale[bot]

I also faced this issue. Thanks for your investigation on this. I helped a lot! Would be great to have this documented. Do you think you can contribute this to the documentation?

mucsi96 avatar Jan 22 '21 19:01 mucsi96

I also faced this issue. Thanks for your investigation on this. I helped a lot! Would be great to have this documented. Do you think you can contribute this to the documentation?

I'm not completely sure this is the best practice for achieving what I'm trying to do, so it would be nice to get some feedback from someone more knowledgable first.

For more clarity, my specific use case here is that I have two distinct builds I want to do for my app created with create-react-app. I have one build that is my standard web build, hosted on Netlify, and another build that is for packaging with Cordova. In my standard web build I want to strip out all Cordova references at build time. Using the method I described above is working for this use case, however, since it was so tricky to figure out I do wonder if there is a more appropriate approach to solving this problem?

NAllred91 avatar Jan 25 '21 14:01 NAllred91

Hi @NAllred91,

I am having a problem tree shaking some code for production while experimenting with MirageJs for development following this and got here while looking for solutions.

The thing is I am not even being able to tree shake my code with process.env.NODE_ENV.

I have tried the following

import { makeServer } from "./server";

if (process.env.NODE_ENV === "development") {
  makeServer();
}

or

import { makeServer } from "./server";

if (false) {
  makeServer();
}

And for any of the cases, the code is removed. It is removed if I do

import { makeServer } from "./server";

if (false) {
  // makeServer();
}

I am using the yarn build command, which uses react-scripts. The version I am using of react-script is 4.0.3. It is a brand new project created with create-react-app with no major changes. Which version are you using?

I am using the typescript template of create-react-app.

Do you have any ideas of what it could be?

Thanks and sorry for bothering you with not related things.

joaoantunesTD avatar Mar 29 '21 13:03 joaoantunesTD

@joaoantunesTD I'm on 3.4.1 for react-scripts, I doubt that matters much but maybe give it a try on that version and see if you have the same issue?

It is odd that if(false) doesn't work, but commenting out the call to makeServer does. All I can think is to try 3.4.1 and see if you get the same results?

I was also using typescript when I created this issue. I have never tried with javascript.

NAllred91 avatar Mar 29 '21 13:03 NAllred91

Thanks @NAllred91 for answering so fast :) !

Just to let you know what I have found. I do not know really why, but if I move the code inside theif I have the code removed on production build. I moved the miragejs import to index.tsx and I have now something like:

./server.ts

import { AnyFactories, AnyModels } from "miragejs/-types";
import { ServerConfig } from "miragejs/server";

export const BASE_CONFIGS: ServerConfig<AnyModels, AnyFactories> = {
  // configs removed here, just to show
};

export function generateConfigsForEnv(
  environment = "test"
): ServerConfig<AnyModels, AnyFactories> {
  return {
    environment,
    ...BASE_CONFIGS,
  };
}

And then on index.tsx:

import React from "react";
import ReactDOM from "react-dom";
import { createServer } from "miragejs";

import "./index.css";
import App from "./App";

import { generateConfigsForEnv } from "./server";

if (process.env.NODE_ENV === "development") {
  createServer(generateConfigsForEnv("development"));
}

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById("root")
);

joaoantunesTD avatar Mar 29 '21 14:03 joaoantunesTD

I also face this problem. follow above comments,i found import did not work but require did work,and "react-scripts": "4.0.3"。when I use import i found axios-mock-adapter in my bundle.js image

import: image

require: image

//mock/index.ts image

my english is not good,sorry about this.

wangi4myself avatar Apr 23 '21 10:04 wangi4myself

Hi @wangi4myself ,

Thanks! Interesting, I have not tried with require. I ended up using an asynchronous import, something like this:

if (process.env.NODE_ENV === 'development') {
  (async function loadApiMockServer() {
    const apiMock = await import('my-lib');

    apiMock.initiateApiMock({ environment: 'development' });
  })();
}

PS: Your English is fine :)

joaoantunesTD avatar Apr 23 '21 10:04 joaoantunesTD

Having the same problem, and here is my solution:

if (process.env.NODE_ENV !== 'production') {
  import('./server').then(({ makeServer }) => {
    makeServer({ environment: 'development' });
  });
}

minhchu avatar Mar 13 '22 18:03 minhchu

Async imports only split code to another chunk but will not prevent it from bundling it to final dist.

My solution is to always define a value with webpack define plugin:

new webpack.DefinePlugin({
    'process.env.MOCK': JSON.stringify(process.env.MOCK === 'true')
}),

In you app code, don't do any comparison check, so it will be replaced at build time and resolves to if (false) {...} then the whole import will be eliminated.

import startMock from './mock'

// don't do any comparison here.
if (process.env.MOCK) {
    startMock()
}

zoubingwu avatar Apr 12 '22 07:04 zoubingwu

@zoubingwu Thank you, your solution is the best here and it works fine for me.

I ended up a little improve in this way, it avoids conflict env error.

new webpack.DefinePlugin({
  ...(!process.env.REACT_APP_ENABLE_MSW && {
    'process.env.REACT_APP_ENABLE_MSW': false,
  }),
}),

iamyoki avatar Aug 28 '22 16:08 iamyoki