前端工程化实践 - React项目配置预发环境和生产环境(三)

4,610 阅读9分钟

这是我参与更文挑战的第8天,活动详情查看:更文挑战

一、前言

完成项目初始化后,首先我们配置下项目打包的不同环境。

场景一:前端项目开发完毕后,要上线,往往测试环境接口和生产环境接口域名是不一样的,如果每次部署都手动修改需要调用的接口域名,那么很容易出错,也不利于工程化的实践,这里整理下在项目开发过程中,如果来配置不同的打包环境。

场景二:有时候项目对应的不是一个后端服务,如果你的前端项目需要接入多个域名的接口,应该怎么样配置,才能代码条理清晰,又不至于多个域名相互冲突。

由于一般配置环境变量最重要的原因就是,通过不同的环境,调用后端不同域名下的接口,如果没有配置就会导致项目代码混乱,不利于后期维护。这里我们来展示下如何通过配置来区分不同的环境构建。

准备工作:

  • 几个用来测试的前端项目,都是通过CRA初始化的
  • 本地安装Docker,通过Docker来测试打包后的文件访问的是哪个配置

二、原理说明

不管需要多少个环境,目的都是让预发环境读取预发环境的配置,让生产环境读取生产环境的配置,那么可以采用各种方式,只要能实现了这个目的就可以了。

项目中常见的操作有:

  1. 每次部署,手动修改打包配置(耗时,易错)
  2. 执行构建命令,传入对应的参数,存储在某个变量,需要时读取。
  3. 通过CI配置关联项目分支,构建时自动构建对应的环境。
  4. 其他

方法一,部署临时项目或小项目时会使用,修改成本比较低,稍大点的项目就不适用了,因为浪费时间,容易出错。

方法二,是我们最常使用的方式,执行构建命令,比如npm run build:pre,npm run build:prod就可以构建不同的环境,配置简单,实现的方式也多,同时可以读取当前分支,避免打错分支。

方法三,通过CI触发项目配置,不需要手动构建,但涉及知识较多,配置最复杂,超大项目的必选方案,一般项目可以使用,但没必要。

知道了原理和目的,那么其他实现方式也就不难了:

  • 修改环境变量,通过环境变量区分不同的配置文件
  • 通过shell,在项目下新建多个配置文件.env .env_pre .env_production .env_local,在打包production环境时,先备份.env,通过cp命令复制.env_production文件为.env,打包完成后再还原.env文件,从而实现不同环境的配置
  • 通过node、fs文件模块,强行写入环境变量来实现
  • 甚至可以先构建项目完成,然后在项目外引入配置文件

下面我们选择几种常用的方式,具体配置和演示下,如何构建不同的打包环境。

下面使用的项目是上一篇文章,我们通过CRA构建的最基础的项目模版。具体项目demo,我们使用不同分支来演示。

二、cross-env 修改变量(推荐)

这个环节的演示使用environment/cross-env分支,也会是项目的主要打包方式。

1、cross-env 说明

cross-env是一款可以跨平台设置,修改项目环境变量的脚本文件

cross-env的版本6仅支持Node.js 8和更高版本

2、安装修改环境变量的插件

yarn add cross-env -D

3、修改package.json启动脚本

"scripts": {
    "start": "cross-env REACT_APP_ENV=development react-app-rewired start",
    "build:pre": "cross-env REACT_APP_ENV=development react-app-rewired build",
    "build:prod": "cross-env REACT_APP_ENV=production react-app-rewired build",
    "test": "react-app-rewired test",
    "eject": "react-scripts eject"
  }

这里可以使用REACT_APP_开头的任意变量

4、执行不同的命令,启动不同的环境

  • yarn start启动的时候会设置REACR_APP_ENV为development,作为本地开发使用
  • yarn build:pre打包预发环境代码,设置REACR_APP_ENV为development,和本地执行的环境相同,当然你可以自行配置
  • yarn build:prod打包生产环境代码,设置REACR_APP_ENV为production 一般项目配置这些环境变量就可以了,你也可以增加其他的环境。

