How Do I Build For A UAT Environment Using React? - node.js

According to the React docs you can have development, test and production envs.
The value of NODE_ENV is set automatically to development (when using npm start), test (when using npm test) or production (when using npm build). Thus, from the point of view of create-react-app, there are only three environments.
I need to change root rest api urls based on how I am deployed.
e.g.
development: baseURL = 'http://localhost:3004';
test: baseURL = 'http://localhost:8080';
uat: baseURL = 'http://uat.api.azure.com:8080';
production: baseURL = 'http://my.cool.api.com';
How do I configure a UAT environment for react if it only caters for dev, test and prod?
What would my javascript, package.json and build commands look like to switch these values automatically?

Like John Ruddell wrote in the comments, we should still use NODE_ENV=production in a staging environment to keep it as close as prod as possible. But that doesn't help with our problem here.
The reason why NODE_ENV can't be used reliably is that most Node modules use NODE_ENV to adjust and optimize with sane defaults, like Express, React, Next, etc. Next even completely changes its features depending on the commonly used values development, test and production.
So the solution is to create our own variable, and how to do that depends on the project we're working on.
Additional environments with Create React App (CRA)
The documentation says:
Note: You must create custom environment variables beginning with REACT_APP_. Any other variables except NODE_ENV will be ignored to avoid accidentally exposing a private key on the machine that could have the same name.
It was discussed in an issue where Ian Schmitz says:
Instead you can create your own variable like REACT_APP_SERVER_URL which can have default values in dev and prod through the .env file if you'd like, then simply set that environment variable when building your app for staging like REACT_APP_SERVER_URL=... npm run build.
A common package that I use is cross-env so that anyone can run our npm scripts on any platform.
"scripts": {
"build:uat": "cross-env REACT_APP_SERVER_URL='http://uat.api.azure.com:8080' npm run build"
Any other JS project
If we're not bound to CRA, or have ejected, we can easily configure any number of environment configurations we'd like in a similar fashion.
Personally, I like dotenv-extended which offers validation for required variables and default values.
Similarly, in the package.json file:
"scripts": {
"build:uat": "cross-env APP_ENV=UAT npm run build"
Then, in an entry point node script (one of the first script loaded, e.g. required in a babel config):
const dotEnv = require('dotenv-extended');
// Import environment values from a .env.* file
const envFile = dotEnv.load({
path: `.env.${process.env.APP_ENV || 'local'}`,
defaults: 'build/env/.env.defaults',
schema: 'build/env/.env.schema',
errorOnMissing: true,
silent: false,
});
Then, as an example, a babel configuration file could use these like this:
const env = require('./build/env');
module.exports = {
plugins: [
['transform-define', env],
],
};
Runtime configuration
John Ruddell also mentioned that one can detect at runtime the domain the app is running off of.
function getApiUrl() {
const { href } = window.location;
// UAT
if (href.indexOf('https://my-uat-env.example.com') !== -1) {
return 'http://uat.api.azure.com:8080';
}
// PROD
if (href.indexOf('https://example.com') !== -1) {
return 'http://my.cool.api.com';
}
// Defaults to local
return 'http://localhost:3004';
}
This is quick and simple, works without changing the build/CI/CD pipeline at all. Though it has some downsides:
All the configuration is "leaked" in the final build,
It won't benefit from dead-code removal at minification time when using something like babel-plugin-transform-define or Webpack's DefinePlugin resulting in a slightly bigger file size.
Won't be available at compile time.
Trickier if using Server-Side Rendering (though not impossible)

To have multiple environments in a React.js application you can use this plugin
env-cmd from NPM
And after that Create the three files as per your need.
For example if you want to setup dev, stag and prod environments you can write your commands like this.
"start:dev": "env-cmd -f dev.env npm start", // dev env
"build:beta": "env-cmd -f stag.env npm run build", // beta env
"build": "react-scripts build", // prod env using .env file

Related

process.env.NODE_ENV always 'development' when building nestjs app with nrwl nx

My NX application's npm run build:server calls ng build api-server that triggers the #nrwl/node:build builder.
It builds the NestJS application as main.js. Things work except I wanted process.env.NODE_ENV to be evaluated at runtime but I think it was resolved at build time (via Webpack).
Currently, the value is always set to 'development'.
I am new to Nrwl's NX. Any solution this?
In NestJs/Nodejs app in Nx.Dev workspace process.env.NODE_ENV is replaced during compilation from typescript to javascript very "smart way" to "development" string constant (everything like NODE_ENV is replaced). I don't know why. But only way how can I get real NODE_ENV in runtime is this code:
//process.env.NODE_ENV
process.env['NODE' + '_ENV']
The reason you're seeing development is because you're building the app in development mode - it's not best practice to evaluate at runtime because then the builder can't do fancy things to make the build production ready. If you want production, you need to build the app in production mode by adding the --prod flag (just like how you need to build Angular in production mode).
If you need to serve the app in production mode (instead of build) the default config doesn't provide you with a prod mode for serve. You'll need to add the configuration to your angular.json.
So this code:
"serve": {
"builder": "#nrwl/node:execute",
"options": {
"buildTarget": "api-server:build"
}
},
Would become
"serve": {
"builder": "#nrwl/node:execute",
"options": {
"buildTarget": "api-server:build"
},
"configurations": {
"production": {
"buildTarget": "api-server:build:production"
}
}
},
and then you can run
ng serve --project=api-server --prod
Indeed the nx builder will replace the expression process.env.NODE_ENV in our source code with the current value of the env var (or the nx-mode).
What happens is this:
the build command executes the nx builder which creates a configuration for web-pack
this configuration instructions for the webpack-define plugin to replace the text process.env.NODE_ENV during compilation with the actual value of the env-var (or the nx-mode):
see nx-code getClientEnvironment()
Since the webpack-define plugin will look for the text process.env.NODE_ENV, it's easy to use a workaround as explained in this answer:
process.env['NODE'+'_ENV']
Warning
When you need to apply this workaround to make your app work, then something is wrong. Since you have compiled your app in production mode, it does not make sense to pass another value for NODE_ENV when you start the (production) app.
The webpack Production page contains some helpful info.
We also had this case, and the issue was, that we relied on the NODE_ENV variable to load different database configs for dev, prod, test, etc.
The solution for our case was to simply use separate env-vars for the database config (e.g. DB_NAME, DB_PORT, ..), so that we can use different db-configs at runtime with any build-variants: dev, prod, test, etc.
I recently faced the same problem using Express instead of Nest.
What we did to overcome this was adding some file replacements when compiling for any of our environments (development, production, staging, staging-dev). This is done in the angular.json file the same way the environment files are replaced for the Angular app .
Another approach that worked for us, was loading the environment variables only once, and retrieve them from that origin. As our app relies on Express for it's backend the used the Express env variable as:
import express from 'express';
const _app = express();
const _env = _app.get('env');
console.log(_env); // shows the right environment value set on NODE_ENV
To come to this conclusion we checked Express code for the env variable and it does use process.env.NODE_ENV internally.
Hope it helps. Best regards.
We had the same issue, we eventually used the cross-env package in our package.json:
"prodBuild": "cross-env NODE_ENV=production nx run api-server:build:production",
"prodServe": "cross-env NODE_ENV=production nx run api-server:serve:production"
process.env is indeed only available at run-time. What is probably happening is that you are not setting this value when running your application. Can I ask how you are running it?
As a trivial example
# The following will read the environment variables that are defined in your shell (run `printenv` to see what those are)
> node main.js
# this will have your variable set
> NODE_ENV=production node main.js
Of course you want to have it actually set in your environment when deploying the app rather then passing it in this way, but if you're doing it locally you can do it like this.

How to make build for react app for different stages?

I have a single page application which is a react app. I am using webpack for it. I am facing problem in configuring server API URL for every stage like test, beta and prod.
Is there some standard way of doing it?
Create a .env and add your variables there ensuring that they are prefixed with REACT_APP e.g. REACT_APP_SERVER_URL=https://example.com
You can create multiple env files one each for dev, prod, test etc. like .env.local, .env.prod
The env files injected from your npm commands
npm start: .env.development.local, .env.development, .env.local, .env
npm run build: .env.production.local, .env.production, .env.local, .env
Use the variable in your code like
if (process.env.NODE_ENV !== 'production') {
analytics.disable();
}
OR
<b>{process.env.NODE_ENV}</b>
Refer https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#adding-development-environment-variables-in-env
Do that based on NODE_ENV.
declare an url file in your application for the basepath of it.
const baseURL = (__DEV__) ? url1 : url 2;
or a switch statement, does'nt matter.
Do be able to have access to these variables, you have to use DefinePlugin from webpack.
new webpack.DefinePlugin({
envType: JSON.stringify(process.env.NODE_ENV)
})
or something like that...

Target certain environments based on console input

To preface the question I am using an ejected create-react-app for the layout of my project.
I have 5 environments that my application is going to be deployed to. Each environment has the same set of services (mostly), for example one might look like:
//environment 1
https://environment1.service1.foo.bar
https://environment1.service2.foo.bar
//environment 2
https://environment2.service1.foo.bar
https://environment2.service2.foo.bar
To achieve this on past projects (Angular/Gulp) I had a gulp task that essentially would look for a variable being passed in
gulp build --environment environment1
The code to do so looks like this:
gulp.task('environment', ['clean-environment'], function() {
log('Copying environment');
var environmentFile = config.environmentSrcDir + 'env2.js';
if (args.environment !== 'env2' ||
args.environment === 'env3' ||
args.environment === 'env4' ||
args.environment === 'evn5') {
environmentFile = config.environmentSrcDir + args.environment + '.js';
}
return gulp
.src(environmentFile)
.pipe(rename(config.environmentService))
.pipe(gulp.dest(config.root));
});
And basically point to the correct file with the correct endpoints inside of it as well as other pertinent variables associated with that environment.
My question is, where given the fact that I am using create-react-app as a starting point, so webpack, and node scripts, how would I accomplish something like this. Basically I want to be able to say yarn build env1 and then the project to set a constant or file of constants as the 'active` constants so to speak.
If you are using "Create-react-app" then you have the ability to define development environment variable through different .env files.
Link: https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#adding-development-environment-variables-in-env
.env:
REACT_APP_GOOGLE_CLIENT_ID = XXX-YYY-ZZZ.apps.googleusercontent.com
REACT_APP_API_PROTOCOL = http:
REACT_APP_API_HOST = localhost:3000
NODE_PATH = src/scripts
PORT = 9001
.env.production:
REACT_APP_GOOGLE_CLIENT_ID = ZZZ-YYY-XXX.apps.googleusercontent.com
REACT_APP_API_PROTOCOL = https:
REACT_APP_API_HOST = api.my-applicaton.com
NODE_PATH = src/scripts
Read different .env configs according to current command (start / test / build). dev.env for start and test. prod.env for build. If custom config does not exist — read env variables from .env file.
Blockquote
You will tell which .env file will be used with your start project command.
You should have something like this in your package.json file under scripts object:
"start-js": "react-scripts start",
"start": "npm-run-all -p watch-css start-js",
"build": "npm run build-css && react-scripts build",
Then you can start your project using specified commands. From the documentation, files on the left have more priority than files on the right:
npm start: .env.development.local, .env.development, .env.local, .env
npm run build: .env.production.local, .env.production, .env.local, .env
npm test: .env.test.local, .env.test, .env (note .env.local is missing)
For example, if you start with npm run build, you will be able to access variables defined in .env.production file.
In JavaScript code, you can use process.env.REACT_APP_API_HOST.
Also, see this link on medium.com: https://medium.com/#tuchk4/why-i-love-create-react-app-e63b1be689a3

How can I set properties conditionally in Node.js via Atom?

I am developing a Node.js app with Electron via Atom.
I want to set some properties conditionally(or automatically), for instance, the url should be http://some.url on production level.
Currently, I use like this.
// win.loadURL('http://app.url/webchat'); //uncomment when production
win.loadURL('http://test.app.url/webchat'); // uncomment when development
This is very annoying me, and it can be a problem when I miss changing comments.
How can I change my properties conditionally with production/development level?
I have a config directory with different config files for different environments: dev, test, prod. Then in my package.json I have added environment specific build commands. e.g. For prod:
"build-prod-config": "config/buildProdConfig.sh",
"build-renderer-prod": "webpack --config=webpack.config.renderer.prod.js",
"build-main-prod": "webpack --config=webpack.config.main.prod.js",
"build-prod": "npm run build-prod-config && npm run build-main-prod & npm run build-renderer-prod",
buildProdConfig.sh
#!/usr/bin/env bash
cp config/app.config.prod.js config/app.config.js
echo "Copied ProdConfig to Config"
//This is what a config file looks like
const Config = {
suppDataDirectoryPath: '/suppData/',
builtFor: 'prod',
}
module.exports = Config;
I then require Config whereever I need in my application and use the values. This is a simple thing for now, but it works.
if (process.env.DEV === "PROD") {
win.loadURL('http://app.url/webchat');
} else {
win.loadURL('http://test.app.url/webchat');
}
then when launching your app just do
DEV="PROD" node app.js or whatever

heroku -- npm postinstall script to run grunt task depending on enviro

I've got two heroku node.js apps, one for prod and one for dev, and I also have a Gruntfile with dev- and prod-specific tasks. I know you can set up package.json to run grunt as a postinstall hook for npm, but can you specify somehow different tasks to be run depending on what enviro you're in?
Here's what the relevant section of my package.json looks like so far:
"scripts": {
"postinstall": "./node_modules/grunt/bin/grunt default"
},
Rather than run grunt default every time, I'd love to run "grunt production" if NODE_ENV is production, etc.
Is this possible?
Sadly there's no difference like postInstall and postInstallDev. You can make an intermediate script to handle the difference though. For example, if you have the following:
"scripts": { "postinstall": "node postInstall.js" },
Then in this script you could check the environment variable and execute the correct Grunt task from there:
// postInstall.js
var env = process.env.NODE_ENV;
if (env === 'development') {
// Spawn a process or require the Gruntfile directly for the default task.
return;
}
if (env === 'production') {
// Spawn a process or require the Gruntfile directly to the prod task.
return;
}
console.error('No task for environment:', env);
process.exit(1);
A couple of peripherally related points...
Try not to have Grunt and co. as dependencies. Keep them to devDependencies to avoid having to install all that stuff in production. Having an intermediary script in vanilla Node like the above will allow you to do this. I like to use a postInstall script like this to install git hook scripts too (but also only on development environments).
You don't have to use ./node_modules/grunt/bin/grunt default. If grunt-cli is a dependency or devDependency, npm knows where to look and grunt default will work fine.
For some reason, my dev environment was never running my "development" if statement. I sent a ticket to Heroku support, and this was their answer: "By default, your environment is not available during slug compilation. If you would like to make this available, you can enable an experimental feature called "user-env-compile". Please see the following article for details:
http://devcenter.heroku.com/articles/labs-user-env-compile". Good to know. So, I went another route using the heroku-buildpack-nodejs-grunt buildpack, and then creating a heroku:development grunt task.

Resources