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 enable storybook on node/server? #4082

Closed
sibelius opened this issue Aug 27, 2018 · 23 comments
Closed

How to enable storybook on node/server? #4082

sibelius opened this issue Aug 27, 2018 · 23 comments

Comments

@sibelius
Copy link

I'd like to run storybook on a server project, so I can have a storybook of all emails that we send

the problem with run at is that some dependencies requires node dependencies like this error message:

Module not found: Error: Can't resolve 'fs' in

how can we solve this?
can we polyfill these?

@igor-dv
Copy link
Member

igor-dv commented Aug 27, 2018

You can mock anything using babel or webpack. But IMO the proper solution is to isolate UI out of any logic that is using state and of course file system.
Can you provide an example of the "email"?

@sibelius
Copy link
Author

email is just a react component, and we use renderToString to send in email.

the problem is that it can use any function of our server codebase, doing this we hit this problem

do you have any example of how to mock using babel or webpack?

@sibelius
Copy link
Author

The path I'm trying to follow is a custom webpack config

I've tried some approaches:

1 - use fs: 'empty':

module.exports = (storybookBaseConfig, configType) => {
  storybookBaseConfig.node = {
     fs: 'empty',
     global: true,
  };

  return storybookBaseConfig;
};

this gave me fs.readFileSync is not a function webpack

  1. try to target: 'node'
module.exports = (storybookBaseConfig, configType) => {
  storybookBaseConfig.target = 'node'

  return storybookBaseConfig;
};

then I've got global is not defined

Try to fix this, using DefinePlugin

new webpack.DefinePlugin({
      'global': {},
    }),

then I've got a require is not a function

@igor-dv
Copy link
Member

igor-dv commented Aug 30, 2018

let's say you have .storybook dir like this:

.storybook
  fsMock.js
  webpack.config.js

fsMock.js:

module.exports = {
  readFileSync: () => 'mocked file',
  // other things in fs that you are using
}

webpack.config.js:

const path = require('path');

module.exports = (storybookBaseConfig, configType) => {
  storybookBaseConfig.resolve.alias = {
    ...storybookBaseConfig.resolve.alias,
    'fs': path.resolve(__dirname, 'fsMock.js')
  };

  return storybookBaseConfig;
};

@sibelius
Copy link
Author

it worked well, tks

@sibelius
Copy link
Author

sibelius commented Dec 3, 2018

can we use WebpackNodeExternals https://github.com/liady/webpack-node-externals/issues with storybook?

@igor-dv
Copy link
Member

igor-dv commented Dec 3, 2018

What do you mean?

@sibelius
Copy link
Author

sibelius commented Dec 3, 2018

instead of using alias, why not use webpack-node-externals to not bundle anything inside node_modules

I've tried, but got require is not a function

@igor-dv
Copy link
Member

igor-dv commented Dec 3, 2018

Can you share an example of the usage?

@sibelius
Copy link
Author

sibelius commented Dec 3, 2018

const path = require('path');
const WebpackNodeExternals = require('webpack-node-externals');

module.exports = (storybookBaseConfig, configType) => {
  storybookBaseConfig.resolve.alias = {
    ...storybookBaseConfig.resolve.alias,
    'fs': path.resolve(__dirname, 'fsMock.js')
  };

  storybookBaseConfig.externals: [
    WebpackNodeExternals({
      whitelist: ['webpack/hot/poll?1000'],
    }),
  ],

  return storybookBaseConfig;
};

related to this https://github.com/entria/entria-fullstack/blob/master/packages/server/webpack/webpack.dev.js#L16

@igor-dv
Copy link
Member

igor-dv commented Dec 3, 2018

What is the error log by the way?

@miketdonahue
Copy link

webpack-node-externals is not meant to be used for the web (target: web). That is why you get the require error.

Seems like you have to mock fs.

@Drillibit
Copy link

Here is a better solution for it:
config.node = { fs: 'empty', tls: 'empty', net: 'empty', module: 'empty', console: true };

@ericsoco
Copy link

ericsoco commented May 23, 2019

Putting together answers from @igor-dv and @Drillibit, and bumping to Storybook v5+:

