facebook / create-react-app

Set up a modern web app by running one command.

Home Page:https://create-react-app.dev

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Support for staging builds

ehtb opened this issue · comments

commented

Hi, I thought about adding support for staging builds. I am not sure if this requires ejecting though, or could be a feature of CRA.

Our use case is that we create staging builds to deploy to our staging servers. This allows us to debug the app in a production like environment, but with the benefit of non-minimised code including proper sourcemap support and still includes the development warnings and notifications, as well as devtools support.

It can look very similar to webpack.config.prod.js, but with changes to L52-L54 and removal of webpack.optimize.UglifyJsPlugin.

commented

This is something worth considering for the future although I think we won’t add this near term.

still includes the development warnings

What is your use case for development warnings in staging builds?

commented

What is your use case for development warnings in staging builds?

Invariant warnings for instance, but also propType checks.

commented

Invariant warnings for instance

Which ones are you referring to? “Invariants” are not warnings, those are hard errors and exist both in production and development builds.

commented

Sorry, I meant more descriptive errors in the case of invariants. It serves the same purpose though, by making errors / bugs more transparent.

commented

I just pushed a first idea of how it could work. It uses the production Webpack config as a base and adds the relevant changes on top of it. Other than that it's mostly adding the proper scripts and small modifications. If you consider supporting it, let me know so I can create a pull request.

An alternative option could be to add a minify flag (react-scripts build --minify=false). We would forego on React's warning etc., which are removed in the production build, but it would make errors more transparent / easier to debug in a staging environment.

Bump - a staging build script would be good, or possibly the ability to override the NODE_ENV?

YES @msmfsd ! We need this. Maybe make passible to set NODE_ENV as you like on the build.

Something like:
"NODE_ENV=WHATEVERYOUWANT npm run build"

What's the alternative right now when deploying? I mean how is it possible to set 'NODE_ENV=staging'

@firaskrichi create a .env file in the root folder of your application, with the variable you want. For eg.: REACT_APP_ENV=whatever

console.log(process.env.REACT_APP_ENV) // will return "whatever".

I meant when deploying. Seems that NODE_ENV is set to production when deploying.

@firaskrichi indeed.

The only way I could find to control my environments was through custom vars. Since I use Jenkins, I created scripts to build the .env file according to my environment needs.

Right know NODE_ENV is 'development' before building, and 'production' after. You can't edit it.

I just set custom vars on Heroku and it works. Thanks @angusyoung84

I can confirm that during build time (yarn run build) NODE_ENV is hard coded to 'production'. It doesn't help if you set NODE_ENV to something else via shell / .env file.

@gaearon wouldn't it make more sense if NODE_ENV found in shell / .env will have precedence and fallback to 'production' during build time ?

commented

No, this is a dangerous footgun and will result in people unknowingly deploying development builds to production. Unfortunately I've seen this enough times to know it has to be hardcoded, and if we want to expose a separate build mode, it needs a separate command that you wouldn't confuse with a regular build.

commented

Somewhat related: #1070.

But to address your first points:

but with the benefit of non-minimised code including proper sourcemap support

We already include sourcemaps in production builds now.

as well as devtools support.

React DevTools have always been working with production builds.

I understand your reasoning.

I ended up interpolating index.html on the server, like the docs suggest. I tried to avoid this thing and be 100% client side logic, but it's not that bad.

However, I do think that this hard coded production when building behavior is not "standard" in Node. I expected NODE_ENV to be taken from an env variable and I was a little surprised that it was set to 'production' no matter what I did. Maybe the docs should mention this just to be sure. Maybe here: https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#npm-run-build

By the way, I think the docs are really great! cover every common use case and explains everything really well. Thanks 👍

commented

Happy to take a PR for docs.

Jumping into the fray on this one - @ehtb, have you had any luck putting together a staging PR? It looks like your fork has a fair bit of work done to it. My use case is pretty much the same as yours - I'd like my staging environment to mirror production, but to have traceable code (and use a staging data warehouse, which I'd switch to from the NODE_ENV setting).

