在实际项目开发中,前端 er 常常会面对多个环境的接口:开发环境、测试环境、生产环境,所以项目中网络请求的 baseUrl
也需要跟随这些环境来变化。
但是,我们一般会使用像 create-react-app
或者 umi
这样的脚手架来做项目的初始化,这些脚手架将 webpack
的配置黑盒化了,如何在不执行 eject
操作的前提下优雅地配置多个项目环境呢?
在项目中最好不要一遇到问题就一键执行
eject
操作,eject
操作是不可逆的,执行之后会把所有细节都暴露在我们面前,让项目目录变得很庞大。
create-react-app 配置多环境接口
其实查看 create-react-app 的官方文档可以发现,create-react-app
默认是支持多个环境配置文件的:
.env
:默认。.env.local
:本地覆盖。除 test 之外的所有环境都加载此文件。.env.development
,.env.test
,.env.production
:设置特定环境。.env.development.local
,.env.test.local
,.env.production.local
:设置特定环境的本地覆盖。
左侧的文件比右侧的文件具有更高的优先级:
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
(注意没有.env.local
)
例如我们部门目前开发流程中只有开发环境和测试环境两种接口(其中,本地开发和测试共用一个环境),
所以,我需要将测试环境下打包时使用的接口地址指定为 env.development
中的接口地址,我分别写了两份配置文件 .env.development
以及 env.production
,但是根据以上create-react-app
的官方文档,在执行 build 命令时,默认是加载 .env.production
文件中的变量,所以我在测试服务器上执行 npm run build
命令时就会使得接口地址被指定为生产环境的接口地址,这显然不是我想要的。
怎么办呢?
官方文档也给了我们答案——可以使用 dotenv 来做环境变量的管理(dotenv
可将环境变量从 .env 文件加载到 process.env
中。)
因为我们要在命令行中使用,所以我们需要使用 dotenv-cli。
话不多说,让我们开始吧~
写好各个环境的配置文件
首先,我们可以写好每个环境下的配置文件。
# .env.development
REACT_APP_BASE_URL='http://development.xxx.xxx'
# env.production
REACT_APP_BASE_URL='http://production.xxx.xxx'
修改 package.json
中的 scripts
来指定环境
"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"
}
这样,当我需要在测试服务器上打包前端代码时,我就可以执行npm run build:dev
来指定使用 .env.development
中的环境变量了~
umi
配置多环境接口
有了以上的经验我们就可以知道,其实多环境配置,不外乎就是将各个环境的配置文件分开,并使用额外的手段来在打包时指定对应环境的配置文件。
写好各个环境的配置文件
查看 UMI 文档 可知,环境变量被放在 config/config.js
下的 define 这个配置中,
如果你使用 TypeScript 开发,那么配置文件是 config/config.ts。
所以同样的,我们可以将原来的 config/config.ts
做个分身,写两份配置文件,分别是 config/config.dev.ts
和 config/config.prod.ts
修改 package.json 中的 scripts 来指定环境
查看 umi
生成的模版项目中的package.json
可以发现: umi
默认是使用 cross-env来为 umi
打包指定配置文件。所以我们将package.json
中的 scripts
改写如下:
"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",
},
Tips:将配置和代码分开存储
将配置和代码分开存储
因为各个环境的部署版本之间,配置文件的差异程度可能很大,但是代码基本是不变的。
当然,上面所说的配置文件不包括内部应用程序的配置(例如,你可能将路由写成了配置文件)。
判断一个应用是否正确地将配置排除在代码之外,一个简单的方法是看该应用的基准代码是否可以立刻开源,而不用担心会暴露任何敏感的信息。——《The Twelve-Factor App》
反面教材
一个比较典型的反面教材就是在代码中再写一份类似 getBaseUrl.js
这样的文件来做环境判断:
// 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
上面的变量是 .env
文件里面写好的变量:
# .env
REACT_APP_DEVELOPMENT_BASE_URL='http://xxxxxx' # 开发环境/测试环境的接口地址
REACT_APP_PRODUCTION_BASE_URL='http://xxxxxx' # 生产环境的接口地址
其实我自己在刚开始用 React 写项目的时候就是这么干的😅,这样相当于将配置写在了代码里面,不仅可维护性比较差,而且别人看你代码的时候,可读性也比较差。
配置文件区分环境,相互独立
其实这种方式是 dotenv 所不推荐的,它的观点如下:
dotenv 推荐直接在配置文件直接存储在不同的部署环境中,不做版本控制,所以你需要在各个环境配置好环境变量,它们都放在 .env 文件中。但是这个对于我们小作坊式开发来说,反而比较麻烦,所以我目前都是按照分配置文件的方式来实践的,如果正在阅读本篇文章的你有更好的方式,欢迎评论~