Rendering an environment variable to the browser i

2020-07-09 09:41发布

The readme of the react redux realworld.io application at https://github.com/gothinkster/react-redux-realworld-example-app says to edit the src/agent.js to change the API_ROOT to point to a different backend api instance. We want to set things up so that API_ROOT can be defined by an environment variable that is different within the multiple environments (e.g., “staging” and “live”) where we run the production build.

We are running in containers on openshift kubernetes following 12factor.net principles where the code is built once then promoted through environments. We can spin up new environments with a single command so we don’t want to have a switch statement within the code that names each environment and hardcodes the backend API_ROOT for each environment. Instead, I want to be able to run an existing production build container image in a fresh environment using an environment variable change the API_ROOT to point to the correct backend API we want to test against.

I have looked at a number of different blogs, stackoverflow answers and the official documentation. The main problem is that typical solutions “bake in” the process.env.API_ROOT environment variable at build time else have a switch that hardcodes the details of all environments into the code. Neither of which are satisfactory as we want to able to take the latest stable code in an existing container and run it in a new environment using the API running there.

The closest I have got so far is to edit the code to render the process.env.API_ROOT into a <script> tag that sets it on a window.API_ROOT variable. Then check whether that exists else use a default when defining the const for API_ROOT. This feels very invasive and a bit fragile and it is not clear to me where is the best place to render such a script tag in the sample app at https://github.com/gothinkster/react-redux-realworld-example-app

3条回答
ゆ 、 Hurt°
2楼-- · 2020-07-09 10:30

Take a look at Immutable Web Apps!

It is a methodology that creates a separation of concern between index.html and all other static assets:

  1. It treats index.html as a deployment manifest that contains all environment-specific values.

This is similar to the accepted answer, by including the environment variables directly in the index.html

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

It also requires that the reference to other static assets are unique and versioned.

  1. It treats javascript bundles as immutable assets that are built once, published once, and used in multiple environments. Allowing the assets to be promoted through environments to production without being modified or moved.

It honors both the "build, release, run" and "config" principles of 12factor.

A great benefit of this approach is that it enables atomic live releases by simply publishing index.html.

查看更多
混吃等死
3楼-- · 2020-07-09 10:33

Issue #578 of react-create-app has a good answer. tibdex suggested using a public/env.js that is generated with the correct properties then in the index.html add:

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

That env.js script can set the API_ROOT on the window:

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

And agent.js can check for the window.env.API_ROOT else default:

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

const API_ROOT = apiRoot();

Exactly how that file is created from an environment variable he doesn't describe but I was able to have the npm start command generate it.

Moorman then suggested simply writing an express server that serves that /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);

To get that to work the start script in the package.json is simply:

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

Then everything works. If API_ROOT is defined in environment variables then the server.js will generate it on window.env and the agent.js will use it.

update I set a cache time of five minutes on env.js with res.setHeader("Cache-Control", "public, max-age=300"); as the setting is rarely going to change.

update I read a lot of confusion around this topic and people answering it along the lines of ”change your workflow to align to the defaults of the tools”. The idea of 12-factor is to use a workflow that is established as best practice that the tools should follow, not vice-versa. Specifically a tagged production ready container should be configurable by environment variables and promoted through environments. Then it's "the same thing" that is debugged and tested that runs in live. In this case of a single page app it requires that the browser makes a trip to the server to load the environment variables rather than baking them into the app. IMHO this answer is a straightforward and simple way of doing that to be able to follow 12-factor best practices.

update: @mikesparr gives a good answer to this problem at https://github.com/facebook/create-react-app/issues/982#issuecomment-393601963 which is to restructure the package.json to do the webapp work of generating the SPA upon start up. We took this approach as a tactical workaround. We are using a saas openshift kubernetes that charges for memory. Building our react app with webpack needs 1.2Gb (and rising!) So this approach of moving the npm build to the container startup command we need to allocate 1.2Gb to every pod we start which is a significant amount of additional costs for a single page app whereas we can get away with 128MB as the memory allocation when the app is precompiled. The webpack step is also slow as it is a large app. Building every time we start up the app slows down rolling deployments by many minutes. If a VM crashes and kubernetes starts replacement containers on a new VM it takes minutes to start up. A precompiled app starts in a few seconds. So the solution of "webpack at startup" is not satisfactory in terms of resource consumption and speed for real business application that are tens of thousands of lines of code. IMHO this answer of fetching a configuration script from the server is superior.

查看更多
聊天终结者
4楼-- · 2020-07-09 10:33

You can replace the environment variables directly in your index.html file exposing a global ENV variable. That replacement needs to be done at runtime to make sure that you have a portable image that you can run in different environments.

I have created an example repository here https://github.com/axelhzf/create-react-app-docker-environment-variables

查看更多
登录 后发表回答