react-static: CSS Modules no longer work

As far as I can tell, #574 may have broken the usage of CSS Modules in React-Static. I’ve upgraded to the latest version and noticed that import styles from 'Foo.scss' no longer works as expected. If you attempt to reference a class name on the styles object, it results in undefined.

Environment

  1. react-static -v: 5.8.2 (first non-working release, last working release is 5.8.1)
  2. node -v: 8.9.3
  3. yarn --version or npm -v: 5.6.0
  4. Operating system: macOS 10.13.4

Steps to Reproduce the problem

  1. Go to examples/sass
  2. npm i
  3. npm i react-static@5.8.2 (first non-working release)
  4. Open static.config.js and add modules: true option on css-loader
  5. Open App.js and replace import './app.scss' with import styles from './app.scss'
  6. Add console.log(styles.content) below the app.scss import
  7. Run npm stage
  8. Notice that the console.log call results in undefined

Expected Behavior

If you did the steps outlined above on 5.8.1, it would work. You’d just need to remove the if condition surrounding ExtractTextPlugin.extract call in static.config.js (which was introduced to conform to changes in #574).


Let me know if I’m missing some trivial configuration that would allow me to use CSS Modules. Otherwise, changes in #574 need to be revisited.

/cc @lottamus

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 2
  • Comments: 65 (9 by maintainers)

Most upvoted comments

@tannerlinsley @ScriptedAlchemy Please correct me if I’m wrong on all of this, but it’s late and I’ve been looking at the internals of my node_modules for too long to maintain any sanity at this point. This is going to be a long one.

This issue should be reopened - issue still persists in both 5.x and 6.x branches. The PR that broke CSS modules is indeed https://github.com/nozzle/react-static/pull/574. Explanation following below, but first:

TL;DR: Workaround until there’s an official fix:

v5.x

  • Adjust your custom webpack config to always use ExtractCssChunks or ExtractTextPlugin and always add the plugin:
    -// Don't extract css to file during node build process
    -if(stage !== 'node') {
      loaders = ExtractCssChunks.extract({
        fallback: {
          loader: 'style-loader',
          options: {
            sourceMap: false,
            hmr: false,
          },
        },
        use: loaders,
      })
    -}
    
    +if (stage === 'node') {
    +  config.plugins.push(new ExtractCssChunks());
    +}
    
  • Same strategy seems to work for both ExtractTextPlugin and ExtractCssChunks, just different APIs obviously - make sure to tailor this to your webpack config.

v6.x

  • Adjust your custom webpack config to always use ExtractCssChunks and always add the plugin:
    if (stage === 'dev') {
      // Dev
      loaders = [styleLoader, cssLoader, sassLoader]
    } else if (stage === 'node') {
      // Node
    - // Don't extract css to file during node build process
    - loaders = [cssLoader, sassLoader]
    + loaders = [ExtractCssChunks.loader, cssLoader, sassLoader]
    + config.plugins.push(new ExtractCssChunks({
    +   filename: '[name].[chunkHash:8].css',
    +   chunkFilename: '[id].[chunkHash:8].css',
    + }));
    } else {
      // Prod
      loaders = [ExtractCssChunks.loader, cssLoader, sassLoader]
    }
    
    config.module.rules[0].oneOf.unshift({
      test: /\.s(a|c)ss$/,
      use: loaders,
    })
    

Something else?

  • If your configuration is significantly different from the example SASS ones, the same concepts should apply but you might want to read the details below to figure out how to fix it for yourself.

Explanation & details

Here’s how the CSS module pipeline usually works for JSX:

Say you have these two files (I’m using SCSS to use an unrelated fresh asset pipeline, but the same concepts apply if replacing the default css loader with a css modules enabled one manually):

// Home.scss
.Home {
  color: red;
}
// Home.js
import React from 'react';
import styles from './Home.scss';

console.log(styles);

export default function Home() {
  return (
    <div className={styles.Home}>
      Home
    </div>
  );
}

This is the configuration we’ll be using (slightly adjusted from the sass example one). NOTE: the only difference is the modules: true, option on css-loader:

webpack: (config, { defaultLoaders, stage }) => {
    let loaders = []

    if (stage === 'dev') {
      loaders = [{ loader: 'style-loader' }, { loader: 'css-loader' }, { loader: 'sass-loader' }]
    } else {
      loaders = [
        {
          loader: 'css-loader',
          options: {
            importLoaders: 1,
            minimize: stage === 'prod',
            sourceMap: false,
            modules: true, // <- the only difference is here
          },
        },
        {
          loader: 'sass-loader',
          options: { includePaths: ['src/'] },
        },
      ]

      if (stage !== 'node') {
        loaders = ExtractCssChunks.extract({
          fallback: {
            loader: 'style-loader',
            options: {
              sourceMap: false,
              hmr: false,
            },
          },
          use: loaders,
        })
      }
    }

    config.module.rules = [
      {
        oneOf: [
          {
            test: /\.s(a|c)ss$/,
            use: loaders,
          },
          defaultLoaders.cssLoader,
          defaultLoaders.jsLoader,
          defaultLoaders.fileLoader,
        ],
      },
    ]
    return config
  },

Before https://github.com/nozzle/react-static/pull/574

For 5.x, using the example configuration:

  1. Use css-loader with modules: true option, webpack will generate the following webpack result module:
/***/ }),
/* 133 */
/***/ (function(module, exports, __webpack_require__) {

exports = module.exports = __webpack_require__(134)(false);
// imports


// module
exports.push([module.i, "._1KGCDNoWpJiH3oe8eoX-{color:red}", ""]);

// exports
exports.locals = {
	"Home": "_1KGCDNoWpJiH3oe8eoX-"
};
  1. Use ExtractTextPlugin or ExtractCssChunks to get the exports.locals directly. If you add that plugin, this will result in the following:
/***/ }),
/* 133 */
/***/ (function(module, exports) {

// removed by extract-text-webpack-plugin
module.exports = {"Home":"_1KGCDNoWpJiH3oe8eoX-"};

/***/ })
  1. We can see the correct exports have been produced and CSS modules work as intended - perfect. This is how it used to work before https://github.com/nozzle/react-static/pull/574. The only side effect is we’re building more css files than needed since we’re running this plugin twice.