commented

@mbifulco Sorry for the late reply, my example repo has diverged too far to be of any use anymore and probably wasn't right way forward for CRA.

This really should have been as simple as:

$ NODE_ENV=development npm build && s3 sync ./build/ s3://mybucket

You can't really mistakenly use the dev keys when you've explicitly set it on NODE_ENV. By forcing the env keys it's making it worse for everyone, because we can't easily support multiple environments.

You shouldn't try to save people from mistakes in there deployment process. A framework should provide the tools to support development, and a good one will help avoid pitfalls through documentation but not through pad locks :).

Not @gaearon, but the method I use:
Set REACT_APP_API_DOMAIN as an environment variable. Can be anything obviously, but must be prefixed with REACT_APP. Use it in your codebase as process.env.REACT_APP_API_DOMAIN.

I use cross-env to set env vars when building locally. Can also use dotenv or similar. #790 (comment) suggests create-react-app may just load .env automagically, I haven't used that method.

Ah, I see. You want to basically extend a base .env file and replace just the one variable. I'll defer comment then as I don't build staging/prod locally.

I've had the same issue in my app builds. I have multiple environments I need to deploy to (staging-1, qa, dev, prod, etc). In CRA, NODE_ENV seems to be used mostly for development vs an optimized build, but we need to be able to have optimized builds with different sets of environment variables.

My current hack is to circumvent .dotenv altogether, but it would be great to simply be able to specific something like APP_ENV=staging with NODE_ENV=production.

Time for another environment variable? :)

@jamesmfriedman I have created #3178, you can give it a try.

The trick here is that react/dotenv overwrite any env variable already set.

So I made a wrapper script build.js which I can run locally like this NODE_ENV=staging npm run build and the cool thing is that when appveyor builds production build which it, if build successfully uploads automatically to cloud servers it will also set some custom variables on build time.

const util = require("util");
const exec = util.promisify(require("child_process").exec);

process.env.NODE_ENV = process.env.NODE_ENV || process.argv[2] || "production";

// Load environment variables from .env files - the CRA way
require("react-scripts/config/env");

process.env.REACT_APP_VERSION = process.env.APPVEYOR_BUILD_VERSION || "local";
process.env.REACT_APP_COMMIT = process.env.APPVEYOR_REPO_COMMIT ||
  "local";

const BRANCH =  process.env.APPVEYOR_REPO_BRANCH;

// If building on appveyor - add and change some env vars.
if (BRANCH === "master" && process.env.NODE_ENV === "production") {
  process.env.GENERATE_SOURCEMAP = false;
}

async function main() {
  await exec("npm run build", { env: process.env });
}

try {
  process.stdout.write(`Building ${process.env.NODE_ENV}... `);
  main();
  process.stdout.write(`✅ \r\n`);
} catch (e) {
  console.log(`❌ Building of ${process.env.NODE_ENV} failed. `);
  console.log(e);
  process.exit(1);
}

I ended up doing this for my CI/CD using shippable:

In my case, all I'm using NODE_ENV for in my production code is to determine which set of APIs react needs to send requests to.

import defaultConfig from './config';
import routesBuilder from './routes';

// Get environment name
const ENV = process.env.REACT_APP_NODE_ENV || 'development';

const config = Object.assign({}, defaultConfig.default, defaultConfig[ENV]);
const routes = routesBuilder(config);
export default config;
export {routes};

This is how config.js looks like:

export default {
  development: {
    mainApiUrl: 'https://api-dev.mydomain.com/',
    secondaryApiUrl: 'https://api2-dev.mydomain.com/',
  },
  production: {
    mainApiUrl: 'https://api.mydomain.com/,
    secondaryApiUrl: 'https://api2.mydomain.com/,
  }
};

And this is how my deployment script looks like (much less relevant specific to shippable):

if [ "$BRANCH" == "master" ]; then export REACT_APP_NODE_ENV="production"; else export REACT_APP_NODE_ENV="development"; fi

