原文:Using React in Multiple Environments
你的 React 应用可以在本地运行,但是如何让它能在不同的环境中也顺利运行呢?
包括生产环境,测试环境,预览环境等等,所有这些环境都有自己的服务器、配置文件,甚至在不同的环境应用会打开或者关闭特定的功能。
那么,如何实现上述需求呢?下面我们介绍几种方法
动态配置
如果不同环境是通过不同的域名访问的,那么这种策略很合适
在 api-config.js 中加入下列代码:
let backendHost;
const apiVersion = 'v1';
const hostname = window && window.location && window.location.hostname;
if(hostname === 'realsite.com') {
backendHost = 'https://api.realsite.com';
} else if(hostname === 'staging.realsite.com') {
backendHost = 'https://staging.api.realsite.com';
} else if(/^qa/.test(hostname)) {
backendHost = `https://api.${hostname}`;
} else {
backendHost = process.env.REACT_APP_BACKEND_HOST || 'http://localhost:8080';
}
export const API_ROOT = `${backendHost}/api/${apiVersion}`;
然后在 API 文件中(比如,api.js),你可以导入 API URL
import { API_ROOT } from './api-config';
function getUsers() {
return fetch(`${API_ROOT}/users`)
.then(res => res.json)
.then(json => json.data.users);
}
编译时配置
如果你想在编译时进行配置,也是可以的:
如果你使用 Create React App,你将会获得一个全局变量 process.env
,通过这个变量应用可以访问环境变量,同时应用构建后,process.env.NODE_ENV
会被设置为 “production"
另外,Create React App 工具只会访问以 “REACT_APP_” 开头的环境变量,所以,同样是环境变量 REACT_APP_SKITTLE_FLAVOR 可以被访问到,而 SKITTLE_FLAVOR 就不行
下面我们来看一下如何在构建时添加环境变量:
-
Linux 系统
$ REACT_APP_API_HOST=example.com yarn run build
在应用中就可以访问到如下变量
- process.env.REACT_APP_API_HOST === "example.com"
- process.env.NODE_ENV === "production"
-
Windows 系统
:\> set REACT_APP_API_HOST=example.com :\> yarn run build
在构建时配置功能开关
环境变量可以被设置为任意值,一个潜在的用途是在不通的环境中打开或者关闭特定的功能。在构建时你可以进行这样的操作:
$ REACT_APP_SPECIAL_FEATURE=true yarn run build
你将获得如下变量:
# process.env.REACT_APP_SPECIAL_FEATURE === "true"
# process.env.NODE_ENV === "production"
然后你可以在代码的任意位置检查这些变量:
function HomePage() {
if(process.env.REACT_APP_SPECIAL_FEATURE === "true") {
return <SpecialHomePage/>;
} else {
return <PlainOldBoringHomePage/>;
}
}
.env Files
Create React App 支持通 .env
文件将环境变量持久保存。
你可以创建一个 .env 文件,然后将环境变量添加到里面:
REACT_APP_SPECIAL_FEATURE=true
REACT_APP_API_HOST=default-host.com
这些变量在不通的环境中都会加载,如果你想定义一些某个环境特定的变量,则可以创建名为.env.development
, .env.test
, 或者 .env.production
的 env 文件。
.env 文件的类型
- .env: 默认.
- .env.local: 本地覆盖(overrides)。这个文件将会在除了测试环境之外的所有环境加载。
- .env.development, .env.test, .env.production: 环境特定设置。
- .env.development.local, .env.test.local, .env.production.local: 针对不通环境的覆盖配置。
.env 文件的优先级
左边的优先级高于右边
- npm start: .env.development.local, .env.development, .env.local, .env
- npm run build: .env.production.local, .env.production, .env.local, .env
- npm test: .env.test.local, .env.test, .env (note .env.local is missing)
Variables are Baked In
At the risk of stating the obvious: the “environment variables” will be baked in at build time. Once you build a JS bundle, its process.env.NODE_ENV and all the other variables will remain the same, no matter where the file resides and no matter what server serves it. After all, a React app does not run until it hits a browser. And browsers don’t know about environment variables.
Unit Testing
Once you’ve got all these variables multiplying your code paths, you probably want to test that they work. Probably with unit tests. Probably with Jest (that’s what I’m showing here anyway).
If the variables are determined at runtime, like in the first example above, a regular import './api-config' won’t cut it – Jest will cache the module after the first import, so you won’t be able to tweak the variables and see different results.
These tests will make use of two things: the require() function for importing the module inside tests, and the jest.resetModules() function to clear the cache.
// (this could also go in afterEach())
beforeEach(() => {
// Clear the Jest module cache
jest.resetModules();
// Clean up the environment
delete process.env.REACT_APP_BACKEND_HOST;
});
it('points to production', () => {
const config = setupTest('realsite.com');
expect(config.API_ROOT).toEqual('https://api.realsite.com/api/v1');
});
it('points to staging', () => {
const config = setupTest('staging.realsite.com');
expect(config.API_ROOT).toEqual('https://staging.api.realsite.com/api/v1');
});
it('points to QA', () => {
const config = setupTest('qa5.company.com');
expect(config.API_ROOT).toEqual('https://api.qa5.company.com/api/v1');
});
it('points to dev (default)', () => {
const config = setupTest('localhost');
expect(config.API_ROOT).toEqual('http://localhost:8080/api/v1');
});
it('points to dev (custom)', () => {
process.env.REACT_APP_BACKEND_HOST = 'custom';
const config = setupTest('localhost');
expect(config.API_ROOT).toEqual('custom/api/v1');
});
function setupTest(hostname) {
setHostname(hostname);
return require('./api-config');
}
// Set the global "hostname" property
// A simple "window.location.hostname = ..." won't work
function setHostname(hostname) {
Object.defineProperty(window.location, 'hostname', {
writable: true,
value: hostname
});
}