Add a Webpack config override as .storybook/webpack.config.js with the following content:

const path = require('path');

module.exports = async ({config, mode}) => {
  config.node = {fs: 'empty'};
  return config;
};

You can replace/add fs in config.node with any other modules you need to mock.

Docs on overriding Storybook's Webpack config here
Docs on Webpack's config.node here

@Vadorequest
Copy link

Vadorequest commented Jan 11, 2021

Slightly better, and documented version of .storybook/webpack.config.js:

/**
 * The doc doesn't really mention using webpack.config.js, but .storybook/main.js instead.
 *
 * Nevertheless, configuring the webpack.config.js seems to work fine.
 *
 * @param config
 * @param mode
 * @return {Promise<*>}
 * @see https://storybook.js.org/docs/react/configure/webpack
 * @see https://storybook.js.org/docs/react/configure/webpack#using-your-existing-config
 */
module.exports = async ({
  config,
  mode,
}) => {
  /**
   * Fixes npm packages that depend on `fs` module, etc.
   *
   * E.g: "winston" would fail to load without this, because it relies on fs, which isn't available during browser build.
   *
   * @see https://github.com/storybookjs/storybook/issues/4082#issuecomment-495370896
   */
  config.node = {
    fs: 'empty',
    tls: 'empty',
    net: 'empty',
    module: 'empty',
    console: true,
  };

  return config;
};

See full PR, adding Storybook to Next.js (TypeScript): UnlyEd/next-right-now#251

@sibelius
Copy link
Author

config.node = {
    fs: 'empty',
    tls: 'empty',
    net: 'empty',
    module: 'empty',
    console: true,
  };

does not work with webpack 5, is there a workaround for webpack 5?

@pliao1996
Copy link

Can anybody explain why it works?

@sibelius
Copy link
Author

it replaces the module to something else

@Vadorequest
Copy link

@PeiLiao Instead of trying to load fs, it will mock it (empty), thus not trying to load a server-side library that doesn't exist on the browser, thus not crashing.

That's my own understanding, not an authoritative answer.

@natterstefan
Copy link

config.node = {
    fs: 'empty',
    tls: 'empty',
    net: 'empty',
    module: 'empty',
    console: true,
  };

does not work with webpack 5, is there a workaround for webpack 5?

Hi @sibelius,

I had the same issue until I discovered this note in the migration guide:

If you are using something like node.fs: 'empty' replace it with resolve.fallback.fs: false.

I ended up adding this to my main.js:

  /**
     * Fixes issue with `next-i18next` and is ready for webpack@5
     * @see https://github.com/isaachinman/next-i18next/issues/1012#issuecomment-792697008
     * @see https://github.com/storybookjs/storybook/issues/4082#issuecomment-758272734
     * @see https://webpack.js.org/migrate/5/
     */
    config.resolve.fallback = {
      fs: false,
      tls: false,
      net: false,
      module: false,
      path: require.resolve('path-browserify'),
    }

@sibelius
Copy link
Author

I did a lot of fallback and it is compiling now

the only problem is process is not defined

@julio-akomplice
Copy link

I ended up adding this to my main.js:

/**
* Fixes issue with next-i18next and is ready for webpack@5
* @see i18next/next-i18next#1012 (comment)
* @see #4082 (comment)
* @see https://webpack.js.org/migrate/5/
*/
config.resolve.fallback = {
fs: false,
tls: false,
net: false,
module: false,
path: require.resolve('path-browserify'),
}

It will end up looking like this

import type { StorybookConfig } from "@storybook/nextjs";

const config: StorybookConfig = {
  stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
  addons: [
  ...
  ],
 ...
  webpackFinal: async (config) => {

    if (config?.resolve?.fallback) {
      config.resolve.fallback = {
        ...config.resolve.fallback,
        fs: false,
        tls: false,
        net: false,
        module: false,
        dns: false,
        child_process: false,
        // path: require.resolve("path-browserify"), // Adding `path-browserify` as a replacement for `path`
      };
    }

    return config;
  },
};
export default config;

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

No branches or pull requests

9 participants