This worked great for my use case.

Maybe it's worth adding to the docs as it looks like it's a common scenario.

@mjsisley's solution is way under appreciated.

This whole concept of preventing NODE_ENV to be overriden to prevent people from pushing their AWS keys to prod or pushing a dev build to prod feels like trying to fool-proof a tool that intrinsically requires you to have a certain level of knowledge; Enough, in my opinion, to know how NOT to push those keys to prod. I don't see a reason why would this tool want to fool-proof that.

That being said, what will probably happen is that instead of using NODE_ENV they will just create a REACT_APP_NODE_ENV custom env variable, and push their AWS keys/dev build to prod accidentally anyway 🙄. I don't see the benefit. It's just forcing us to create a non-standard environment variable to do environment checks.

I don't think that is a problem that this tool should be focusing to prevent

Edit: fixed grammar.

CRA2 defaults should be safe for general use, but denying overrides because it "is a dangerous footgun" is not a valid excuse. The fact that CRA2 explicitly says in the console, "Creating an optimized production build..." should be enough if the developer "accidentally" typed in NODE_ENV=development...

+1 for some way to override this. Don't care if it's DANGEROUSLY_SET_NODE_ENV=Staging :D

I could use this for css as I'm currently trying to diagnose a styling issue that happens with yarn build but not with yarn start.

Everything works when I run in yarn watch
But on build
image
which is https://reactjs.org/docs/error-decoder.html/?invariant=130&args[]=undefined&args[]=
Well, no clue. Lets try see source code
image
Pretty much useless. How to fix it? With magic 🔮

Chiming in here. I'm running my development API server in minikube; therefore it is going to have a different IP address / hostname than the application when started with npm start. I could use the "proxy" value in packages.json as documented, if it worked. Since it does not work for me, I ended up researching how to actually get CORS to work, which was a big enough pain that I wrote this. I could have avoided days of work, which is weeks of real time given that my development projects are not my day job, had there been an ability to publish a development build to a local directory. With a local persisted development build, I would have exposed it from the same host via another minikube container proxied to the same host. Why work this way? Because my basic principle that I'd prefer my development environment to be as close as reasonably possible to my production environment, and my production environment is going to be pure Kubernetes.

I know I could eject and tweak things. I know I could choose a different strategy to run my development API server. I like the simplicity that we get with create-react-app, I vehemently disagree with the design decision that there is no easy way to persist a development build. That design decision significantly complicates the use of create-react-app in my use case. Granted, my use case may not be your target for create-react-app, which is more my problem than yours.

Having said that, there seems to me that there are other potential safeguards for the concerns raised earlier in the discussion. Some of which already exist in the project. If, for example, I edit the URL in the browser used to load the build served by npm start to an alias of localhost, the app doesn't run. I get an error that the origin doesn't match the HOST setting. Brainstorming here, if you had a package.json value for development build host name, couldn't you use that mechanism to avoid running a development build on any host other than the one the developer explicitly identified as valid for development builds?

Thanks for listening, thanks for a very useful project, and hopefully my whining isn't excessive. Back to my project now.

I created a .env.production.local on staging server which contains

NODE_PATH=src/
REACT_APP_API_ROOT=http://api.dev.foo.bar

as this file would be ignored and has higher priority than .env.production

hope it helps.

I solved this by using npm's env-path and then in my package.json file:

"scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "build:staging": "env-path -p .env.staging react-scripts build"
...
}

( 'start' by default uses .env.development and
build to .env.production)
.env.staging file:

REACT_APP_VAR=**staging-var***

and the just npm run build:staging....

@Liel208 I cant seem to get that working env-path: command not found happens every time?

@The-Code-Monkey make sure you install this package: https://www.npmjs.com/package/env-path, then @Liel208's solution should work.

So there's still no way to run some "staging" environment now where there are watching js files disabled and hot-reloading disabled? It eats a lot of memory on our staging server (which is supposed to be a testing server).

