In actual project development, front-end ER often faces the interfaces of multiple environments: development environment, test environment, and production environment, so the baseUrl of network requests in the project also needs to change with these environments.

However, scaffolding such as create-React-app or UMI is used for project initialization. These scaffolders black-box webPack configuration. How do you elegantly configure multiple project environments without eject?

It’s best not to just do eject every time you have a problem in a project. Eject is irreversible, and it exposes all the details and makes the project catalog huge.

Create-react-app Configures multiple environment interfaces

Create-react-app supports multiple environment configuration files by default:

  • .env: the default.
  • .env.local: local coverage.All environments except test load this file.
  • .env.development..env.test..env.production: Sets a specific environment.
  • .env.development.local..env.test.local..env.production.local: Sets local override for a specific environment.

Files on the left have higher 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(Notice no.env.local )

For example, there are only two interfaces, development environment and test environment, in the current development process of our department (local development and test share the same environment).

So, I need to specify the interface address to use for packaging in the test environment as the interface address in env.development. I wrote two configuration files. Env.development and env.production. When executing the build command, the default is to load variables in the.env.production file. So when I run the NPM run build command on the test server, the interface address is specified as the interface address of the production environment, which is obviously not what I want.

What to do?

The official documentation also tells us that we can use Dotenv to manage environment variables (dotenv loads environment variables from the.env file into process.env).

Because we want to use it on the command line, we need to use dotenv- CLI.

Without further ado, let’s begin

Write the configuration files for each environment

First, we can write configuration files for each environment.

# .env.development
REACT_APP_BASE_URL='http://development.xxx.xxx' 
Copy the code
# env.production
REACT_APP_BASE_URL='http://production.xxx.xxx' 
Copy the code

Modify thepackage.jsonIn thescriptsTo specify the environment

Start by installing Dotenv-CLI as a development dependency for the project:

yarn add --dev dotenv-cli
# or
npm i -D dotenv-cli
Copy the code

Then modify scripts in package.json to specify the environment:

 "scripts": {
    "start": "react-app-rewired start"."build:dev": "dotenv -e .env.development react-app-rewired build"."build": "react-app-rewired build"."test": "react-app-rewired test"."eject": "react-scripts eject"
  }
Copy the code

This way, when I need to package front-end code on the test server, I can execute NPM run build:dev to specify the use of environment variables in.env.development ~

Umi Configure multiple environment interfaces

With this experience in mind, multi-environment configuration is little more than separating the configuration files for each environment and using additional means to specify the configuration files for each environment at package time.

Write the configuration files for each environment

Check UMI documentation and you can see that environment variables are placed in the define configuration under config/config.js.

If you’re developing in TypeScript, the configuration file is config/config.ts.

Ts and config/config.dev.ts

Modify scripts in package.json to specify the environment

By looking at package.json in the template project generated by UMI, you can find that umi uses cross-env by default to specify configuration files for UMI packaging. So we’ll rewrite scripts in package.json as follows:

"scripts": {
  "start": "react-app-rewired start"."build-dev": "cross-env UMI_ENV=dev umi dev"."build-test": "cross-env UMI_ENV=test umi build"."build-prod": "cross-env UMI_ENV=prod umi build",}Copy the code

Tips: Store configuration and code separately

Because the degree of variation in the configuration files can be significant between deployed versions of each environment, the code is essentially the same.

Of course, the configuration files mentioned above do not cover internal application configuration (for example, you might write routing as a configuration file).

A simple way to determine if an application is correctly excluding configuration from its code is to see if the application’s benchmark code is immediately open source without fear of exposing any sensitive information.

— The Twelve-Factor App

Cautionary tale

A good example of how not to do this is to write another file in your code like getBaseurl.js to do the environmental judgment:

// getBaseUrl.js
const TEST_DOMAIN = process.env.REACT_APP_BASE_URL
const PRODUCTION_DOMAIN = process.env.REACT_APP_PRODUCTION_BASE_URL
let domain = TEST_DOMAIN
switch (process.env.NODE_ENV) {
  case 'development':
    domain = TEST_DOMAIN
    break
  case 'production':
    domain = PRODUCTION_DOMAIN
    break
  default:
    domain = TEST_DOMAIN
    break
}
export default domain
Copy the code

The above variables are variables written in the.env file:

# .env
REACT_APP_DEVELOPMENT_BASE_URL='http://xxxxxx' The development/test environment interface address
REACT_APP_PRODUCTION_BASE_URL='http://xxxxxx'  The interface address of the production environment
Copy the code

In fact, I did this myself when I first started writing projects with React 😅. This is equivalent to writing the configuration in the code, which is not only bad for maintainability, but also bad for readability when others read your code.

Refer to the file

  • The umi issue
  • Create-react-app Official document
  • 12factor