5、在项目中读取变量

console.log(process.env.REACT_APP_ENV)

在项目中直接使用就行了,接着我们配置下不同环境下的接口请求。

6、配置接口不同的访问地址

在src目录下新建config文件夹,新建文件

const version = require("../../package.json") //获取当前版本

export const development = { // 配置预发和本地环境接口
    env: "development",
    version,
    links: {
        upm: "/api/test",
        case: "/api/forward"
    },
    apis: {
        api: "/api/test",
        case: "/user"
    }
}

export const production = { // 配置生产环境接口地址
    env: "production",
    version,
    links: {
        upm: "/production/api/test",
        case: "/production/api/forward"
    },
    apis: {
        api: "/production/api/test",
        case: "/production/user"
    }
}

看文件配置应该就知道是干啥的,项目中一般会有一些不同环境下使用的变量,比如

  • 版本号
  • 页面内的跳转链接
  • 最重要的接口请求域名

这里在这个文件下统一引入,方便后续扩展更多的配置

接着是在相同路径下,新建另一个文件index.tsx

import * as config from "./environment"
class Config {
    static DEV:string = "development";
    static PRODUCTION:string = "production";
    static allKeys:string[] = ["development", "pre", "production"];
    static currentDev: string = process.env.REACT_APP_ENV || Config.PRODUCTION
    
    static isDev(): boolean {
        return Config.currentDev === Config.DEV;
    }

    static isProduction(): boolean {
        return Config.currentDev === Config.PRODUCTION;
    }

    static getUrlLinks(key:string):string{
        return config[Config.currentDev].links[key];
    }

    static getApiUrl(key: string):string {
        return config[Config.currentDev].Apis[key];
    }

    static getVersion():string {
        return config[Config.currentDev].version;
    }
}
export default Config

7、通过该文件获取当前环境变量,封装函数获取不同的配置信息

(1)在src/index.tsx中,

import Config from 'src/config'

console.log("获取环境", Config.env)

(2)然后执行yarn build:pre,可以看到build部署文件成功。

(3)启动本地docker服务(docker命令说明在结尾),然后运行

docker run -d -v /Users/user/Desktop/mine/fronted-demo/build:/usr/share/nginx/html -p 8000:80 --name nginx-fronted1 nginx

image.png

(4)在浏览器访问http://localhost:8000

image.png

(5)执行 yarn build:prod,重启docker容器,访问http://localhost:8000,会发现此时打印的是

image.png

我们可以看到,传入不同的变量就可以请求不同的地址。

同样的道理,修改build:pre 和 build:prod对应的环境,就可以保证生产环境和开发环境分离。

详细代码见environment/cross-env分支

三、使用项目下的.env配置文件

方法一:只用项目自带的环境配置

1、CRA本身已经配置了不同的环境,我们只需要在项目下配置

.env .env.production 文件就可以了,当执行yarn start,项目会读取.env配置,当执行yarn build项目将会读取.env.production 文件,所以如果你的项目并不复杂,完全可以省去这一步的配置,直接使用项目自带的配置

.env

REACT_APP_ENV = environment

.env.production

REACT_APP_ENV = production

2、还原package.json

image.png

3、根目录添加文件config-overrides.js,具体可自行配置

module.exports = function override(config, env) {
    //do stuff with the webpack config...
    return config;
}

4、打印内容

image.png

此时执行yarn build, 部署docker,打开浏览器localhost:8000

image.png

详细代码见environment/env分支

方法二:结合dotenv-cli修改打包的环境

该方法使用environment/env-dotenv-cli分支下演示

1、安装dotenv-cli

yarn add dotenv-cli -D

dotenv-cli是个很简单的脚本,可以帮助项目读取不同的.env文件。

2、在根目录下新建.env .env.pre .env.production文件

.env