I run my development server inside Docker and I cannot find a way to get it to use the .env.development settings.

I recently ran into this issue. I created a fork that adds --development and --production flags to the start and build commands. Now I can run react-scripts build --development (and react-scripts start --production, for that matter).

Is this something that CRA would be interested in merging?

Terrabits@ec2bd4a

... I don't have much experience with open source contribution or pull requests, so if there is value in this feature can someone help me make sure my code meets the standards of the project and walk me through the pull request?

This issue is well-aged like a fine European wine 🍾
We are using mobx and it has these nice checks for production environment https://github.com/mobxjs/mobx/blob/master/src/core/spy.ts which disable mobx dev tools on staging environments ☹️
Please consider adding staging environments support 🙏

commented

I think this issue already has some solutions:

  1. https://create-react-app.dev/docs/production-build#profiling
    "scripts": {
        "build:prod": "react-scripts build",
        "build:staging": "react-scripts build --profile",
    },
  1. https://create-react-app.dev/docs/deployment/#customizing-environment-variables-for-arbitrary-build-environments
    "scripts": {
        "build:prod": "react-scripts build",
        "build:staging": "env-cmd -f .env.staging react-scripts build --profile",
    },

@someden These solutions address only some special cases, but not the whole issue.
For example, they do not solve mine.

@someden

  1. Profiling is part of but not wholly sufficient for development builds
  2. react-scripts ignores/overwrites certain environment variables to ensure that start is always in a development environment (e.g. NODE_ENV=development) and build is always in production (e.g. NODE_ENV=production). The solutions that involve custom environment variables do not resolve this.

The only solutions I've found that build in development mode involve either ejecting CRA or building your own react-scripts, both of which are pretty painful options.

I got stuck on @Liel208's workaround for 30 minutes because I decided my custom variable would be named BUILD_ENV. This somehow gets plucked from the process.env if used. So anyone thinking of using the same variable name; don't. 😁

Edit

Okay, so actually tried APP_ENV and LADIDAH_ENV which also get pulled from the process.env object. So I'm guessing REACT_APP_VAR specifically gets passed on by create-react-app?

commented

@Terrabits go ahead and open a PR please :)

just read here https://github.com/facebook/create-react-app/blob/master/CONTRIBUTING.md#submitting-a-pull-request

NOBODY from the team has worked on this, and regarding the testing you could just explain a little bit the changes you introduced, on the PR description...

As per the code standards, in my opinion I would say that it's good... however, the process is to open the PR, and then comments can be made, and you can work from there. You can reference this issue on the PR, along with a descriptive title, you can also put fixes #790

But yeah, for now, i'm using your fork, so I would like this one to be added!

Strange to me that this was dismissed by the core team, as it seems like such an obvious need. I have two versions of an API key in my .env and .env.production files, for dev and prod which is fine. When I build and deploy to my staging environment, I explicitly don't want to use my production API key. I would prefer a staging-specific key, like how every environment everywhere generally works. I find it baffling that I can't seem to do this?

I guess I can pass my server ENV down to the client and jury-rig it, but why?

commented

@bmoeskau there is no problems with your case, it is simple to do it, just read my comment above #790 (comment)

This workaround might help some

#2880 (comment)

Being able to set development mode in a packaged build is a very useful thing. At this point all of our analytics are going to our production bucket because we have to generate builds to push to our staging environments. This is unacceptable from a user analytics standpoint. We can't have internal testing in our staging environments mucking with the data from our prod environment. I'm really surprised this hasn't been accounted for by now.

I recently ran into this issue. I created a fork that adds --development and --production flags to the start and build commands. Now I can run react-scripts build --development (and react-scripts start --production, for that matter).

Is this something that CRA would be interested in merging?

Terrabits@ec2bd4a

@Terrabits Just a quick note, I skimmed through your diff and it seems to me you're bypassing PUBLIC_URL handling, which we rely on for our pre-staging area, where a single webserver serves several React webapps each one under its own subpath.

