乐趣区

关于javascript:react前端读取环境变量的骚操作

背景

在经典的单页面利用中,以 react 为例,利用会辨别开发环境和生产环境。开发环境是用 webpack-dev-server 编译并启动的前端我的项目,实质上启动的是一个 node 服务。生产环境也是须要 node 编译为纯动态页面,期待后续部署。

因为编译都用到了 node,所以就能够用 node 提供的 process.env 拜访零碎环境变量。同时 react 还提供了 process.env.NODE_ENV 来辨别开发和生产环境

process.env.NODE_ENV == 'development' ## 开发环境
process.env.NODE_ENV == 'production'  ## 生产环境

应用 NODE_ENV 最多的中央是 webpack 配置文件,用来判断以后环境是开发还是生产,而后应用不同的编译配置打包我的项目。

下面说的开发环境和生产环境只是用来辨别 run startrun build。当我的项目 build 后生成动态文件筹备部署时,咱们还要辨别是部署 staging 环境还是部署 release 环境。因为 staging 是正式部署生产前的测试阶段,所以这两个环境最大的区别就是接口的 baseURL 不同。

此时 process.env.NODE_ENV 的值都是 production,所以咱们须要 自定义一个新的环境变量来辨别 staging 和 release。而后别离应用不同的 baseURL

假如新的环境变量是 REACT_APP_ENV,判断形式如下:

switch(process.env.REACT_APP_ENV) {
    case 'staging':
        // staging 环境
        baseURL = 'http://staging-url.com'
    case 'release':
        // release 环境
        baseURL = 'http://release-url.com'
}

那么,如何自定义 REACT_APP_ENV 呢?

编译时定义

下面说了,开发和生产环境打包文件时都是基于 node,所以在 process.env 上设置就能够。

举荐应用 cross-env 这个库,它的益处就是跨平台,window,Linux 或 Mac 都是雷同的设置形式。

怎么用呢?比方你的 package.json 中的 build 命令是这样:

"scripts": {"build": "node scripts/build.js}

那么就能够基于 build 别离新增打包命令:

"scripts": {"build": "node scripts/build.js,"staging":"cross-env REACT_APP_ENV=staging node scripts/build.js","release":"cross-env REACT_APP_ENV=release node scripts/build.js",}

当初 npm run stagingnpm run release 打包进去的文件惟一的区别就是 REACT_APP_ENV 的不同,这刚好满足了下面咱们辨别环境设置不同 baseURL 的需要。

然而要留神,也是前面我采坑的中央:这种环境变量的设置形式,只在 编译的时候无效。实质上是在编译时依据 REACT_APP_ENV 判断出 baseURL 的值,而后写死在文件里。

也就是说,编译后,baseURL 的值曾经是最终值,不会再执行 REACT_APP_ENV 的判断逻辑。

那么,有没有方法只 build 一次,打包一份文件,而后代码里间接读取零碎环境变量,来判断应用哪个 baseURL 呢?

有,这是本文重点。

间接读取

首先说说为什么会有这个需要。我司要做前端我的项目容器化,打包成 docker 镜像部署。冀望的是只打包一份镜像,而后 docker run 的时候传一个环境变量,而后前端我的项目里读这个环境变量,判断出 baseURL 是什么。

开始我的思路就是 process.env,起初发现不对,因为前端 js 是不能间接读取零碎信息的。最初咱们选定了计划,应用 node。

前端不能读取环境变量,然而 node 能够。咱们的思路是用 node 读取到环境变量,而后用服务端渲染的形式传给前端,前端再将这个值保留在 window 全局对象里后续应用。

有了这个思路,咱们来实际:

首先,我的项目目录下建 release 文件夹,蕴含两个文件:

1. package.json

{
    "name": "release-server",
    "version": "1.0.0",
    "scripts": {"start": "node server.js"},
    "dependencies": {
        "express": "^4.13.3",
        "path": "^0.12.7",
        "ejs": "^2.3.4"
    }
}

服务端渲染用 ejs 传值,所以要装 ejs

2. server.js

const path = require('path');
const express = require('express');
// 读取零碎环境变量(docker 设置)const REACT_APP_ENV = process.env.REACT_APP_ENV;
const port = process.env.PORT || 8080;
const app = express();

app.use(express.static(__dirname + '/dist')); // dist 是打包后的目录
app.set('views', path.join(__dirname, '/dist/'));
app.set('view engine', 'ejs');

app.get('*', function response(req, res) {res.render('index', { REACT_APP_ENV}); // 传递 REACT_APP_ENV
});

app.listen(port);

理论部署的时候,用 node 启动 server.js,而后 node 会读取到 REACT_APP_ENV,并通过 ejs 传给前端。

这一步实现,下一步就是将 html 模版换成 ejs 模版,并承受变量。

在 public 目录下将 index.html 改成 index.ejs,而后在 head 中减少一个承受变量的脚本块

<script>
    var GLOBAL_ENV = {"REACT_APP_ENV": "<%= REACT_APP_ENV %>", // 接管 node 传来到 REACT_APP_ENV};
    Object.freeze(GLOBAL_ENV);
</script>

而后将 webpack 的解析模版改为 ejs

new HtmlWebpackPlugin({
    filename: "index.ejs",
    template: path.resolve('public/index.ejs'),
})

最初批改 webpack 的 outputrelease/dist

功败垂成!

配置实现后执行 run build,打包进去的文件是这样的

而后将这份代码交给 node 运行起来就好了~

退出移动版