在 https://github.com/gothinkster/react-redux-realworld-example-app 的 react redux realworld.io 应用程序的自述文件说要编辑src/agent.js
以更改API_ROOT
以指向不同的后端 API 实例。我们希望进行设置,以便API_ROOT
可以由一个环境变量来定义,该环境变量在我们运行生产构建的多个环境(例如,"暂存"和"实时")中是不同的。
我们遵循 12factor.net 原则在 openshift kubernetes 上的容器中运行,其中代码构建一次,然后通过环境提升。我们可以使用单个命令启动新环境,因此我们不希望在代码中使用 switch 语句来命名每个环境并为每个环境硬编码后端API_ROOT
。相反,我希望能够使用环境变量在新环境中运行现有的生产构建容器映像,更改API_ROOT
以指向我们要测试的正确后端 API。
我看过许多不同的博客,stackoverflow答案和官方文档。主要问题是典型的解决方案在构建时"烘焙"process.env.API_ROOT
环境变量,否则有一个开关将所有环境的详细信息硬编码到代码中。两者都不能令人满意,因为我们希望能够在现有容器中获取最新的稳定代码,并使用在那里运行的 API 在新环境中运行它。
到目前为止,我最接近的是编辑代码以将process.env.API_ROOT
呈现为<script>
标签,该标签将其设置为window.API_ROOT
变量。然后检查是否存在,否则在为 API_ROOT 定义 const 时使用默认值。这感觉非常具有侵入性且有点脆弱,我不清楚在示例应用程序中呈现此类脚本标记的最佳位置在哪里 https://github.com/gothinkster/react-redux-realworld-example-app
react-create-app 的问题 #578 有一个很好的答案。Tibdex建议使用具有正确属性生成的public/env.js
,然后在index.html
中添加:
<script src="%PUBLIC_URL%/env.js"></script>
该env.js
脚本可以在窗口上设置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
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 上设置了五分钟的缓存时间.jsres.setHeader("Cache-Control", "public, max-age=300");
因为设置很少会改变。
更新我读到了很多关于这个话题的困惑,人们按照"更改您的工作流程以符合工具的默认值"来回答它。12 因素的想法是使用作为工具应遵循的最佳实践建立的工作流,反之亦然。具体来说,标记的生产就绪容器应可由环境变量配置并通过环境进行提升。然后是调试和测试的"相同的东西",在现场运行。在这种情况下,对于单页应用程序,它要求浏览器前往服务器以加载环境变量,而不是将它们烘焙到应用程序中。 恕我直言,这个答案是一种简单明了的方法,能够遵循 12 因素最佳实践。
更新:@mikesparr https://github.com/facebook/create-react-app/issues/982#issuecomment-393601963 给出了这个问题的一个很好的答案,即重组package.json以完成启动时生成SPA的Webapp工作。我们将这种方法作为一种战术解决方法。我们正在使用一个对内存收费的 saas openshift kubernetes。使用 webpack 构建我们的 react 应用程序需要 1.2Gb (而且还在上升!因此,这种将 npm build 移动到容器启动命令的方法,我们需要为我们启动的每个 pod 分配 1.2Gb,这对于单页应用程序来说是一笔可观的额外成本,而我们可以在应用程序预编译时获得 128MB 作为内存分配。webpack 步骤也很慢,因为它是一个大型应用程序。每次启动应用时生成都会将滚动部署速度减慢数分钟。如果 VM 崩溃并且 kubernetes 在新 VM 上启动替换容器,则需要几分钟才能启动。预编译的应用将在几秒钟内启动。因此,"启动时 webpack"的解决方案在资源消耗和速度方面对于数万行代码的实际业务应用程序并不令人满意。恕我直言,从服务器获取配置脚本的答案是优越的。
您可以直接替换 index.html 文件中的环境变量,以公开全局 ENV 变量。需要在运行时完成替换,以确保您拥有可在不同环境中运行的可移植映像。
我在这里创建了一个示例存储库 https://github.com/axelhzf/create-react-app-docker-environment-variables
看看不可变的 Web 应用程序!
这是一种在index.html
和所有其他静态资产之间创建关注点分离的方法:
- 它将
index.html
视为包含所有特定于环境的值的部署清单。
这类似于接受的答案,通过将环境变量直接包含在index.html
window.env={'API_ROOT':'https://conduit.productionready.io/api'}
它还要求对其他静态资产的引用是唯一的并且是版本化的。
- 它将 JavaScript 捆绑包视为不可变的资产,这些资产构建一次,发布一次,并在多个环境中使用。允许资产通过环境提升到生产环境,而无需修改或移动。
它尊重 12factor 的"构建、发布、运行"和"配置"原则。
这种方法的一大好处是,它通过简单地发布index.html
来实现原子实时发布。
这个问题发布已经有一段时间了,但我似乎有一个很好的简单解决方法.
创建一个访问 window.location.hostname 的 js 文件;
function getEnvConfig(key){
let host = '';
if (typeof window !== 'undefined') {
host = window.location.hostname;
}
console.log(`Hostname: ${host}`);
if((host.includes('localhost') || host.includes('dev'))) {
return envConfigs.dev[key];
} else if(host.includes('stage')) {
return envConfigs.stage[key];
} else if(host.includes('prod')) {
return envConfigs.prod[key];
}
}
然后在单独的JS文件中为每个环境进行配置。
const envConfigs = {
dev: {
NEXT_PUBLIC_API_URL: "https://test.com",
},
stage: {
},
prod: {
},
};
module.exports = {
envConfigs
};
这行得通!它应该在每个环境中都有效。