Razzle: RFC: process.env.RAZZLE_RUNTIME_XXXX

Созданный на 8 мар. 2018  ·  27Комментарии  ·  Источник: jaredpalmer/razzle

Текущее поведение

Люди борются с тем, как Razzle работает с переменными .env (то есть привязывает их во время _build_ с помощью webpack) точно так же, как create-response-app.

Ожидаемое поведение

У Razzle должен быть способ учитывать переменные времени выполнения, чтобы пользователям было проще развертывать свои приложения в Now / Heroku / Azure и т. Д.

Предлагаемое решение

Сделайте PORT , HOST и любые другие переменные env с префиксом RAZZLE_RUNTIME_XXXXXXX доступными во время выполнения.

До

Доступно во время компиляции (т.е. будет преобразовано в строку веб-пакетом)

  • RAZZLE_XXXXXX
  • PORT
  • HOST

После

Доступно во время выполнения

  • PORT
  • HOST
  • RAZZLE_RUNTIME_XXXXXXX

Доступно во время компиляции (т.е. будет преобразовано в строку веб-пакетом)

  • RAZZLE_XXXXXX

Обсуждение

Другой альтернативой является _only_ строковое преобразование переменных с префиксом RAZZLE_XXXX как это делает плагин razzle-heroku . Это также будет обратно совместимо. С одной стороны, это упростило бы работу с тем, как Heroku называет свои переменные среды конфигурации (например, MONGO_URI ). С другой стороны, это было бы _ слишком_ легко испортить случайно (то есть ссылаться на переменную времени выполнения в совместно используемом изоморфном коде, которая undefined на клиенте ... взрывая приложение).

Связанный

527 # 526 # 514 # 477 # 356 # 285

discussion

Самый полезный комментарий

@jaredpalmer Возможно, мне что-то не хватает, но это все еще проблема для таких вещей, как PORT, несмотря на то, что подразумевают документы readme. process.env.MY_THING работает нормально, но process.env.PORT по-прежнему заменяется во время сборки и не читается во время выполнения. Так что пример с Heroku на самом деле не работает.

Я не вижу специальной обработки PORT, HOST и т. Д., Как обсуждается в этой теме.

Все 27 Комментарий

Я лично не стал бы слишком беспокоиться о том, что process.env недоступен на клиенте. Мы уже должны знать, что «окно» недоступно на сервере; Мне кажется, что у process.env будет противоположное поведение. (нет процесса узла, для которого нужно иметь env, и браузеры не раскрывают свои env vars, о которых я знаю) Я думаю о process.env как о месте, где могут оказаться секреты сервера, поэтому я бы предпочел, чтобы это было по умолчанию недоступен на стороне клиента.

