Storybook: Add loader for .module.css to load CSS modules

Created on 13 Mar 2019  ·  18Comments  ·  Source: storybookjs/storybook

Is your feature request related to a problem? Please describe.

I'd love to use CSS Modules together with Storybook but the default config won't let me do that out of the box.

Describe the solution you'd like

I'd like to have another loader added to the storybookBase webpack config which checks for /\.module.css/ and uses css-loader with the modules option set to true. At the same time /\.module.css/ must be added as exclude pattern to the default css loader config.

Describe alternatives you've considered

I have added a really super ugly (and error prone) hack to filter out the css from Storybook's default webpack config and replace it with my own css loading mechanism:

https://github.com/wiremore/grid/blob/master/config/webpack.config.js/storybook.js#L7-L9

Are you able to assist bring the feature to reality?

I did, kind of. See my hack above. Feel free to adopt. If you point me to the webpack config that is used to generate the stories I could also implement it myself. It's probably not a breaking change.

Additional context

Both Gatsby and Create React App use .module.css to support CSS Modules without any further configuration, hacks or workarounds:

babel / webpack feature request has workaround inactive

Most helpful comment

I'm currently setting CSS Modules in a shared component library written in TypeScript (apps consuming this library already use CSS Modules, but the shared library doesn't) and I ended up with this:

  config.module.rules.find(
    rule => rule.test.toString() === '/\\.css$/',
  ).exclude = /\.module\.css$/;

  config.module.rules.push({
    test: /\.module\.css$/,
    use: [
      'style-loader',
      'css-modules-typescript-loader',
      {
        loader: 'css-loader',
        options: {
          modules: true,
        },
      },
    ],
  });

It's a work in progress for a few reasons which I assume are on our end (e.g. the build fails the first time because *.d.ts.css files do not initially exist as we are not committing them in, but they exist upon re-builds), but a preset and/or improvement to the Storybook Wepback config would be very welcome. What would help:

  • Preset or not, the base config could exclude *.module.css files, that way the first block is not necessary (which would be necessary in the preset as well)
  • The preset would remove the dependency on style-loader, css-loader, and css-modules-typescript-loader on our end, which is always welcome.

Unfortunately, our Webpack config does something additional that the preset would not do (see below) so depending on the capabilities to extend / hook into / compose presets, we would or would not be able to use it. If not, then excluding *.module.css files from the base config would already help!

Thanks for your hard work! 👏


If anyone is interested about what we do in the Webpack config:

config.plugins.push(
  new DefinePlugin({
    'process.env.GIT_REVISION': JSON.stringify(
      execSync('git describe')
        .toString()
        .trim(),
    ),
  }),
);

This exposes a human-readable git revision to the build, which we then use for a couple things, such as setting a link to the component library repo using the theme brandUrl:

brandUrl: `https://github.com/my-org/my-repo/tree/${process.env.GIT_REVISION}`,

That offers a quick way to know the current version one is looking at, among other things.

All 18 comments

Hey @manuelbieh - one particularly simple way to get css modules working in your storybook is to add react scripts to the project. Storybook should then pick up on your create-react-app config:

image

Doing so enables .module.css files without any further configuration.

With that being said, adding react-scripts effectively adds dependency weight under the hood, even if you're not using it for anything else. It's not perfect, but it does _just work_ to enable CSS modules.

Caveat: I've found this to be _almost_ perfect for my needs building a gatsby/storybook project for thegymnasium.com. The unfortunate problem I'm having at the moment is that Gatsby and Storybook process modules differently - you can see my dilemma here.

Hi everyone! Seems like there hasn't been much going on in this issue lately. If there are still questions, comments, or bugs, please feel free to continue the discussion. Unfortunately, we don't have time to get to every issue. We are always open to contributions so please send us a pull request if you would like to help. Inactive issues will be closed after 30 days. Thanks!

@manuelbieh This is a great feature. However, I don't want to add any more dependencies to Storybook (in fact, I'd like to start removing them!). Last year @igor-dv developed a feature called "presets" which is perfect for this. It's a solution that can modify your webpack/babel configs and more by adding a single line to presets.js. It's used internally by all of our different "apps" (React, Vue, Angular, etc.) but we're still documenting it for public use. I'd like to circle back with you as soon as its documented and see if you might be able to implement "preset-css-modules" as one of the first Storybook presets.