If you just want to inspect the output if it was development, you can temporarily fix this by editing node_modules/react-scripts/scripts/build.js and replacing production with development:

process.env.BABEL_ENV = 'development';
process.env.NODE_ENV = 'development';

const config = configFactory('development');
commented

Has there been any traction on this? I'm trying to output a build with sourcemaps, etc. to be served with an app server. Maybe there's another way to do this that I'm not seeing, but right now it looks like if I want sourcemaps, I have to run npm run start, which serves from localhost:3000, and then bind my app server to a different port, and then enable CORS in the app server, which I can't do.

@gaearon also for your consideration: I think it's noble to want to protect users from accidentally releasing a development build, but there's other ways to protect against that like CI. Even if a development build was released to production, that code has to work. If it doesn't, it means it hasn't been tested, and at that point there's more issues in the process than just pushing a huge website to prod right? I don't expect this to necessarily change your mind, it's just a different perspective.

4 years for a flag? hmmm. this is related to the same issue of not being able to output a watchable dev build. preventing from using CRA in extension development.

If you just want to inspect the output if it was development, you can temporarily fix this by editing node_modules/react-scripts/scripts/build.js and replacing production with development:

process.env.BABEL_ENV = 'development';
process.env.NODE_ENV = 'development';

const config = configFactory('development');

And you can eject it before if you want to see the script build.js in the scripts folder (rather than diving into the node_modules).
Then you can make this change line 4-5 and 47.

You may have also to change the default appBuild: resolveApp('dist'), in the config/path.js file since development build files inside a dist folder.

Has there been any traction on this? I'm trying to output a build with sourcemaps, etc. to be served with an app server.

This is exactly my use case. There's quite a high barrier to doing something so common.

I don't want to use rewired just for this. We are trying to test builds inside of a web-component 😢

Another use case for this.

I'm using Skaffold to constantly rebuild docker images as I develop.

The production build takes a long time. It really slows down my development loop.

One more use case for this:

I am error searching my app in IE10 (I know...) and hot-reload doesn't work because of some dependencies that won't get polyfilled (there's some issue on that, can't find it now).

Anyway, only way to try my app in IE10 is through building it and serve. The errors in IE10 points to a minified version. Would love to have a development build.

I am developing an Application for a desktop app, using Chrome Embedded Frame, and there is a bug with source maps : the devtools cannot load them. Which makes development utterly punishing, exception always at line 3 or 4, forget debug, ...

I really would like a way to alter, overrides the build process without having to eject. At this time the only working solution I've found is https://github.com/timarney/react-app-rewired as it allows to override the webpack configuration.
There is a warning that tool appear lightly maintained, but the version 2.1.8 works with react-scripts 4.0.3

yarn --cwd internal-pwa add react-app-rewired

Here's what my config-overrides.js looks like.

module.exports = function override(config, env) {
    config.mode = "development";
    config.optimization.minimize = false;
    config.optimization.minimizer = [];
    config.devtool = 'inline-source-maps'
    
    return config;
};

This react-app-rewired has been a game changer for me. I hope the react-script tools could provide similar features.

Sadly adding to the pile of request for this... 5 years after the issue was opened. At some point I'm wondering if someone still really cares about the people using this project. (@gaearon 👀)

Staging builds are a thing, especially in projects/organisations where the established workflow imposes a build to be deployed in a staging environment (through CI) and where you want to keep the flexibility of having an unminified build to allow meaningful bug reporting with the help of the console.

I don't see any valid reason not to have this ; there's no solid case to refute a staging configuration mode as proposed in the original issue ; there's no cost in doing so (the OP was kind enough to give pointers and it seems fairly straightforward), and there's even less reason not to let the consumer pass their own NODE_ENV value (NODE_ENV=development npm run build).

And for sake not letting people do what they wish with their build is nothing but the goal of a convenient developer companion tool.

