Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to use environment variables at runtime? #477

Closed
alex2 opened this issue Jan 31, 2018 · 10 comments
Closed

How to use environment variables at runtime? #477

alex2 opened this issue Jan 31, 2018 · 10 comments
Labels

Comments

@alex2
Copy link

alex2 commented Jan 31, 2018

I've just started using Razzle but it seems like a webpack DefinePlugin is stripping out all the environment variables at runtime and replacing them with pre-baked ones. Since environment variables are the standard way to configure applications in different environments (e.g. heroku, lambda, all 12-factor compatible hosting) I assume there's something I'm missing. Do you have any advice for how to stop razzle replacing everything in process.env?

Many thanks

@iamrommel
Copy link

I second the motion for @alex2 claim, I just found out that it really really replaces the existing process.env because of that DefinePlugin,

I need it NOT be overwritten too

@whs
Copy link

whs commented Feb 2, 2018

What I do is by breaking static analysis:

let proc = process;
module.exports = proc.exports;

Then require('./env').RUNTIME_ENV

@ikappas
Copy link
Contributor

ikappas commented Feb 6, 2018

I've personally struggled with this issue and spend several hours figuring out a solution to this issue.

This is inherent to webpack compilation and not related to razzle it self.

After looking into how create-react-app handles this, and porting some javascript and ruby code, across two projects, I've successfully deployed a razzle typescript react app in a docker container on heroku with the following solution:

env.ts

This script is used as a module to handle runtime 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;

usage:

import { loadRuntimeEnv, EnvironmentStore } from './env';
const env: EnvironmentStore = loadRuntimeEnv();

const serverHost: string =env.RAZZLE_SERVER_HOST || 'localhost';

docker-start.js

This script is used as the entrypoint point instead of server.js and is used to inject {{RAZZLE_VARS_AS_BASE64_JSON___... }} placehoder with the actual runtime environment variables.

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

Please note that the overflow message processing is not finished, but I hope this helps

References:

Heroku Buildpack for create-react-app
Inner layer of Heroku Buildpack for create-react-app

@ikappas
Copy link
Contributor

ikappas commented Feb 6, 2018

Not relevant to the issue but here is the Dockerfile for completeness:

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

@ikappas
Copy link
Contributor

ikappas commented Feb 6, 2018

@jaredpalmer Can you have a look at this and see whether this solution can be used to solve #356, #394 and this issue in razzle or a child package?

@stale
Copy link

stale bot commented Aug 15, 2018

Hola! So here's the deal, between open source and my day job and life and what not, I have a lot to manage, so I use a GitHub bot to automate a few things here and there. This particular GitHub bot is going to mark this as stale because it has not had recent activity for a while. It will be closed if no further activity occurs in a few days. Do not take this personally--seriously--this is a completely automated action. If this is a mistake, just make a comment, DM me, send a carrier pidgeon, or a smoke signal.

@stale stale bot added the stale label Aug 15, 2018
@stale
Copy link

stale bot commented Aug 22, 2018

ProBot automatically closed this due to inactivity. Holler if this is a mistake, and we'll re-open it.

@stale stale bot closed this as completed Aug 22, 2018
@HamidTanhaei
Copy link

if anyone want to use .env variables in client side in runtime, use this little package.
https://www.npmjs.com/package/razzle-plugin-runtimeenv

@creatzor
Copy link

creatzor commented Jan 16, 2019

I came across this during a google search for an issue related to this. Full disclosure: im not using razzle but I think this might be helpful.

A way to circumvent webpack's replacement of process.env is to access the env vars in a different way.

You can access it via

const env = process['env']; 
console.log(env.RUNTIME_VAR); // should be var passed in at runtime

or by importing process... import * as process from 'process'; basically, any way other than process.env

DefinePlugin only replaces free variables

@HamidTanhaei
Copy link

@albert-the-creator Razzle clear env variable's during development in browser area.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

6 participants