After https://github.com/nozzle/react-static/pull/574

By not including ExtractTextPlugin or ExtractCssChunks in the node stage, during bundle step the static.[hash].js file does NOT have the ExtractTextPlugin or ExtractChunksPlugin applied and we’re stuck with the result from Step 1 above - hence css modules don’t work. Note that the prod step result still looks and works fine but since the HTML is prebuilt using the node step output, the result is that none of the class names are applied (they’re all accessible to components via styles.locals.<className> rather than styles.<className>

6.x ?

There’s essentially the same problem, except we’re using newer versions of everything so the APIs are a bit different. Core of the issue is still the same - we’re not using ExtractCssChunks plugin for the node stage which means that css modules built with css-loader are not getting properly transformed for HTML generation.

Solutions?

Considering we don’t want to be building duplicate CSS files during the node step, we still need a way to generate and extract the correct locals for CSS modules. I’ve posted workarounds at the top but those are fairly dirty and might break when updating React Static. The way I can see it there are a few options here:

  1. Add ExtractCssChunks plugin back into the plugins for the node stage. This seems to work fine and the only issue I could find is the unused css files.
  2. Add a “dry-run” config option to ExtractCssChunks that would just transform the webpack modules but not actually output any CSS. Seems to be going slightly against the idea since the plugin is not actually necessary for this.
  3. Create a lightweight loader to transform the css-loader result into just the locals - kinda like style-loader but without the actual injecting into the DOM, just this transform basically:
if(content.locals) module.exports = content.locals;

CSS modules and React Static

On a side note, considering the adoption rate of CSS modules, it would be great to have more support for them. I can see two options here and I’m willing to work on both:

  1. Support CSS modules out of the box via a configuration option
  2. Add an example for a custom webpack config to add the support

Which one would align better with the direction of React Static @tannerlinsley?

Sounds like things got a little heated for a bit. Given the conversation I’d like to echo and clarify some things from our contribution guidelines:

  • This is open source, not a business, but that doesn’t mean it’s free. It’s an exchange of knowledge and support to make great tools accessible to the world.
  • React Static is developed and maintained for free by a number of very generous people. Thank you all for the help!
  • GitHub and the contributor slack channel are not a support network. Keep support and questions of implementation on Spectrum.
  • Competition is good, but trash talking a library you do not pay for and are not willing to actively contribute to is considered very disrespectful in my book and won’t be tolerated in any of my repos.
  • Let’s make some awesome software!

Carry on!

With #870 merged, we can now put this to rest 🎉

@JustFly1984 We are not gatsby and if you’re not helping, please don’t be an ass about it as well.

Then give me some feedback dude! You had the author of half this crap working on your private project for free. I was waiting for your review so I could fix the issue once and for all. The fix I was trying to apply for you would have been the PR I open to this project.

Emotions aside. I do apologize that we didn’t pull through for you. If you do end up using either my tools or ones I’m accociated with. feel free to reach out, I am always willing to support our users and care about the success of their projects

It does not support CSS modules, and it will not support it for long time. This is a thread about CSS modules. I’m telling everybody who does need CSS modules as I am that it does not resolving this issue since 5.8.2 version. I was pointing this out for long time, and wasted my time.

You don’t have to apologize. We also need to thank you for helping solve this problem. I hope this framework is getting better and better.

hey guys, sorry for such a delay.

Regarding my css loader extract-css-chunks-webpack-plugin which is part of react static. Im about to release a next tag build on npm which should get HMR and CSS modules working again.

This assumes you are on the new RS6

UPDATE:::: its on npm under @next tag

@tannerlinsley sorry bro, I didn’t mean to be considered trash talking. Peace everybody.

Guys don’t waste your time with react-static, I’ve migrated every project I had to gatsby, and gatsby is faster, reliable and does support css modules out of the box without the need to hustle with webpack config, although you can do that too.

Thanx, that means I can try to update! Can’t wait!!!