人们对 Razzle 如何像 create-react-app 一样处理.env
变量(即在 _build_ 时由 webpack 将它们字符串化)而苦恼。
Razzle 应该有一种方法来支持运行时变量,以便用户可以更轻松地将他们的应用程序部署到 Now/Heroku/Azure 等。
使PORT
、 HOST
和任何其他以RAZZLE_RUNTIME_XXXXXXX
为前缀的环境变量在运行时可用。
在编译时可用(即会被 webpack 字符串化)
RAZZLE_XXXXXX
PORT
HOST
运行时可用
PORT
HOST
RAZZLE_RUNTIME_XXXXXXX
在编译时可用(即会被 webpack 字符串化)
RAZZLE_XXXXXX
另一种选择是像 razzle -heroku 插件那样以 _only_ 字符串化变量前缀RAZZLE_XXXX
。 这也将向后兼容。 一方面,这将使 Heroku 如何命名它的配置环境变量(例如MONGO_URI
)更容易。 另一方面,这将_太_容易意外搞砸(即在共享同构代码中引用一个运行时变量,即客户端上的undefined
...使应用程序爆炸)。
我个人不会太担心 process.env 在客户端不可用。 我们已经知道'window'在服务器上不可用; 对我来说 process.env 会有相反的行为是有道理的。 (没有节点进程可以使用 env,并且浏览器不会公开我所知道的它们的 env 变量)我认为 process.env 是服务器机密可以结束的地方,所以我宁愿它是默认在客户端不可用。
如果由我决定, process.env.RAZZLE_INLINED_XXXXXX 变量将在构建过程中被编译,(并且在客户端上作为内联 DefinePlugin'd 字符串可用),其他任何东西都只能在服务器端可用。
NODE_ENV 也可以内联,因为它主要用作构建的描述,而不是像代码运行的环境这样的变量。这样做还有一些性能原因。
我确实喜欢 PORT 和 HOST 在运行时可变的想法。 我可能在不同的环境中运行相同的构建工件。
我可以确认斗争。 当前的行为不是我所期望的,尤其是在 aws 上使用up
进行部署时,我被绊倒了。 我现在已经自定义了razzle.config.js
以便所有特定于 razzle 的变量都以RAZZLE_XXX
为前缀,并且这些变量在编译时被字符串化。 其余的可在process.env.XXX
。 我也同意@gregmartyn关于process.env
运行时变量不可用的看法。
在尝试在现有 React 项目中设置 Razzle 时,我们遇到了这个问题。 该端口在运行时不可覆盖,并且其他 process.env 变量在服务器中不可用。
构建时的当前行为:
process.env.PORT
process.env.NODE_ENV;
process.env.ASSETS_MANIFEST;
process.env.HOSTING_SET_VARIABLE;
在客户端和服务器上都变成:
3000;
'development';
'/Users/[...]/build/assets.json';
undefined;
虽然这确保客户端和服务器可以使用完全相同的进程环境,但这使得无法在运行时覆盖或使用其他环境变量。
如果你想完全向后兼容,它应该在构建时转换为这个(在服务器上,客户端可以保持不变):
process.env.PORT || 3000;
process.env.NODE_ENV || 'development';
process.env.ASSETS_MANIFEST || '/Users/[...]/build/assets.json';
process.env.HOSTING_SET_VARIABLE;
这样,您可以在客户端和服务器中使用 process.env.PORT。 它将默认为 3000。
如果 razzle 是 _built_ 与PORT=80
,则客户端将process.env.PORT
转换为80
并且服务器将其转换为process.env.PORT || 80
。 这将导致与现在相同的行为。
如果 razzle 使用PORT=81
_run_(使用80
构建时),则客户端环境变量将保持80
而服务器变量将导致81
。
这种行为当然会导致意外行为,但它确实提供了最灵活的 process.env 用法,同时保持了完全的向后兼容性。 该端口仍然可以在运行时在服务器上被覆盖,并且托管平台设置的其他环境变量将按原样在服务器上工作。
我认为这里的根本问题是 Razzle 目前正试图将多个不同的功能打包到一个对象中,并从根本上改变现有的功能。
它试图:
问题:
我的偏好是将此功能拆分为不同的部分,并且根本不篡改 process.env。
DefinePlugin 可以调用像 razzle.config.js 这样的文件来获取构建常量
推荐使用类似于 Redux 的 configureStore(window.__PRELOADED_STATE__) 的模式来将数据从服务器传递到客户端。
为了向后兼容,升级说明可以提供一个示例 razzle.config.js 来定义老式的方式。
一个附录:我说“完全不要篡改 process.env。” 但我可以看到 process.env.NODE_ENV 有一个例外,原因是我上面的评论中的性能原因,并且“NODE_ENV”本身通常具有“NODE_ENV = 生产意味着这是一个优化构建”的含义
@gregmartyn我们必须为性能优化设置 NODE_ENV 并使用 babel 预设,因为它现在可以工作。 我使用 env 的最初理由是为了让从 CRA 迁移变得更容易,因为 SSR 通常是在项目已经启动后添加的。 我们还在其他项目中使用 CRA,因此它简化了我们的构建工具(稍微)。
是的; 同意 NODE_ENV 是一个有用的例外。 这是它自己的东西并且与构建紧密相关,所以这并不奇怪。 我从自定义 SSR 解决方案中直接跳过了 CRA,所以我不太熟悉他们的工作方式。 看起来这也是那里的一个问题: https :
我认为这对 Razzle 来说是一个比 CRA 更大的问题,因为 CRA 应用程序中的运行时代码根本不在服务器上运行。 CRA 可以使用 process.env 做任何它想做的事情,因为就其客户端代码而言,否则它将为空。 另一方面,Razzle 开始表达其 SSR,并且该代码合理地期望 process.env 具有访问完整节点运行时环境变量集的通常语义。 Process.env 在服务器上具有实际意义,因此不幸的是,CRA 将其用于不同的用例。 他们可以使用其他名称而不是“process.env”,例如“cra.inlines”。 相反,同构代码会受到仅考虑客户端时做出的决定的影响。
应该以红色标出 RAZZLE_XXX 环境变量在客户端上全部可用。
如何使用敏感环境变量而不将其发送给客户端?
它们不会发送给客户端,除非您在同构代码中引用它们
@jaredpalmer也许这个问题是特定于 afterjs 的? 我只在服务器代码中引用它们。
我想为定义没有RAZZLE
前缀的环境变量的能力投上一票。 至少, process.env
不应该在服务器端被清除,这使您无法使用诸如dotenv
来加载服务器端环境变量。 这似乎对应用程序环境做出的假设太具有侵入性。
我不太清楚 razzle 目前如何将环境变量注入客户端和服务器,但你肯定不希望客户端上有特定于服务器的东西。 不幸的是,这对我来说现在有点破坏交易。
我正在从https://github.com/jaredpalmer/razzle/issues/477#issuecomment -363538712 重新发布我为同构反应应用程序提出的解决方案
主要概念是在编译时使用占位符,在服务器执行之前在运行时注入,以便正确设置运行时环境变量。 此解决方案用于在 docker 容器中运行服务器,但可能适用于此 RFC。
请注意,在此解决方案中,RAZZLE_XXXX 环境变量与 HOST、PORT 和 REDIS_URL 一起匹配并注入。
我个人一直在努力解决这个问题,并花了几个小时找出这个问题的解决方案。
这是 webpack 编译所固有的,与 razzle it self 无关。
在研究了 create-react-app 如何处理这个问题,并在两个项目中移植了一些 javascript 和 ruby 代码之后,我已经成功地在 heroku 上的 docker 容器中部署了一个 razzle typescript react 应用程序,并使用以下解决方案:
此脚本用作处理运行时环境的模块。
export interface EnvironmentStore {
NODE_ENV?: string;
[key: string]: string | undefined;
}
// Capture environment as module variable to allow testing.
let compileTimeEnv: EnvironmentStore;
try {
compileTimeEnv = process.env as EnvironmentStore;
} catch (error) {
compileTimeEnv = {};
// tslint:disable-next-line no-console
console.log(
'`process.env` is not defined. ' +
'Compile-time environment will be empty.'
);
}
// This template tag should be rendered/replaced with the environment in production.
// Padded to 4KB so that the data can be inserted without offsetting character
// indexes of the bundle (avoids breaking source maps).
/* tslint:disable:max-line-length */
const runtimeEnv = '{{RAZZLE_VARS_AS_BASE64_JSON__________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________}}';
/* tslint:enable:max-line-length */
// A function returning the runtime environment, so that
// JSON parsing & errors occur at runtime instead of load time.
export const loadRuntimeEnv = (): EnvironmentStore => {
let env;
if (typeof env === 'undefined') {
if (compileTimeEnv.NODE_ENV === 'production') {
try {
env = JSON.parse((Buffer.from(runtimeEnv.trim(), 'base64').toString()));
} catch (error) {
env = {};
const overflowsMessage = runtimeEnv.slice(32, 33) !== null;
// tslint:disable-next-line no-console
console.error(
'Runtime env vars cannot be parsed. Content is `%s`',
runtimeEnv.slice(0, 31) + (overflowsMessage ? '…' : '')
);
}
} else {
env = compileTimeEnv;
}
}
return env;
};
export default loadRuntimeEnv;
用法:
import { loadRuntimeEnv, EnvironmentStore } from './env';
const env: EnvironmentStore = loadRuntimeEnv();
const serverHost: string =env.RAZZLE_SERVER_HOST || 'localhost';
此脚本用作入口点而不是 server.js,并用于将 {{RAZZLE_VARS_AS_BASE64_JSON___... }} 占位符注入实际运行时环境变量。
require('newrelic');
const logger = require('heroku-logger');
const path = require('path');
const fs = require('fs');
const PLACEHOLDER = /\{\{RAZZLE_VARS_AS_BASE64_JSON_*?\}\}/;
const MATCHER = /^RAZZLE_/i;
const InjectableEnv = {
inject: function(file, ...args) {
const buffer = fs.readFileSync(file, { encoding: 'utf-8' });
let injectee = buffer.toString();
const matches = injectee.match(PLACEHOLDER);
if (!matches) {
return;
}
const placeholderSize = matches[0].length;
let env = InjectableEnv.create(args);
const envSize = env.length;
const newPadding = placeholderSize - envSize;
if (newPadding < 0) {
console.log('You need to increase your placeholder size');
process.exit();
}
const padding = Array(newPadding).join(' ');
env = InjectableEnv.pad(padding, env);
const injected = injectee.replace(PLACEHOLDER, env);
fs.writeFileSync(file, injected, { encoding: 'utf-8' });
},
create: function() {
const vars = Object.keys(process.env)
.filter(key => MATCHER.test(key))
.reduce((env, key) => {
env[key] = process.env[key];
return env;
}, {});
vars.NODE_ENV = process.env.NODE_ENV;
if (typeof process.env.HOST !== 'undefined' && typeof vars.RAZZLE_SERVER_HOST === 'undefined') {
vars.RAZZLE_SERVER_HOST = process.env.HOST;
}
if (typeof process.env.PORT !== 'undefined' && typeof vars.RAZZLE_SERVER_PORT === 'undefined') {
vars.RAZZLE_SERVER_PORT = process.env.PORT;
}
if (typeof process.env.REDIS_URL !== 'undefined' && typeof vars.RAZZLE_REDIS_URL === 'undefined') {
vars.RAZZLE_REDIS_URL = process.env.REDIS_URL;
}
return Buffer.from(JSON.stringify(vars)).toString('base64');
},
pad: function(pad, str, padLeft) {
if (typeof str === 'undefined')
return pad;
if (padLeft) {
return (pad + str).slice(-pad.length);
} else {
return (str + pad).substring(0, pad.length);
}
}
}
const root = process.cwd();
const serverBundle = path.resolve(path.join(root, '/build/server.js'));
if (fs.existsSync(serverBundle)) {
logger.info('Injecting runtime env');
InjectableEnv.inject(serverBundle);
logger.info('Launching server instance');
require(serverBundle);
}
# You should always specify a full version here to ensure all of your developers
# are running the same version of Node.
FROM node:8.9.4
ENV NODE_ENV=production \
REACT_BUNDLE_PATH=/static/js/vendor.js \
PATH=/app/node_modules/.bin:$PATH \
NPM_CONFIG_LOGLEVEL=warn
RUN curl -o- -L https://yarnpkg.com/install.sh | bash
# use changes to package.json to force Docker not to use the cache
# when we change our application's nodejs dependencies:
COPY package.json yarn.lock /tmp/
RUN cd /tmp \
&& yarn install --production=false --pure-lockfile \
&& mkdir -p /app \
&& cp -a /tmp/node_modules /app \
&& yarn cache clean \
&& rm -rf *.*
# From here we load our application's code in, therefore the previous docker
# "layer" thats been cached will be used if possible
WORKDIR /app
ADD . /app
RUN yarn build
EXPOSE 3000
CMD ["node", "docker-start.js"]
请注意,溢出消息处理尚未完成,但我希望这会有所帮助
用于 create-react-app 的 Heroku Buildpack
用于 create-react-app 的 Heroku Buildpack 内层
这是 webpack 编译所固有的,与 razzle it self 无关。
Razzle 是设置 DefinePlugin 的工具。 这在 Razzle 中是可以解决的。
我想我按照你说的去做。 如果我弄错了,请告诉我:在构建时,将占位符放入 process.env 中,以便在服务器构建中的实例启动时替换字符串。 它旨在处理服务器机密。 (我不明白为什么它也不能在客户端构建上运行) 问题:它不适用于 HMR。 这是一个黑客——它引入了一个任意的 4k 边界。 在目前的形式中,它没有解决必须与客户端共享的环境变量——那些仍然是构建时常量。 这是容器的额外启动步骤。
重述我在https://github.com/jaredpalmer/razzle/issues/528#issuecomment -377058844 中所说的很多内容
我认为解决方案是认识到 Razzle 和 CRA 正试图将更多的功能打包到 process.env 中。 为了让它与 Docker 一起工作,我们试图让一个对象的字段具有 4 种可能状态之一:静态(构建时间)和动态(这里是容器启动时间)、秘密和非秘密。 我们可以为所有 4 个状态(process.env.STATIC_PRIVATE_X,process.env.DYNAMIC_PUBLIC_Y,...)想出前缀,但我认为我们会更好地使用更简洁的解决方案。
如果 process.env 以它本机的方式运行——作为服务器机密的存储——那么事情就更容易理解了。 有一个例外:NODE_ENV 作为构建时内联,但这很好,因为它是构建的一个属性。 在运行时设置 NODE_ENV 没有意义。
剩下的就是一种向客户端获取数据的方法。 我根本不明白为什么要使用 process.env。 为什么不将例如 razzle.build.X 用于静态内容,并以与 redux 相同的方式将动态内容传递给客户端?
另一个问题是 process.env 在 Node 上运行缓慢,但最好使用读取 process.env 一次的缓存层来解决这个问题。
@gregmartyn我同意这是一个黑客......它确实引入了任意的 4K 边界。 这个想法基于当前使用 CRA 完成的工作(请参阅发布的参考资料),并且适用于服务器端运行时环境变量。
打开了一个 PR ,我认为应该有助于解决这个问题的根源 - 环境变量在运行时在服务器上不可用,有兴趣听听这是否解决了这里的一些问题。
我也同意PORT
和HOST
也最好在服务器编译时单独放置。
@tgriesser不错! 这是一个很大的改进。
除了PORT
和HOST
,我还会将PUBLIC_PATH
到不应编译的变量列表中。
我仍然发现敏感的自定义环境变量都被编译到客户端中。 我只在server.js 中引用它们。 razzle是否认为同构是因为
大家好,我本周将在工作中解决所有这些问题。 敬请关注。 #611 很可能会被合并。
遵循带有 config.js 的自述文件中的新指南,对于从包中删除敏感的 env 变量对我有用。 真棒 :D
查看 v2 注释
@jaredpalmer我可能会遗漏一些东西,但这仍然是 PORT 之类的问题,尽管自述文件暗示了什么。 process.env.MY_THING
工作正常,但process.env.PORT
仍会在构建时被替换,并且不会在运行时读取。 所以 Heroku 示例实际上不起作用。
我没有看到本线程中讨论的对 PORT、HOST 等的任何特殊处理。
请注意,使 PORT 成为真实变量也被 #581 阻止。 我必须修补它并使用 razzle.config.js 创建一个 DefinePlugin 数组,该数组删除 PORT 以使其工作。 (但它确实有效!)
如果有人想在运行时使用 .env 变量,请使用这个小包。
https://www.npmjs.com/package/razzle-plugin-runtimeenv
有人可以建议如何在 Azure 上部署 Razzle 应用程序吗? 我真的很挣扎。
如果有人想在运行时使用 .env 变量,请使用这个小包。
https://www.npmjs.com/package/razzle-plugin-runtimeenv
它是如何工作的? 你能举个例子吗?
我认为确实应该在运行时注入环境变量。 如果我们包含一个 razzle 应用程序,我们希望创建一个独立于它运行的环境的图像,并在启动服务器时读取环境变量,然后将它们提供给客户端应用程序。
任何其他方法都不是真正使用环境变量,因为它仅在构建期间发生。
正如我在这里提到的:
https://github.com/HamidTanhaei/razzle-plugin-runtime/issues/1#issuecomment -525731273
您可以通过razzle-plugin-runtime
在运行时使用您的 .env 和 .env.development 文件。 它增加了在运行时在应用程序中使用环境变量的能力。
例如我用它来配置axios
:
axios.defaults.baseURL =
${process.env.RAZZLE_APP_API_BASE_PATH}${process.env.RAZZLE_APP_API_VERSION} ;
您可以为生产提供生产环境变量,如下所示:
https://github.com/jaredpalmer/razzle#adding -temporary-environment-variables-in-your-shell
有人可以建议如何在 Azure 上部署 Razzle 应用程序吗? 我真的很挣扎。
我使用@fabianisher在https://github.com/jaredpalmer/razzle/issues/906#issuecomment -467046269 共享的解决方案解决了 Azure 端口问题
最有用的评论
@jaredpalmer我可能会遗漏一些东西,但这仍然是 PORT 之类的问题,尽管自述文件暗示了什么。
process.env.MY_THING
工作正常,但process.env.PORT
仍会在构建时被替换,并且不会在运行时读取。 所以 Heroku 示例实际上不起作用。我没有看到本线程中讨论的对 PORT、HOST 等的任何特殊处理。