Razzle: RFC:process.env.RAZZLE_RUNTIME_XXXX

创建于 2018-03-08  ·  27评论  ·  资料来源: jaredpalmer/razzle

当前行为

人们对 Razzle 如何像 create-react-app 一样处理.env变量(即在 _build_ 时由 webpack 将它们字符串化)而苦恼。

预期行为

Razzle 应该有一种方法来支持运行时变量,以便用户可以更轻松地将他们的应用程序部署到 Now/Heroku/Azure 等。

建议的解决方案

使PORTHOST和任何其他以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 ...使应用程序爆炸)。

有关的

527#526#514#477#356#285

discussion

最有用的评论

@jaredpalmer我可能会遗漏一些东西,但这仍然是 PORT 之类的问题,尽管自述文件暗示了什么。 process.env.MY_THING工作正常,但process.env.PORT仍会在构建时被替换,并且不会在运行时读取。 所以 Heroku 示例实际上不起作用。

我没有看到本线程中讨论的对 PORT、HOST 等的任何特殊处理。

所有27条评论

我个人不会太担心 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 目前正试图将多个不同的功能打包到一个对象中,并从根本上改变现有的功能。

它试图:

  1. 出于性能原因将 process.env 变量转换为常量(参见 https://webpack.js.org/plugins/define-plugin/#usage 上的缩小示例和 https://github.com/nodejs/node/issues 上的 nodejs 缓慢/3104)
  2. 让这些常量对服务器和客户端都可用,这样等距代码就不用担心了。

问题:

  • process.env 用于环境 _variables_。 把它变成一组常量会改变它的预期行为。
  • process.env 通常包含敏感信息,包括密码,因此它不是与客户端共享数据的好来源。 如果它部分共享,部分不共享,则开发人员必须知道这一件事——process.env——不仅不像它本身的行为那样,而且根据键的前缀它也有不同的行为。
  • 它在构建时被转换为常量,这与环境变量在其他上下文中的行为方式不同。 例如,Docker 区分了构建时变量(当构建本身需要变量信息取决于它的运行位置时很有用)和环境变量(当一个预构建的映像部署到不同的环境时很有用)。 Elastic Beanstalk、Heroku 等。 照着做。
  • 建议的“RAZZLE_RUNTIME_”前缀不是不言自明的。 它应该传达它在客户端可用并且它不是变量。 我认为“INLINED_”涵盖了这一点,因为内联通常用于指代在构建时发生的事情。 它并不完美,(“ISOMORPHIC_INLINED_”?)但它也试图变得简短。

我的偏好是将此功能拆分为不同的部分,并且根本不篡改 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';

docker-start.js

此脚本用作入口点而不是 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 ,我认为应该有助于解决这个问题的根源 - 环境变量在运行时在服务器上不可用,有兴趣听听这是否解决了这里的一些问题。

我也同意PORTHOST也最好在服务器编译时单独放置。

@tgriesser不错! 这是一个很大的改进。
除了PORTHOST ,我还会将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 应用程序吗? 我真的很挣扎。

我使用@fabianisherhttps://github.com/jaredpalmer/razzle/issues/906#issuecomment -467046269 共享的解决方案解决了 Azure 端口问题

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

GouthamKD picture GouthamKD  ·  3评论

JacopKane picture JacopKane  ·  3评论

sebmor picture sebmor  ·  4评论

howardya picture howardya  ·  5评论

panbanda picture panbanda  ·  5评论