storybook: Webpack5 + type: module = require is not defined

Describe the bug A barebones example with type: module and Webpack5 results in “require is not defined” error in browser:

Uncaught ReferenceError: require is not defined
    js generated-stories-entry.js:3
    Webpack 7
        __webpack_require__
        __webpack_exec__
        <anonymous>
        O
        <anonymous>
        webpackJsonpCallback
        <anonymous>

To Reproduce

git clone git@github.com:arty-name/storybook-webpack5-module.git && yarn install && npx start-storybook

Alternatively these simple steps:

  1. Create package.json containing { "type": "module" }
  2. yarn add react react-dom
  3. yarn add --dev @storybook/react @storybook/builder-webpack5 @storybook/manager-webpack5
  4. Create .storybook/main.cjs containing module.exports = { core: { builder: 'webpack5' }, stories: ['../test.stories.js'] }
  5. Create test.stories.js containing:
export default { title: 'Story' }
export function Story() {
  return 'Pass!'
}
  1. npx start-storybook

System

Environment Info:

  System:
    OS: Linux 5.8 Ubuntu 21.04 (Hirsute Hippo)
    CPU: (8) x64 11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz
  Binaries:
    Node: 16.3.0 - /usr/bin/node
    Yarn: 1.22.5 - /usr/bin/yarn
    npm: 7.15.1 - /usr/bin/npm
  Browsers:
    Chrome: 91.0.4472.114
    Firefox: 89.0.1
  npmPackages:
    @storybook/builder-webpack5: ^6.3.0 => 6.3.0 
    @storybook/manager-webpack5: ^6.3.0 => 6.3.0 
    @storybook/react: ^6.3.0 => 6.3.0 

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 39
  • Comments: 52 (32 by maintainers)

Commits related to this issue

Most upvoted comments

If you want this fixed, please upvote by adding a 👍 to the issue description. We use this to help prioritize!

The .storybook/package.json workaround works indeed, thanks!

I looked into this a bit, and it’s a bit weird since the file as checked in to Git actually does use the import syntax. But it gets transpiled to CommonJS format in the cjs build of storybook, which I suppose is being used because we currently have to use .storybook/main.cjs instead of just .storybook/main.js.

Not quite sure what the proper solution is for this. One possibility is to maintain two copies of virtualModuleStory.template.js, one for CJS and one for ESM, and then keeping them from being processed during the build, would work. Then the template code could use packageJson.type to determine which template to use. There might be a better solution though that lets webpack convert the template or something. Or probably best would be if Node would just run the ESM build of storybook.

EDIT: Actually it’s more complicated than this since the template files also reference module and are populated with code that uses require.context. In the end, I found it worked to create a .storybook/package.json file with the contents just {}, nothing else. This seems to put webpack in a mode where it’s tolerant of both ES6 import/export and CommonJS require.

I have a particularly horrible workaround based on https://github.com/storybookjs/storybook/issues/14877#issuecomment-973831859 . In the main.*(c)js file you can change the VirtualModulesPlugin instance to add the .cjs suffix to the appropriate files. This is only likely to work for a specific version of Storybook so I’d suggest that you pin to a specific version if you’re going to use it.

const path = require("path");

const replaceFileExtension = (filePath, newExtension) => {
  const { name, root, dir } = path.parse(filePath);
  return path.format({
    name,
    root,
    dir,
    ext: newExtension,
  });
};

module.exports = {
  // Your other configuration.
  core: {
    builder: "webpack5",
  },
  webpackFinal: (config) => {
    // Find the plugin instance that needs to be mutated
    const virtualModulesPlugin = config.plugins.find(
      (plugin) => plugin.constructor.name === "VirtualModulesPlugin"
    );

    // Change the file extension to .cjs for all files that end with "generated-stories-entry.js"
    virtualModulesPlugin._staticModules = Object.fromEntries(
      Object.entries(virtualModulesPlugin._staticModules).map(
        ([key, value]) => {
          if (key.endsWith("generated-stories-entry.js")) {
            return [replaceFileExtension(key, ".cjs"), value];
          }
          return [key, value];
        }
      )
    );

    // Change the entry points to point to the appropriate .cjs files
    config.entry = config.entry.map((entry) => {
      if (entry.endsWith("generated-stories-entry.js")) {
        return replaceFileExtension(entry, ".cjs");
      }
      return entry;
    });
    return config;
  },
};

I’ve tested this with Storybook 6.4.9. It may work for other versions.