Если бы это зависело от меня, переменные process.env.RAZZLE_INLINED_XXXXXX были бы скомпилированы во время сборки (и доступны на клиенте как встроенные строки DefinePlugin'd), а все остальное было бы доступно только на стороне сервера.

NODE_ENV также может быть встроен, поскольку он в основном используется как описание сборки, а не как переменная, например среда, в которой выполняется код. Для этого есть также некоторые причины для повышения производительности.

Мне нравится, что ПОРТ и ХОСТ являются переменными во время выполнения. Я могу запускать одни и те же артефакты сборки в разных средах.

Я могу подтвердить борьбу. Текущее поведение было не тем, что я ожидал, и особенно сбил меня с толку при развертывании с up на aws. Теперь я настроил razzle.config.js так, чтобы все переменные, специфичные для razzle, имели префикс RAZZLE_XXX и они были преобразованы в строку во время компиляции. Остальные доступны по process.env.XXX . Я также согласен с @gregmartyn по поводу недоступности переменных времени выполнения через process.env .

При попытке установить Razzle в существующий проект React мы столкнулись с этой проблемой. Порт не может быть переопределен во время выполнения, а другие переменные 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 _run_ с PORT=81 (при сборке с 80 ), переменная среды клиента останется 80 а переменная сервера приведет к 81 .

Такое поведение, конечно, может привести к неожиданному поведению, но оно обеспечивает наиболее гибкое использование process.env при сохранении полной обратной совместимости. Порт все еще может быть перезаписан на сервере во время выполнения, а другие переменные среды, установленные хостинговыми платформами, будут работать на сервере как есть.

Я думаю, что основная проблема здесь в том, что Razzle в настоящее время пытается упаковать несколько разрозненных функций в один объект и коренным образом изменить существующую функциональность.

Он пытается:

  1. Превратите переменные process.env в константы по соображениям производительности (см. Пример минификации на https://webpack.js.org/plugins/define-plugin/#usage и медленность nodejs на https://github.com/nodejs/node/issues / 3104)
  2. Сделайте эти константы доступными как для сервера, так и для клиента, чтобы изометрический код не беспокоился.

Проблемы:

  • process.env предназначен для _variables_ окружения. Превращение его в набор констант меняет его ожидаемое поведение.
  • process.env часто содержит конфиденциальную информацию, включая пароли, поэтому это не лучший источник данных для обмена с клиентом. Если он частично разделяется, а частично не используется совместно, разработчик должен знать, что эта единственная вещь - process.env - не только ведет себя не так, как изначально, но также имеет другое поведение в зависимости от префиксов ключей.
  • Он превращается в константы во время сборки, что отличается от того, как переменные среды ведут себя в других контекстах. Docker, например, делает различие между переменными времени сборки (полезно, когда сама сборка требует информации о переменных в зависимости от того, где она выполняется) и переменными среды (полезно, когда один предварительно созданный образ развертывается в разных средах). Эластичный бобовый стебель, 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 = production означает, что это оптимизированная сборка»

@gregmartyn, мы должны установить NODE_ENV для оптимизации производительности и использовать предустановку babel, поскольку она работает сейчас. Изначально я решил возиться с env, чтобы упростить переход от CRA, поскольку SSR часто добавляется после того, как проект уже запущен. Мы также используем CRA в других проектах, поэтому он упрощает наши инструменты сборки (немного).

Ага; согласился, что NODE_ENV - полезное исключение. Это отдельная вещь и тесно связана со сборкой, поэтому в этом нет ничего удивительного. Я пропустил CRA из специального решения SSR, поэтому я не совсем знаком с тем, как они что-то делают. Похоже, это тоже проблема: https://github.com/facebook/create-react-app/issues/2353

Я думаю, что это более серьезная проблема для Razzle, чем для CRA, потому что код времени выполнения в приложении CRA вообще не запускается на сервере. CRA может делать с process.env все, что хочет, потому что, что касается его клиентского кода, в противном случае он был бы пустым. Razzle, с другой стороны, запускает Express для своего SSR, и этот код разумно ожидает, что process.env будет иметь обычную семантику с доступом к полному набору переменных среды выполнения узла. Process.env имеет реальное значение на сервере, поэтому, к сожалению, CRA адаптировал его для другого варианта использования. Они могли бы использовать другое имя вместо process.env, например cra.inlines. Вместо этого в изоморфный код попадает решение, которое было принято только с учетом клиентской стороны.

Следует отметить красным цветом везде, что ВСЕ переменные среды RAZZLE_XXX доступны на клиенте.

Как использовать конфиденциальные переменные среды, не отправляя их клиенту?

Они не отправляются клиенту, если вы не ссылаетесь на них в изоморфном коде

@jaredpalmer, возможно, эта проблема специфична для afterjs? Я ссылаюсь на них только в серверном коде.

Я хотел бы проголосовать за возможность определять переменные среды без префикса RAZZLE . По крайней мере, process.env не следует уничтожать на стороне сервера, что лишает вас возможности использовать что-то вроде dotenv для загрузки переменных env на стороне сервера. Это кажется слишком навязчивым предположением о среде приложения.

Я не совсем понимаю, как razzle в настоящее время вводит переменные среды в клиент и сервер, но, конечно, вам не нужны специфические для сервера вещи на клиенте. К сожалению, сейчас для меня это своего рода препятствие.

Я повторно публикую свое предлагаемое решение для приложения изоморфного реагирования с https://github.com/jaredpalmer/razzle/issues/477#issuecomment -363538712

Основная концепция заключается в использовании заполнителя во время компиляции, который вводится во время выполнения непосредственно перед выполнением сервера, чтобы правильно установить переменные среды выполнения. Это решение предназначено для запуска сервера в контейнере докеров, но, вероятно, его можно адаптировать для этого RFC.

Обратите внимание, что в этом решении переменные среды RAZZLE_XXXX сопоставляются и вводятся вместе с HOST, PORT и REDIS_URL.


Я лично боролся с этой проблемой и потратил несколько часов на поиск решения этой проблемы.

Это характерно для компиляции веб-пакетов и не связано с самодвижением.

После изучения того, как приложение create-react-app обрабатывает это, и переноса некоторого кода javascript и ruby ​​в два проекта, я успешно развернул приложение реакции razzle typescript в контейнере докеров на heroku со следующим решением:

env.ts

Этот сценарий используется как модуль для обработки среды выполнения env.

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);
}

