Search code examples
javascriptcsswebpackcss-loader

Webpack prevent emit css for some files with css modules


I'm trying to configure webpack in such a way, that for a certain .css files it does not emit css(does not add anything to styles.css), but emits locals(className to style mapping in the imported object in js). In css-loader modules option is always enabled (aka CSS Modules). For example:

In some .js file:

import cssNoEmit from './styles.noemit.scss'
import cssEmit from './styles.scss'

the contents of styles.noemit.scss:

.testNoEmit {
  background: orange;
}

the contents of styles.scss:

.testEmit {
  background: blue;
}

What i want to achieve is that the final generated .css contains .testEmit class only. Also, both cssNoEmit and cssEmit contain mappings to a relevant classes in the generated .css

It may by possible to solve this with extract-text-plugin, but i'm not looking for that solution, since this plugin is deprecated.

Here is what i have tried:

All the attempts have reproductible examples here

EDIT: attempts 1 (thanks to @felixmosh) and 5 managed to work.

Attempt 1 (working). css-loader has an option onlyLocals. The description in the css-loader documentation is very vague, but as i understand it is supposed to do the exactly the same thing i am trying to achieve.

  {
    test: /\.noemit\.scss$/,
    use: [
      //MiniCssExtractPlugin.loader, // after disabling this, it started to work
      {
        loader: 'css-loader',
        options: {
          modules: {
            mode: 'local',
            exportGlobals: true,
            localIdentName: '[path][name]__[local]--[hash:base64:5]',
            context: path.resolve(__dirname, 'src'),
            hashPrefix: '_',
          },
          importLoaders: 2,
          onlyLocals: true, // setting this to true causes transpilation to crash
        },
      },
      
      ...

    ],
  },

Attempt 5 (working, not perfectly). Trying to use webpack built-in feature, SplitChunksPlugin. I tried to create a config for this use case based on this, SplitChinksPlugin docs, this, this.

  ...

  optimization: {
    splitChunks: {
      cacheGroups: {
        groupUnusedStyles: {
          name: "unusedStyles",
          chunks: "all",

          test: /\.noemit\.scss$/,

          enforce: true
        },
      }
    }
  }

This approach redirects unwanted css into unusedStyles.css

It works, with one small, but frustrating drawback. Apart from unusedStyles.css it generates unusedStyles.js and this has to be included in html, otherwise the app won't run.

To be more specific, both chunks have to be inside html:

  <script type="text/javascript" src="/app.bundle.js"></script>
  <script type="text/javascript" src="/unusedStyles.bundle.js"></script>

Does anyone know how to make it work without needing to add another bundle?

Attempt 2 (not working). Don't use onlyLocals setting and ommit a loader that is responsible for the "css output"(such as style-loader or mini-extract-css-plugin). In this case the contents of imported css are very strange

strange contents of the imported object in javascript

Attempt 3 (not working) null-loader does not work either, as it imports just an empty object

Attempt 4 (not working). There is another idea:

class ServerMiniCssExtractPlugin extends MiniCssExtractPlugin {
  getCssChunkObject(mainChunk) {
    return {};
  }
}

... // use ServerMiniCssExtractPlugin the same way as MiniCssExtractPlugin 

This does not work, because final .css has all the styles, as if normal MiniCssExtractPlugin was used

Also, there is a related thread on github.


Solution

  • Your first attempt is the correct one, just remove MiniCssExtractPlugin.loader from the noemit.scss loader list.

    I've tried it locally, it works, the out css contains only the none "noemit" files but there is css-modules object mapping for both.

    The removed loader (MiniCssExtractPlugin) is the one that responsible to include the scss in the final css file.