PS: You're free to delete this comment - i'm probably gonna have to use rewire as i'm certainly not gonna wait 5 more years to see a move.

PS2:

No, this is a dangerous footgun and will result in people unknowingly deploying development builds to production.

The problem isn't the tool in that case, but the people using it. In the same way you can't prevent a person from using a regular screwdriver on a cruciform screw. Add blinking warnings at build time if you wish to prevent and educate them, call the option dangerouslySetNodeEnv= but don't be an ass by blocking them.

This feature would be very welcome for doing mobile app development with React and projects like Capacitor and Ionic. In those situations you have to deploy a built bundle to a native app container, so having the ability to do a dev build is actually really useful as a development aid. Will look into the workarounds suggested here but hope some day this gets reconsidered!

My takeaway from this: don't use NODE_ENV in general in my app code, and instead use something I have total control over. Just gonna use something like APP_ENV (or rather REACT_APP_ENV) instead. I don't see how this hurts anything. I'd rather stick to the conventional NODE_ENV tho

The same problem.

React dev builds report about some issues like that popular one with "key", right?
But in production builds they completely disappear leaving bad code untouched.
So what is it in CRA workflow which could prevent this? Nothing. No way to automate this process and catch the warnings.
Only visual console monitoring in the "npm run start" instances....

Don't forget that CRA is more for prototyping, not for real projects folks. Just don't use it for something real.
Unfortunately in our company we made this mistake and now we're stuck to CRA and get ourselves into some trouble like this one.

Really CRA is not for production?

Really CRA is not for production?

We use it happily in production for many tools

Don't forget that CRA is more for prototyping, not for real projects folks.

@OnkelTem is there somewhere written something to support that claim?

I need the development build feature for dev server!
I hope the discussion on this continues.

commented

Sometimes their are spa's that are more closely coupled to the API or the environment of the backend server. When run from the dev server these bugs are not exercised because all these dependencies are mocked. I basically just ejected the spa.

I don't want to use rewired just for this. We are trying to test builds inside of a web-component 😢

My plan was to use build for wdio tests under docker-compose, sadly rewired also doesn't allow to trivially re-set the envirnoment.

As a workaround, I have used the https://github.com/ds300/patch-package to build the development version.

I have patched react-scripts/scripts/build.js by commenting the process.env.NODE_ENV = 'production'; line.

Is

NODE_ENV=development react-scripts build

working now?
The internet says to use dotenv, is there magic happening there?

With resct-scripts v5 work this in react-scripts/scripts/build.js. It will force webpack development build

// Generate configuration
const config = configFactory('development'); // configFactory('production');
  1. convert your lockfile to version 2
npm shrinkwrap --lockfile-version 2
  1. edit node_modules/react-scripts/scripts/build.js
    and change line 12 | 13 + 58:
// process.env.BABEL_ENV = "production"
// process.env.NODE_ENV = "production"
process.env.BABEL_ENV = process.env.NODE_ENV
process.env.NODE_ENV = process.env.NODE_ENV

// const config = configFactory("production"); 
const config = configFactory(process.env.NODE_ENV);
  1. use patch-package to create a patch:
npx patch-package react-script
  1. package.json change build and add build-dev + postinstall patch
"scripts": {
...
"build": "NODE_ENV=production react-scripts build",
"build-dev": "NODE_ENV=development react-scripts build",
"postinstall": "npx patch-package"
...
},

thanks for the idea @sytolk @chaosmirage

To fix development builds error:

NODE_ENV=development react-scripts build:

Creating an optimized production build...
Compiled with warnings.

...

ENOENT: no such file or directory, open '/Users/nezort11/dev/d-etf/build/static/js/bundle.js'

You also need to comment printFileSizesAfterBuild code (lines 110-116):

      // printFileSizesAfterBuild(
      //   stats,
      //   previousFileSizes,
      //   paths.appBuild,
      //   WARN_AFTER_BUNDLE_GZIP_SIZE,
      //   WARN_AFTER_CHUNK_GZIP_SIZE
      // );