Still seeing this as an issue in 6.5.15 with storyStoreV7 true 😕 with build-storybook

@jpzwarte I think the reason that the .storybook/package.json solution doesn’t work any more in 6.4 is because we “moved” the location of the virtual module created by the file referenced in @dimfeld’s comment to the root of the project, rather than the .storybook directory.

Let me see if I understand this issue correctly:

  1. If you specify your project as { "type": "module" }, webpack doesn’t want to see require() any more
  2. The entry point(s) we create on your behalf as SB (generated from virtualModuleStory.template.js, which was compiled to CJS) makes use of require()
  3. In 6.3, as that file was “put” in ./.storybook, if you put a package.json in there declaring a non “module” package, WP is happy again.

I wonder if there is an alternate way to tell WP that the bundle we are create is (to begin with at least) CJS, not ESM, so it doesn’t look in ./package.json:type to figure that out?

We would like to properly sort out this ESM/CJS business in 6.5 so it is possible to actually just use ESM everywhere.

I’ve updated the example repo from above and updated the packages to 6.4.0-beta.33 and added the empty package.json, but I still get the require is undefined error. cc @shilman

Yee-haw!! I just released https://github.com/storybookjs/storybook/releases/tag/v6.5.0-alpha.23 containing PR #16727 that references this issue. Upgrade today to the @next NPM tag to try it out!

npx sb upgrade --prerelease

Closing this issue. Please re-open if you think there’s still more to do.

Would it make sense to remove the “has workaround” label meanwhile?

Hi! I ran into this issue and none of the workarounds worked for me so I tried npx sb upgrade --prerelease but I’m still seeing the same error whenever I try to add a webpack plugin in main.js

ReferenceError: require is not defined
    at Object.regenerator-runtime/runtime (main.iframe.bundle.js:2217:1)
    at __webpack_require__ (runtime~main.iframe.bundle.js:854:30)

Thanks to @jpzwarte for the fix here!

It works! Thank you very much for fixing it! 🙇

After a few hours of fiddling with it, I found an elegant solution. I’ve created a PR https://github.com/storybookjs/storybook/pull/17108 that replaces require with import.

Currently, the extensions are not ensured, so you’ll probably still need to do some replacements. Here’s mine:

    webpackFinal: async (config, { configType }) => {
        const virtualModulesPlugin = config.plugins.find(
            (plugin) => plugin.constructor.name === "VirtualModulesPlugin"
        );

        const storybookConfigEntryKey = Object.keys(virtualModulesPlugin._staticModules)
            .find((key) => key.endsWith('storybook-config-entry.js'))

        virtualModulesPlugin._staticModules[storybookConfigEntryKey] = virtualModulesPlugin._staticModules[storybookConfigEntryKey]
            .replace('config\'', 'config.js\'');

        return config;
    }

@tmeasday I created a PR last year in July to partially fix this issue, but it was never merged: https://github.com/storybookjs/storybook/pull/18620. I will do some quick testing in the afternoon to determine whether this is still a thing.

@tmeasday Thank you for checking! My projects seem to run fine with the SB7 prerelease, and I even could remove the workarounds. Well done!

@benjambles thanks for the suggestion, that plugin does indeed solve the issue. so as it stands i can get storybook/webpack5/esm/ts to work with the workarounds mentioned in this issue. i do hope for the future we won’t need to provide these workarounds.

to summarise:

  • rename main.js to main.cjs
  • package.json with {} contents within .storybook
  • this workaround to fix the require is not defined error
  • resolve-typescript-plugin webpack plugin to allow fully specified paths in typescript (required for ESM support)

here’s my updated repo showing the working workarounds: https://github.com/badsyntax/storybook-webpack5-esm-typescript

Just don’t use the new on-demand architecture as it will break the workaround - the following configuration:

   features: {
        storyStoreV7: true,
    },

@philippone that’s intentional, and that’s how ESM support works in TypeScript. See: https://www.typescriptlang.org/docs/handbook/esm-node.html#type-in-packagejson-and-new-extensions (You can also run npm run build in the repo to show that it does work with TypeScript, it just doesn’t work with StoryBook.)

If i remove the fully specified path (including the extension), then my library is not ESM compatible and Node will throw errors, so i can’t remove the extension.

@tmeasday I’ve tested it by changing node_modules/@storybook/builder-webpack5/dist/cjs/preview/iframe-webpack.config.js in place and the only thing that is required is to rename generated-stories-entry.js to generated-stories-entry.cjs.

I can confirm that with that change, the require is undefined error is gone!