Hi everyone! Seems like there hasn't been much going on in this issue lately. If there are still questions, comments, or bugs, please feel free to continue the discussion. Unfortunately, we don't have time to get to every issue. We are always open to contributions so please send us a pull request if you would like to help. Inactive issues will be closed after 30 days. Thanks!

Hey there, it's me again! I am going close this issue to help our maintainers focus on the current development roadmap instead. If the issue mentioned is still a concern, please open a new ticket and mention this old one. Cheers and thanks for using Storybook!

@manuelbieh FYI we merged the presets doc and released our first preset.

https://github.com/storybooks/storybook/pull/5333
https://www.npmjs.com/package/@storybook/preset-typescript

Any interest in doing this for CSS modules? If so, I'd def feature it as part of the push towards presets.

Hi everyone! Seems like there hasn't been much going on in this issue lately. If there are still questions, comments, or bugs, please feel free to continue the discussion. Unfortunately, we don't have time to get to every issue. We are always open to contributions so please send us a pull request if you would like to help. Inactive issues will be closed after 30 days. Thanks!

Hey there, it's me again! I am going close this issue to help our maintainers focus on the current development roadmap instead. If the issue mentioned is still a concern, please open a new ticket and mention this old one. Cheers and thanks for using Storybook!

@manuelbieh Any interest in pairing on a CSS modules preset? I'm familiar with the presets side and it sounds like you're familiar with the CSS modules side. I bet we could get something going in 30m if you're interested.

@shilman Hey, sure. Just let me know how I can help. I just googled the concept of presets in Storybook, looks awesome. I guess the preset will probably look similar to what I've already done here, right?

https://github.com/manuel-bieh/ui/blob/master/config/webpack.config.js/storybook.js

@manuelbieh Yeah, we can probably just copy and paste from that. Want to hop on our discord and pair on it? Or if you can explain to me how to use it / test it, I can probably just whip something out.

I'm currently setting CSS Modules in a shared component library written in TypeScript (apps consuming this library already use CSS Modules, but the shared library doesn't) and I ended up with this:

  config.module.rules.find(
    rule => rule.test.toString() === '/\\.css$/',
  ).exclude = /\.module\.css$/;

  config.module.rules.push({
    test: /\.module\.css$/,
    use: [
      'style-loader',
      'css-modules-typescript-loader',
      {
        loader: 'css-loader',
        options: {
          modules: true,
        },
      },
    ],
  });

It's a work in progress for a few reasons which I assume are on our end (e.g. the build fails the first time because *.d.ts.css files do not initially exist as we are not committing them in, but they exist upon re-builds), but a preset and/or improvement to the Storybook Wepback config would be very welcome. What would help:

  • Preset or not, the base config could exclude *.module.css files, that way the first block is not necessary (which would be necessary in the preset as well)
  • The preset would remove the dependency on style-loader, css-loader, and css-modules-typescript-loader on our end, which is always welcome.

Unfortunately, our Webpack config does something additional that the preset would not do (see below) so depending on the capabilities to extend / hook into / compose presets, we would or would not be able to use it. If not, then excluding *.module.css files from the base config would already help!

Thanks for your hard work! 👏


If anyone is interested about what we do in the Webpack config:

config.plugins.push(
  new DefinePlugin({
    'process.env.GIT_REVISION': JSON.stringify(
      execSync('git describe')
        .toString()
        .trim(),
    ),
  }),
);

This exposes a human-readable git revision to the build, which we then use for a couple things, such as setting a link to the component library repo using the theme brandUrl:

brandUrl: `https://github.com/my-org/my-repo/tree/${process.env.GIT_REVISION}`,

That offers a quick way to know the current version one is looking at, among other things.

@astorije thanks for the solution, we don't have Typescript, but SASS and css modules and I'm using the classes from css module to add them to the components, how would I adapt your code to our setup? so far I have

config.module.rules.push({
    test: /\.scss$/,
    use: ['style-loader', 'css-loader', 'sass-loader'],
    include: path.resolve(__dirname, '../'),
    exclude: /\.module\.scss$/
  });
