storybook: Vite + Storybook 6.3 + Jest: "global is not defined"

Describe the bug I am trying to get Vite + Storybook 6.3 working with some existing stories. These stories use jest.fn() as mocks. I previously added this in preview.js to get jest.fn() working within storybook:

import jest from "jest-mock";
window.jest = jest;

But when using the Vite integration I am now getting an error when running storybook:

Uncaught ReferenceError: global is not defined
    at ../node_modules/jest-mock/build/index.js (index.js:948)
    at __require2 (chunk-IHTDASF6.js?v=3b66b68d:17)
    at dep:jest-mock:1

To Reproduce I am trying vite with storybook 6.3 and npm 7.19 workspaces. My repro repo is here: https://github.com/adiun/vite-monorepo:

  1. Ensure you have npm 7.19 installed for the workspaces functionality
  2. In the root, run npm i
  3. In the app folder, run npm run storybook.
  4. In the browser console for storybook you will see:
Uncaught ReferenceError: global is not defined
    at ../node_modules/jest-mock/build/index.js (index.js:948)
    at __require2 (chunk-IHTDASF6.js?v=3b66b68d:17)
    at dep:jest-mock:1

System

Environment Info:

  System:
    OS: macOS 11.3
    CPU: (16) x64 Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz
  Binaries:
    Node: 14.16.0 - /usr/local/bin/node
    npm: 7.19.0 - /usr/local/bin/npm
  Browsers:
    Chrome: 91.0.4472.114
    Edge: 91.0.864.48
    Firefox: 88.0.1
    Safari: 14.1
  npmPackages:
    @storybook/addon-docs: 6.3.0 => 6.3.0 

About this issue

  • Original URL
  • State: open
  • Created 3 years ago
  • Reactions: 2
  • Comments: 17 (11 by maintainers)

Commits related to this issue

Most upvoted comments

Following up on this… it’s actually a bad idea to redefine global like this in main.js:

define: {
  ...config.define,
  global: "window",
},

The Vite docs even mention this.

The problem is that when running a build-storybook, any references to global in libraries like @storybook/client-logger will be rewritten to window and it will break since it’s running in node.

So I removed this code in main.js to get build-storybook working and added this hack to .storybook/preview-head.html to get start-storybook working:

<script>
  window.global = window;
</script>

main.js:

module.exports = {
  stories: ["../src/**/*.stories.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
  addons: ["@storybook/addon-links", "@storybook/addon-essentials"],
  core: {
    builder: "storybook-builder-vite",
  },
  async viteFinal(config) {
    return {
      ...config,
      esbuild: {
        ...config.esbuild,
        jsxInject: `import React from 'react'`,
      },
      rollupOptions: {
        ...config.rollupOptions,
        // Externalize deps that shouldn't be bundled
        external: ["react", "react-dom"],
        output: {
          // Global vars to use in UMD build for externalized deps
          globals: {
            react: "React",
            "react-dom": "ReactDOM",
          },
        },
      },
    };
  },
};

preview.js:

import * as jest from "jest-mock";
window.jest = jest;

export const parameters = {
  actions: { argTypesRegex: "^on[A-Z].*" },
  controls: {
    matchers: {
      color: /(background|color)$/i,
      date: /Date$/,
    },
  },
};

preview-head.html:

<script>
  window.global = window;
</script>

Alright, I finally fixed this… jest-mock doesn’t explicitly provide a default export so in storybook’s preview.js this is what I have:

import * as jest from "jest-mock";
window.jest = jest;

This combined with assigning global to window in main.js fixed this issue.

preview.js

import * as jest from "jest-mock";
window.jest = jest;

export const parameters = {
  actions: { argTypesRegex: "^on[A-Z].*" },
  controls: {
    matchers: {
      color: /(background|color)$/i,
      date: /Date$/,
    },
  },
};

main.js

module.exports = {
  stories: ["../src/**/*.stories.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
  addons: ["@storybook/addon-links", "@storybook/addon-essentials"],
  core: {
    builder: "storybook-builder-vite",
  },
  async viteFinal(config) {
    return {
      ...config,
      define: {
        ...config.define,
        global: "window",
      },
      esbuild: {
        ...config.esbuild,
        jsxInject: `import React from 'react'`,
      },
    };
  },
};

Updated my repo too

Maybe we should document this in the README - a section about troubleshooting and potential workarounds? We could inject window.global = window into the iframe, generated by the builder itself, but I suspect that we may encounter edge cases where that’s not desirable? I guess the problem here is that Jest is written to be run in Node.js, where global is a variable. I suppose the “correct” solution would be to fix Jest, so it uses globalThis?

For anyone using yarn, overriding the jest-mock version using resolutions

Or with overrides in npm, confirmed working.

Yes, I’ve been working on upgrading jest in the storybook packages, but it’s a bit complex because of the way that jest types are global and the interactions with jest-dom. I’m waiting for some feedback on https://github.com/testing-library/jest-dom/pull/483.

For anyone using yarn, overriding the jest-mock version using resolutions in your package.json might work for you to get the latest version of jest-mock which doesn’t have the global issue.

"resolutions": {
    "jest-mock": "^28.1.0"
  },

I’m confused, if this is the fix, couldn’t we open a PR here in storybook to upgrade the dependency

├─ @storybook/addon-interactions@npm:7.0.0-alpha.41
│  └─ jest-mock@npm:27.4.0 (via npm:^27.0.6)

Or what else are we missing?

For anyone using yarn, overriding the jest-mock version using resolutions in your package.json might work for you to get the latest version of jest-mock which doesn’t have the global issue.

"resolutions": {
    "jest-mock": "^28.1.0"
  },

Nuxt tries to cover a few distribution and render channels (e.g. service workers) and for this reason adds its own polyfills, but global in a cjs file is not yet supported https://github.com/nuxt/nuxt.js/issues/12830. One can workaround this issue by an adding custom alias that essentially redirects it to globalThis, see the instructions at https://github.com/nuxt-community/storybook/pull/385. But it would be nice if this workaround wouldn’t be needed at all and storybook would rely directly on the more modern globalThis.

Yes, my use case is completely independent of jest.