REACT_APP_ENV = development
REACT_APP_API_REST = https://localhost:9000
REACT_APP_API_USER = https://localhost:9001

.env.pre

REACT_APP_ENV = pre
REACT_APP_API_REST = https://www-pre.baidu.com
REACT_APP_API_USER = https://www-pre.nihao.com

.env.production

REACT_APP_ENV = production
REACT_APP_API_REST = https://www.baidu.com
REACT_APP_API_USER = https://www.nihao.com

3、修改package.json

"scripts": {
    "start": "react-app-rewired start",
    "build:pre": "dotenv -e .env.pre react-app-rewired build",
    "build:prod": "dotenv -e .env.production react-app-rewired build"
}

build:pre指令中加入,dotenv -e .env.pre

这里可以看到执行yarn build:pre 读取的是.env.pre的配置内容,这样就可以项目就可以读取不同的配置了,可以看到这个方法,是将配置文件放在了env中,上面一种方法是将文件放在js文件中,区别不大。

image.png 演示代码,分支environment/env-dotenv-cli

四、直接使用node包将变量写入

该分支使用environment/env-create-fs分支演示

通过插件我们写入变量,意思就是只要我们有一个配置,标识当前环境就可以了,所以我们可以在项目根目录下新建.env文件,并声明一个变量

REACT_APP_BUILD_ENV = development

此时读取该变量会是development,只需要在打包之前修改这个变量就行了。

具体操作在根目录新建config-env.js文件,这里需要注意的一点是commander的高版本和低版本语法有变化,需要注意看看文档

const fs = require('fs');
const program = require('commander');

program.option('-e, --env <type>', 'set build env', 'pre');
program.parse(process.argv);
const params = program.opts()

const $1 = params.env;

if (['development', 'pre', 'production', 'proxy'].includes($1)) {
    try {
       fs.writeFileSync('.env', `REACT_APP_BUILD_ENV = '${$1}'`, 'utf-8');
    } catch (error) {
       console.error(error);
       return;
    }
} else {
    console.error('参数不符合要求');
    return;
}

修改package.json文件

"scripts": {
    "start": "node ./config-env.js && react-app-rewired start",
    "build:prod": "node ./config-env.js -e production && react-app-rewired build",
    "build:pre": "node ./config-env.js -e pre && react-app-rewired build"
}

这里可以看到执行打包命令,然后运行config-env.js文件,该文件引用node的fs模块和commander包,读取传入的环境变量,然后通过fs的方法修改文件的环境变量,直接运行

由于项目默认读取的是.env文件,所以我们只需要执行node 。/config-env.js -e production文件看到 .env 中环境变量修改成功就可以了。

具体配置的示例代码,见分支environment/env-create-fs

五、需要注意的点

1、ts通过变量获取对应的值,报错,解决办法?

static getUrlLinks(key:string):string{
    return config[Config.currentDev].links[key];
}

这里ts语法会报错 image.png

解决办法这里暂时直接屏蔽掉,在tsconfig.json添加配置

{
  "compilerOptions": {
      "suppressImplicitAnyIndexErrors": true
  }
}

2、.env* 文件需要以REACR_APP_开头,才能在项目中读取

3、启动docker的镜像nginx

docker run -d -v /Users/user/Desktop/mine/fronted-demo2/build:/usr/share/nginx/html -p 8000:80 --name nginx-fronted2 nginx
  • 其中-d 后面/Users/user/Desktop/mine/fronted-demo/build:/usr/share/nginx/html以冒号分割,前面是我本地项目的地址
  • build是项目打包后文件所在的文件夹,后面是nginx容器访问的路径,通过挂载运行项目
  • -p 8000:80 表示,本地通过8000端口访问,容器使用的80端口
  • --name nginx-fronted 为启动的容器命名
  • nginx是启动容器的镜像名称,本地没有会从远程docker仓库拉取。

4、其他可以使用的插件 www.npmjs.com/package/env…

六、github项目示例