config.module.rules.push({
    test: /\.module\.scss$/,
    use: [
      'style-loader',
      {
        loader: 'css-loader',
        options: {
          modules: true,
          importLoaders: 1,
          localIdentName: '[path]__[name]___[local]'
        }
      },
      'sass-loader'
    ]
  });

I have SASS working, SOME css modules working (I use them as SCSS files) but if there's a SCSS module overriding a default material-ui rule (I'm using gatsby and material-ui) then the module.scss file gets overridden by the material-ui rule :(

I pretty much used the same technique, just in an immutable way:

module.exports = async ({ config }) => {
  const rules = config.module.rules.map(rule => {
    if (rule.test.toString() !== '/\\.css$/') {
      return rule;
    }

    const use = rule.use.map(u => {
      const { loader } = u;

      if (!loader || !loader.includes('/css-loader/')) {
        return u;
      }

      const options = {
        ...u.options,
        modules: true
      };

      return {
        ...u,
        options
      };
    })

    return {
      ...rule,
      use
    };
  })

  return {
    ...config,
    module: {
      ...config.module,
      rules,
    }
  };
};

Loop over rules, find rule applied to .css files and update the option to add modules: true to css-loader

@astorije
damn, you saved my 2 hours, thx

I solved it like that in my ./storybook/main.js

module.exports = {
  stories: ['../stories/**/*.stories.js'],
  addons: ['@storybook/addon-actions', '@storybook/addon-links'],
  webpackFinal: async (config, { configType }) => {

    // get index of css rule
    const ruleCssIndex = config.module.rules.findIndex(rule => rule.test.toString() === '/\\.css$/');

    // map over the 'use' array of the css rule and set the 'module' option to true
    config.module.rules[ruleCssIndex].use.map(item => {
      if (item.loader && item.loader.includes('/css-loader/')) {
        item.options.modules = {
          mode: 'local',
          localIdentName: configType === 'PRODUCTION' ? '[local]--[hash:base64:5]' : '[name]__[local]--[hash:base64:5]',
        };
      }
      return item;
    })

    // Return the altered config
    return config;
  }
};

@freesh thanks, though i did something different it helped. in my case I have been struggling for ages and I just removed all the default storybook webpack CSS stuff and used (copy/paste mostly) what Create React App gave me when I ejected, manually:

module.exports = {
  stories: ['../storybook/**/*.stories.tsx'],
  addons: ['@storybook/addon-actions', '@storybook/addon-links'],
  webpackFinal: async config => {

     config.module.rules = config.module.rules.filter(rule => rule.test.toString() !== '/\\.css$/')

      config.module.rules.push({oneOf: [
        { ... }
      ]})

also note that I know very little about webpack but running yarn storybook --debug-webpack was much better than nothing!

@freesh solution was still required for me to use Storybook outside a create react app. Thanks. I had style-loader and css-loader with typical css:modules true but it seems that the configuration is ignored in this sort of environment. I was literally only creating components in this project so the humungous create react app seemed like overkill. I don't think this should take this kind of webpack hijacking personally.

It's interesting too that it appears like https://github.com/storybookjs/presets/tree/master/packages/preset-scss might provide css module too. Seems too bad that it's scss specific. I'm using css-loader modules: true without scss to keep my CSS lightweight until/unless my project absolutely needs a preprocessor (in which case postcss is most likely next thing I'd add).

Lastly, I think it's also a Storybook noob thing (at least for me) since I just also encountered:
https://storybook.js.org/docs/react/configure/webpack#extending-storybooks-webpack-config

which seems to perfectly tell you how to override this manually which brings me around full circle that this is not really a bug or missing feature ¯_(ツ)_/¯ but:

https://storybook.js.org/docs/react/get-started/install if you see the:

sb init is not made for empty projects

It would maybe be nice to point out that Webpack can be overriden by looking at #extending-storybooks-webpack-config and a similar link to rollup if it's overridable. I think I/we are finding ourselves stubbornly trying anyway and then finding this issue :)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

tirli picture tirli  ·  3Comments

rpersaud picture rpersaud  ·  3Comments

wahengchang picture wahengchang  ·  3Comments

sakulstra picture sakulstra  ·  3Comments

Jonovono picture Jonovono  ·  3Comments