storybook: process.env is empty since 6.4.10

Describe the bug At runtime, inside of a story, we need access to a (subset of) process.env to pass some values from the buildserver onto Storybook, and ultimately the application proper.

In Storybook 6.4.9 this worked fine. In Storybook 6.4.10 this got broken. process.env is now { }. In Storybook 6.4.14 this is not fixed, unfortunately.

Neither env variables from the CLI are passed, nor variables from .env. We use both, and neither end up in process.env.

Is it possible that #17174 broke this? That one is the only fix that remotely seems to have anything to do with process.env, assuming the changelog is complete.

To Reproduce I’m not sure what has been done to get this to work in the first place, or whether this is standard functionality. I do see dotenv being exported from paths.js, but it’s really difficult to dig into Storybook to see what it’s doing with that, if anything.

System

Environment Info:

  System:
    OS: Windows 10 10.0.19043
    CPU: (12) x64 Intel(R) Core(TM) i9-8950HK CPU @ 2.90GHz
  Binaries:
    Node: 16.13.2 - C:\Program Files\nodejs\node.EXE
    npm: 8.3.2 - C:\Program Files\nodejs\npm.CMD
  Browsers:
    Chrome: 97.0.4692.99
    Edge: Spartan (44.19041.1266.0), Chromium (97.0.1072.62)
  npmPackages:
    @storybook/addon-a11y: 6.4.10 => 6.4.10
    @storybook/addon-actions: 6.4.10 => 6.4.10
    @storybook/addon-controls: 6.4.10 => 6.4.10
    @storybook/addon-docs: 6.4.10 => 6.4.10
    @storybook/addons: 6.4.10 => 6.4.10
    @storybook/preset-create-react-app: 3.2.0 => 3.2.0
    @storybook/react: 6.4.10 => 6.4.10
    @storybook/theming: 6.4.10 => 6.4.10

About this issue

  • Original URL
  • State: open
  • Created 2 years ago
  • Reactions: 40
  • Comments: 23 (1 by maintainers)

Most upvoted comments

As a temporary workaround, we can use this in the main.js:

module.exports = {
  webpackFinal: async config => {
    const definePlugin = config.plugins.find(
      ({ constructor }) => constructor && constructor.name === 'DefinePlugin',
    );
    if (definePlugin) {
      definePlugin.definitions = injectEnv(definePlugin.definitions);
    }

    return config;
  },
};

Where injectEnv() looks like this:

function injectEnv(definitions) {
  const env = 'process.env';

  if (!definitions[env]) {
    return {
      ...definitions,
      [env]: JSON.stringify(
        Object.fromEntries(
          Object.entries(definitions)
            .filter(([key]) => key.startsWith(env))
            .map(([key, value]) => [key.substring(env.length + 1), JSON.parse(value)]),
        ),
      ),
    };
  }
  return definitions;
}

Honestly though, I believe #17174 should be reverted, because this is quite a convoluted workaround to something that didn’t need to be broken in the first place. I’m sure it wasn’t intentionally broken, but it sure is a regression, iyam.

What is the status on this issue? Makes Storybook unusable, if you want some of the newer features.

Also accessing process.env by variable is not working which is very strange AndI it is very problematic since it works with create-react-app so our code access process.env[someEnv] in many places But it’s not working when we render these components inside storybook

@shilman I hope it can be resolved soon

const name = "REACT_APP_API_URL";

console.log(process.env.REACT_APP_API_URL); // logs real value
console.log(process.env["REACT_APP_API_URL"]); // logs real value
console.log(process.env[name]); // logs undefined
console.log(process.env); // logs empty object

Can we get an update on where this issue is at?

Running Storybook 7.0.12 with CRA , using a variable to read an environment value is not working.

const name = "REACT_APP_EXAMPLE";

console.log(process.env.REACT_APP_EXAMPLE);    // ✅ logs real value
console.log(process.env["REACT_APP_EXAMPLE"]); // ✅ logs real value
console.log(process.env[name]);                // ❌ logs undefined
console.log(process.env);                      // ✅ logs non-empty object

It seems that this problem has not been solved even in Storybook ver6.5, so I will describe how to define your own environment variables in main.js when the builder is vite.

async viteFinal(config: Record<string, any>) {
    return mergeConfig(config, {
      define: {
        'process.env.STORYBOOK': true,
      },
    })
  },

In the console, when I now log process.env, I’m seeing it as an object. There’s nothing holding anyone back from adding a key to it. So, my question is, how did it break in the first place?

Question number two that mystifies me completely, process.env is an empty object. So how on great mother earth is process.env.key ever going to work? No amount of magic is going to give folks a value out of an empty object.

On top of this, when our website runs by itself, outside of Storybook, our config is set up to have process.env fully populated with the keys we need. This used to be the case in Storybook too. I cannot emphasize enough that this is a regression, and it breaks components that rely on it. I really don’t think it’s right to put a breaking change in a minor version update, no matter what it is. At best, this should have been kept on hold until Storybook 6.5.

Following the above changes, I was able to retain dynamic environment access:

// main.ts
import type { StorybookConfig } from '@storybook/react-webpack5';

const config: StorybookConfig = {
  [...],
  env: (config) => ({
    ...config,
    STORYBOOK_ENVS: JSON.stringify(config),
  }),
};

export default config;
// environment.ts
function getEnv<IsOptional extends boolean = false>(
  name: string,
  isOptional?: IsOptional
): Env<string, IsOptional> {
  let environment: Record<string, unknown>;

  if (process.env.STORYBOOK === 'true') {
    assert(
      process.env.STORYBOOK_ENVS !== undefined,
      `Missing required environment variable: STORYBOOK_ENVS`
    );
    environment = JSON.parse(process.env.STORYBOOK_ENVS);
  } else {
    environment = process.env;
  }

  const processValue = environment[`REACT_APP_${name}`];
  const windowValue = (window as any)[name];
  const value = processValue || windowValue; // prefer local development override

  if (!isOptional) {
    assert(
      value !== undefined && value !== `$${name}`,
      `Missing required environment variable: ${name}`
    );
  }

  return value;
}

Can we get an update on this issue?!

@israelKusayev Exactly my point. What manner of black magic happens when logging some key in an object yields its real value, but the object as a whole logs empty. How? How how how?

This really feels like bad practice hackery/trickery, so I would say the cure is worse than the disease, so to speak.

Confirming issue with Storybook 6.5.16 and 7.4.0.

My temp workaround is to set whatever I need in main.js:

module.exports = {
	// ...
	env: config => ({
		...config,
		REACT_FOOBAR: "whatever"
	})
};

It seems that in storybook 6.4.19 if your .env has:

REACT_APP_API_URL='/api'

and your code has

get(process.env.REACT_APP_API_URL)

the code actually executed is:

get('/api')

and so having code like:

process.env.REACT_APP_API_URL= '/api/test'

results in:

'/api' = '/api/test'

which doesn’t end well!

It also seems that if REACT_APP_API_URL is not set in the .env then setting and using process.env.REACT_APP_API_URL in the story works as expected.

If you add

module.exports = { env: () => ({}), /* other exported properties */ }

to the default exports of main.js you’ll get no imports from the .env and be able to configure them for each story as needed. This seems good to me as I would expect a story’s behaviour to be predictable and independent of the local .env especially if you have tests for your stories.