Dockerfile

# 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"]

Обратите внимание, что обработка сообщения о переполнении еще не завершена, но я надеюсь, что это поможет.

Использованная литература:

Heroku Buildpack для приложения create-response-app
Внутренний слой Heroku Buildpack для приложения create-react-app

Это характерно для компиляции веб-пакетов и не связано с самодвижением.

Razzle - это тот, кто настраивает DefinePlugin. Это решаемо в Razzle.

Думаю, я понимаю, что вы говорите. Скажите, если я ошибся: во время сборки поместите в process.env заполнители, которые заменяют строку при запуске экземпляра в сборке сервера. Он предназначен для обработки секретов сервера. (Я не понимаю, почему он не может работать и на клиентской сборке). Проблемы: Он не работает с HMR. Это хак - он вводит произвольную границу 4k. В своей текущей форме он не обращается к переменным env, которые должны быть переданы клиенту - они остаются константами времени сборки. Это дополнительный этап запуска контейнеров.

Чтобы перефразировать многое из того, что я сказал в 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 (см. Опубликованные ссылки), и предназначена для переменных env времени выполнения на стороне сервера.

Открыл PR, который, как я считаю, должен помочь с этим корнем этой проблемы - env ​​vars не доступны на сервере во время выполнения, интересно узнать, решит ли это некоторые из проблем здесь.

Я также согласен с тем, что PORT & HOST идеале также следует оставить в покое во время компиляции сервера.

@tgriesser здорово! Это большое улучшение.
Помимо PORT и HOST , я бы добавил PUBLIC_PATH в список переменных, которые не должны компилироваться.

Я все еще нахожу, что все чувствительные пользовательские переменные среды компилируются в клиент. Я ссылаюсь на них только в хотлоадера ? Как я могу предотвратить их попадание к клиенту?

Привет всем, на этой неделе я занимаюсь всем этим на работе. Будьте на связи. # 611 скорее всего будет объединен.

Следуя новому руководству в readme с config.js, я помог мне удалить чувствительные переменные env из пакета. Потрясающе: D

См. Примечания к версии 2

@jaredpalmer Возможно, мне что-то не хватает, но это все еще проблема для таких вещей, как PORT, несмотря на то, что подразумевают документы readme. 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

Может кто-нибудь посоветовать, как развернуть приложение Razzle в Azure? Я действительно борюсь с этим.

если кто-то хочет использовать переменные .env во время выполнения, используйте этот небольшой пакет.
https://www.npmjs.com/package/razzle-plugin-runtimeenv

Как это работает? Не могли бы вы показать пример?

Я думаю, что переменные среды действительно должны вводиться во время выполнения. Если мы реализуем приложение razzle, мы хотели бы создать изображение независимо от среды, в которой оно запускается, и прочитать переменные среды при запуске сервера и затем передать их клиентскому приложению.

Любой другой подход на самом деле не использует переменные среды, поскольку это происходит только во время сборки.

Как я уже упоминал здесь:
https://github.com/HamidTanhaei/razzle-plugin-runtime/issues/1#issuecomment -525731273

вы можете использовать свои файлы .env и .env.development во время выполнения с помощью razzle-plugin-runtime . он добавляет возможность использовать ваши переменные env в вашем приложении во время выполнения.

например, я использую его для настройки axios :
axios.defaults.baseURL = $ {process.env.RAZZLE_APP_API_BASE_PATH} $ {process.env.RAZZLE_APP_API_VERSION} ;
и вы можете предоставить производственные переменные ENV для производства следующим образом:
https://github.com/jaredpalmer/razzle#adding-временные-переменные-среды-в-вашей-оболочке

Может кто-нибудь посоветовать, как развернуть приложение Razzle в Azure? Я действительно борюсь с этим.

Я решил проблему с портом Azure с помощью решения, предоставленного @fabianishere по адресу https://github.com/jaredpalmer/razzle/issues/906#issuecomment -467046269.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги

Смежные вопросы

krazyjakee picture krazyjakee  ·  3Комментарии

howardya picture howardya  ·  5Комментарии

gabimor picture gabimor  ·  3Комментарии

MaxGoh picture MaxGoh  ·  4Комментарии

pseudo-su picture pseudo-su  ·  3Комментарии