mini-css-extract-plugin: Don't output empty JS files

I have seen people use the Extract plugin and name the file the same to prevent a useless .js file, however, naming the file the same in this instance gives the error: Conflict: Multiple assets emit to the same filename main.js

I only want to take a CSS file and run it though autoprefix and minify, I don’t need it to output a JS file.

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 80
  • Comments: 45 (10 by maintainers)

Most upvoted comments

@Tiendq I don’t want Webpack to output a JS file for CSS. It seems strange to me for it to do that. At the moment I’m getting the CSS file and a JS file. I’m coming from Gulp so maybe it’s just I don’t understand Webpack.

Same issue. I have a PHP application but I am managing assets with Webpack. With Webpack 4 & MiniCssExtractPlugin, generating CSS files is a pain at the moment. I need to directly load CSS file.

Part of my config:

{
    mode: "development",
    entry: {
        "admin": "./assets/admin/src/scss/theme.scss"
    },
    module: {
        rules: [
            {
                test: /\.(sa|sc|c)ss$/,
                use: [
	            MiniCssExtractPlugin.loader,
                    'css-loader',
                    'sass-loader'
                ],
            },
        ]
    },
    plugins: [
        new MiniCssExtractPlugin({
            filename: "[name].css",
            chunkFilename: "[id].css"
        })
    ],
    output: {
        path: path.resolve(__dirname, 'assets/admin/dist/css')
    }
},

Files generated are: admin.css & admin.js.

I don’t need admin.js file. It’s useless in my case.

Weppack is js bundle, i can not imagine why this might be necessary, please provide use case

I faced the same issue of having a style only entry (css/sass/less) generating an extra .js file, and ended up creating a webpack plugin to remove the js file from the compilation.

I published it on npm as webpack-fix-style-only-entries. You can find the source code on https://github.com/fqborges/webpack-fix-style-only-entries.

😃 shamelessly promoting my package 😃

@danechitoaie thx for the tip!

I’ve just created a more abstract class to do this:

class Without {
    constructor(patterns) {
        this.patterns = patterns;
    }

    apply(compiler) {
        compiler.hooks.emit.tapAsync("MiniCssExtractPluginCleanup", (compilation, callback) => {
            Object.keys(compilation.assets)
                .filter(asset => {
                    let match = false,
                        i = this.patterns.length
                    ;
                    while (i--) {
                        if (this.patterns[i].test(asset)) {
                            match = true;
                        }
                    }
                    return match;
                }).forEach(asset => {
                    delete compilation.assets[asset];
                });

            callback();
        });
    }
}

module.exports = {
    mode: process.env.NODE_ENV || 'development',
    resolve: {
        extensions: ['.scss', '.css']
    },
    module: {
        rules: [
            {
                test: /\.scss$/,
                use: [
                    MiniCssExtractPlugin.loader,
                    'css-loader',
                    'sass-loader',
                ],
            },
        ],
    },
    plugins: [
        new MiniCssExtractPlugin({
            filename: '[name]',
        }),
        new Without([/\.css\.js(\.map)?$/]), // just give a list with regex patterns that should be excluded
    ],
};

We search way to fix it inside plugin

v5 is here and it has changed absolutely nothing. Seems it’s being tracked at https://github.com/webpack/webpack/issues/11671.

I had to make my own plugin:

class MiniCssExtractPluginCleanup {
    apply(compiler) {
        compiler.hooks.emit.tapAsync("MiniCssExtractPluginCleanup", (compilation, callback) => {
            Object.keys(compilation.assets)
                .filter(asset => {
                    return ["*/scss/**/*.js", "*/scss/**/*.js.map"].some(pattern => {
                        return minimatch(asset, pattern);
                    });
                })
                .forEach(asset => {
                    delete compilation.assets[asset];
                });

            callback();
        });
    }
}

It’s very specific for my use case and has things hardcoded and I even have just put it directly in the webpack.config.js file (so not published on npm) but maybe it can be integrated somehow in some version directly into mini-css-extract-plugin? And made configurable with some additional options.

If it helps anyone I used the plugin remove-files-webpack-plugin, just need to change folder to match your project structure:

new RemovePlugin({
  /**
   * After compilation permanently remove empty JS files created from CSS entries.
   */
  after: {
    test: [
      {
        folder: 'dist/public/css',
        method: (absoluteItemPath) => {
          return new RegExp(/\.js$/, 'm').test(absoluteItemPath);
        },
      }
    ]
  }
}),

Having same issue. I’m using webpack to compile both JS and SCSS and different entries (i.e. not requiring the css inside JS) and for each .css file generated I also get a .js file.

Fixed for webpack@5, anyway you can send a PR with fix it on mini-css-extract-plugin side (using option)

It will be solved for webpack@5 (near future), right now please use solutions above

To give you another “solution”, I’m currently just excluding the useless .js on the bundled index.html:

        plugins: [
            new HtmlWebpackPlugin({
                template: './app/index_webpack.html',
                excludeAssets: [/partials.*.js/],
            }),
            new HtmlWebpackExcludeAssetsPlugin()
        ],

@TomS- I don’t want it too 😃

@evilebottnawi Yup, I’ll set it up this evening

I use Symfony Encore and have to fix the entrypoints too. But this should also work with a normal Webpack.

My approach to this problem:

class MiniCssExtractPluginCleanup {
    apply(compiler) {
        compiler.hooks.emit.tapAsync("MiniCssExtractPluginCleanup", (compilation, callback) => {
            let jsFile = /\.js$/;

            compilation.entrypoints.forEach((entrypoint) => {
                entrypoint.chunks.forEach((chunk) => {
                    if (chunk.files.length > 1) {
                        let notEmptyJsModules = chunk.getModules().filter(module => {
                            return module.constructor.name === 'NormalModule'
                                && module.originalSource().source() !== '// extracted by mini-css-extract-plugin';
                        });

                        if (notEmptyJsModules.length === 0) {
                            chunk.files = chunk.files.filter(file => {
                                if (jsFile.test(file)) {
                                    delete compilation.assets[file];
                                    return false;
                                } else return true;
                            });
                        }
                    }
                });
            });

            callback()
        });
    }
}

and i use it with:

.addPlugin(new MiniCssExtractPluginCleanup(), -11)

Thank you for the answer, i was in hope to not get this ‘answer’.

Here we have a thread discussing this very problem since 1 1/2 years, the tip to wait for v5 had already been given a year ago, but the discussion went on.

I did use a solution from above, i tried all of them. I am looking for a solution for my problem (as described above). Any help is welcome.

@fqborges I’m trying to use your plugin, but I’m running into a problem where the file is getting passed as * instead of the actual file path.

For example, if I add a console.log(resources, file), I get a bunch of results like this: [ '<redacted>/src/app/css/pages/Home.scss' ] '*'

Do you know why this might be happening? The webpack docs on what gets passed for the chunkAsset hook are fairly light…

I have seen people use the Extract plugin and name the file the same to prevent a useless .js file, however, naming the file the same in this instance gives the error: Conflict: Multiple assets emit to the same filename main.js

Use { filename: '[name].css' }. The empty JS File is going away (in webpack >= v5.0.0), but this needs to be fixed in webpack core itself