在不同环境中的 react.js redux 生产构建 运行 中将环境变量呈现给浏览器

Rendering an environment variable to the browser in a react.js redux production build running in different environments

https://github.com/gothinkster/react-redux-realworld-example-app 处的 react redux realworld.io 应用程序的自述文件说要编辑 src/agent.js 以更改 API_ROOT 以指向不同的后端 api 实例。我们希望进行设置,以便 API_ROOT 可以由在我们 运行 生产构建的多个环境(例如,“staging”和“live”)中不同的环境变量定义。

我们 运行 在 openshift kubernetes 上的容器中遵循 12factor.net 代码构建一次然后通过环境提升的原则。我们可以使用单个命令启动新环境,因此我们不想在代码中使用 switch 语句来命名每个环境并为每个环境硬编码后端 API_ROOT。相反,我希望能够 运行 使用环境变量在新环境中 运行 现有生产构建容器映像更改 API_ROOT 以指向我们要测试的正确后端 API .

我查看了许多不同的博客、Whosebug 答案和官方文档。主要问题是典型的解决方案在构建时“嵌入”process.env.API_ROOT 环境变量,否则有一个开关将所有环境的细节硬编码到代码中。这两者都不令人满意,因为我们希望能够在现有容器中获取最新的稳定代码,并在新环境中使用 API 运行 将其 运行 放置在那里。

到目前为止,我最接近的是编辑代码以将 process.env.API_ROOT 呈现为 <script> 标记,并将其设置在 window.API_ROOT 变量上。然后检查是否存在,否则在为 API_ROOT 定义常量时使用默认值。这感觉非常具有侵入性并且有点脆弱,我不清楚在 https://github.com/gothinkster/react-redux-realworld-example-app

的示例应用程序中呈现此类脚本标记的最佳位置在哪里
react-create-app 的

Issue #578 有一个很好的答案。 tibdex 建议使用使用正确属性生成的 public/env.js,然后在 index.html 中添加:

 <script src="%PUBLIC_URL%/env.js"></script>

那个env.js脚本可以在window上设置API_ROOT:

window.env={'API_ROOT':'https://conduit.productionready.io/api'}

agent.js可以检查window.env.API_ROOT否则默认:

function apiRoot() {
  if( window.env.API_ROOT !== 'undefined') {
    return window.env.API_ROOT
  }
  else {
    return 'https://conduit.productionready.io/api'
  }
}

const API_ROOT = apiRoot();

确切地说,该文件是如何从他没有描述的环境变量中创建的,但我能够让 npm start 命令生成它。

Moorman 然后建议简单地编写一个服务于 /env.js else index.html:

的快速服务器
const express = require('express');
const path = require('path');

const app = express();

app.use(express.static(path.join(__dirname, 'build')));

const WINDOW_ENV = "window.env={'API_ROOT':'"+process.env.API_ROOT+"'}\n";

app.get('/env.js', function (req, res) {
  res.set('Content-Type', 'application/javascript');
  res.send(WINDOW_ENV);
});

app.get('/*', function (req, res) {
  res.sendFile(path.join(__dirname, 'build', 'index.html'));
});

app.listen(process.env.PORT);

要使其正常工作,package.json 中的启动脚本很简单:

"start": "PORT=8080 node server.js",

然后一切正常。如果在环境变量中定义了 API_ROOT,那么 server.js 将在 window.env 上生成它,而 agent.js 将使用它。

更新 我在 env.js 和 res.setHeader("Cache-Control", "public, max-age=300"); 上设置了五分钟的缓存时间,因为设置很少改变。

更新 我读到很多关于这个主题的困惑,人们按照“改变你的工作流程以与工具的默认值保持一致”的方式回答它。 12-factor 的想法是使用建立为工具应遵循的最佳实践的工作流程,而不是相反。具体来说,带标签的生产就绪容器应该可以通过环境变量进行配置,并通过环境进行提升。然后是"the same thing"调试测试运行在live中。在这种单页应用程序的情况下,它要求浏览器访问服务器以加载环境变量,而不是将它们烘焙到应用程序中。恕我直言,这个答案是一种直接而简单的方法,能够遵循 12 因素最佳实践。

update:@mikesparr 在 https://github.com/facebook/create-react-app/issues/982#issuecomment-393601963 给出了这个问题的一个很好的答案,它是重组 package.json 来完成生成的 webapp 工作SPA 启动时。我们将这种方法作为一种战术解决方法。我们正在使用对内存收费的 saas openshift kubernetes。使用 webpack 构建我们的 React 应用程序需要 1.2Gb(并且还在上升!)所以这种将 npm build 移动到容器启动命令的方法我们需要为我们启动的每个 pod 分配 1.2Gb,这对于单个页面来说是大量的额外成本应用程序,而我们可以在预编译应用程序时使用 128MB 作为内存分配。 webpack 步骤也很慢,因为它是一个大型应用程序。每次我们启动应用程序时构建都会使滚动部署减慢很多分钟。如果 VM 崩溃并且 kubernetes 在新 VM 上启动替换容器,则需要几分钟才能启动。预编译的应用程序会在几秒钟内启动。所以"webpack at startup"的方案对于真正的几万行代码的业务应用来说,在资源消耗和速度上都不能令人满意。恕我直言,这个从服务器获取配置脚本的答案是优越的。

看看Immutable Web Apps

这是一种在 index.html 和所有其他静态资产之间创建关注点分离的方法:

  1. 它将 index.html 视为包含所有环境特定值的部署清单。

这类似于 ,直接在 index.html

中包含环境变量
window.env={'API_ROOT':'https://conduit.productionready.io/api'}

它还要求对其他静态资产的引用是唯一的和版本化的。

  1. 它将 javascript 包视为不可变资产,一次构建,一次发布,并在多个环境中使用。允许资产在不被修改或移动的情况下通过环境提升到生产环境。

它遵循 12factor 的 "build, release, run" 和 "config" 原则。

这种方法的一大好处是它可以通过简单地发布 index.html.

来实现原子实时发布

您可以直接在 index.html 文件中替换环境变量,从而公开全局 ENV 变量。该替换需要在 运行 时间完成,以确保您拥有可以在不同环境中 运行 的便携式图像。

我在这里创建了一个示例存储库https://github.com/axelhzf/create-react-app-docker-environment-variables