webpack-contrib / mini-css-extract-plugin

Lightweight CSS extraction plugin

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Don't output empty JS files

TomS- opened this issue · comments

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.

@TomS- can you create minimum reproducible test repo?

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

@TomS- I'm not sure I understand your issue. Do you want to not output tailwind.js from importing tailwind.css because there is already a tailwind.js in your source?

@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.

@TomS- I don't want it too :)

+1 Mainly because it'll probably fix Va1/browser-sync-webpack-plugin#69

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

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.

The cause of this is probably webpack/webpack#7300, which has been added to the Webpack 4.x milestone.

This issue is biting me as well. Feel free to close this if this plugin can't offer an alternative. Btw, webpack/webpack#7300 has been retagged with Webpack 5.x, so we won't likely see a fix in webpack until then.

We search way to fix it inside plugin

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.

My temporary solution is to use this plugin - https://github.com/medfreeman/ignore-assets-webpack-plugin
Works with v4, but use deprecated methods.

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.

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()
        ],

@riccardomessineo correct me if I'm wrong, but it still generates those files, but don't add them to the html file, right?

@Igloczek you're perfectly right.
It does not resolve the issue, I just wanted to provide another workaround.

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 :-)

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

@michael-ciniawsky bug, still open before it was fixed in webpack

@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...

@artemkochnev would you mind to open an issue here to provide me more info? Relevant parts of your webpack.config, webpack version, etc.

To remove these redundant js files from my test project, I tried the HtmlWebpackExcludeAssetsPlugin as suggested by @riccardomessineo and found that it broke the app. The app no longer loaded correctly, even though all of the js files (except the almost empty ones which are generated due to the css splitting) are downloaded by the browser, the js doesn't seem to be executed.

I also tried , in addition to the above, removing the files from the compilation using the plugin method provided by @danechitoaie

When using the plugin method, i still needed to use HtmlWebpackExcludeAssetsPlugin because the redundant js files were still being added to index.html despite not being outputted.

Maybe these redundant files are still required by the runtime?
I am not going to spend any more time on this as its not a major issue, it's just not clean. I guess i will have to wait for webpack v5?

commented

@riccardomessineo
HtmlWebpackExcludeAssetsPlugin broke the app.

Sorry to hear that...
As I pointed before, it was just a workaround and not a viable clean final solution.

@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
    ],
};

I forked disable-output-webpack-plugin to allow me to conditionally remove the outputs (https://github.com/Jorenm/disable-output-webpack-plugin/tree/options)

I use it with this config, which works like a charm:

var stylusCompiler = {
	name: 'stylus',
	entry: {
		above_fold: './src/css/above_fold.styl',
		site: './src/css/site.styl'
	},
	output: {
		path: path.resolve(__dirname, 'dist/css'),
	},
	module: {
		rules: [
			{
				test: /\.styl$/,
				use: [
					{
						loader: MiniCssExtractPlugin.loader,
						options: {
							// you can specify a publicPath here
							// by default it use publicPath in webpackOptions.output
							publicPath: '../'
						}
					},
					{
						loader: "css-loader" // translates CSS into CommonJS
					},
					{
						loader: "stylus-loader", // compiles Stylus to CSS
						options: {
							use: [
								require('nib')(),
								require('rupture')()
							]
						}
					},
				]
			},
		]
	},
	plugins: [
		new MiniCssExtractPlugin({
			filename: '[name].bundled.css',
		}),
		new DisableOutputWebpackPlugin({
			test: /\.js$/
		})
	],
	optimization: {
		minimizer: [new OptimizeCSSAssetsPlugin({})],
	}
};

@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
],
};

very well ! Thanks !

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)

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

commented

Another way to prevent this:

const IgnoreEmitPlugin = require('ignore-emit-webpack-plugin');
plugins: [
    new IgnoreEmitPlugin(/(?<=main_css\s*).*?(?=\s*js)/gs),
]
``

Hello erveryone.
After six days, for one short moment i thought i succeeded in removing 'empty' js files from the html and from the filesystem with 'html-webpack-exclude-assets-plugin' and 'ignore-emit-webpack-plugin' (one suggested by riccardomessineo above, one by psdon).
Everything fine, links gone, empty files gone, the bootstrap js is inside of index.js, index.js is properly linked, no console error and all JavaScript dead.

As soon as i put the line 'excludeAssets: [/app.js/, /custom.js/]' in the webpack config above in comments, everything works (ok, an console error that missing files are linked which, in this case, does indicate that it works) but i have the links to the files in the html. When i uncomment that line, the behaviour is as described above.

Some help before reaching insanity would be highly appreciated. Thank you.

const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("extract-css-chunks-webpack-plugin");
const HtmlWebpackExcludeAssetsPlugin = require("html-webpack-exclude-assets-plugin");
const IgnoreEmitPlugin = require("ignore-emit-webpack-plugin");

const path = require('path');
const isProd = process.env.NODE_ENV === 'production';
const outputDir = path.join(__dirname, 'build/');

module.exports = {

  entry: {
    index: path.resolve( process.cwd(), 'src', 'index.js')
  },

  mode: isProd ? 'production' : 'development',

  plugins: [
    new HtmlWebpackPlugin({
      filename: 'index-fsc.html',
      template: 'src/index-fsc.html',
      excludeAssets: [/app.js/, /custom.js/]
    }),

    new MiniCssExtractPlugin({
      filename: isProd ? 'assets/css/[name].[hash].css' : 'assets/css/[name].css',
    }),

	new HtmlWebpackExcludeAssetsPlugin(),
	// finally, together with 'html-webpack-exclude-assets-plugin', this is useful 

	new IgnoreEmitPlugin(['app.js', 'custom.js'])
  ],

  optimization: {
     splitChunks: {
      cacheGroups: {
        app: {
          name: 'app',
          test: /app\.s?css$/,
          chunks: 'all',
          enforce: true,
        },
        custom: {
          name: 'custom',
          test: /custom\.s?css$/,
          chunks: 'all',
          enforce: true,
        }
      },
    },
  },
  
  module: {
    rules: [
      {
        test: /\.(sa|sc|c)ss$/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
            options: {
              hmr: process.env.NODE_ENV === 'development',
            },
          },
          'css-loader', 'postcss-loader', 'sass-loader',
        ],
      },  
      {
         test: /.(ttf|otf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/,
         use: [{
           loader: 'file-loader',
           options: {
             emitFile: true,
             name: '[name].[ext]',
             outputPath: 'assets/fonts/',    // where the fonts will go
             publicPath: '../fonts'       // override the default path
           }
         }]
       }
    ]
  },

  output: {
    path: outputDir,
    filename: 'assets/js/[name].js'
  },

  devServer: {
    compress: true,
    contentBase: outputDir,
    port: process.env.PORT || 8000,
    historyApiFallback: true
  }
};

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

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.

It turned out that not importing the scss files in my index.js but setting them as entry points in the webpack config did the job.

Everything else was fine. I never changed this throughout my efforts to get this to work. Of course i tried setting the scss files as entry points but not once did i think of removing them from my index.js. Other solutions here might also only work well if crafted like this, i don't know, but check if they do not.

I know some folks say that setting (s)css as an entry point should not work at all, consider it bad practice. At this moment in time i couldn't care less as long as i do not encounter direct drawbacks from that in my project (but i am interested in what these might be).

And, to be honest, i have no real need to get this going this way, i am new to webpack, this is my first 'live' test which began 6 days ago. This is how i start stuff, i try to accomplish something i have in mind. That also is the reason for why there's Foundation and Bootstrap in there, i was curious. Had to get into Foundation for a job and i wanted to transfer some of my Boostrap blocks to be styled by Foundation. To do this in one file in parallel seemed interesting and now it seems to work.
Thanks to all the contributors to this thread, before i was desperate because of this :-)

const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('extract-css-chunks-webpack-plugin');
const HtmlWebpackExcludeAssetsPlugin = require('html-webpack-exclude-assets-plugin');
const IgnoreEmitPlugin = require('ignore-emit-webpack-plugin');

const path = require('path');
const isProd = process.env.NODE_ENV === 'production';
const outputDir = path.join(__dirname, 'build/');

module.exports = {

  entry: {
    index: path.resolve( process.cwd(), 'src', 'index.js'),
    app: path.resolve( process.cwd(), 'scss', 'app.scss'),
    custom: path.resolve( process.cwd(), 'scss/fsc-custom', 'custom.scss')
  },

  mode: isProd ? 'production' : 'development',

  plugins: [
    new HtmlWebpackPlugin({
      filename: 'index-fsc.html',
      template: 'src/index-fsc.html',
      excludeAssets: [/app.js/, /custom.js/]
    }),

    new MiniCssExtractPlugin({
      filename: isProd ? 'assets/css/[name].[hash].css' : 'assets/css/[name].css',
    }),

    new HtmlWebpackExcludeAssetsPlugin(),
    // finally, together with 'html-webpack-exclude-assets-plugin', this is useful 
    new IgnoreEmitPlugin(['app.js', 'custom.js'])
  ],

  optimization: {
    splitChunks: {
      cacheGroups: {
        app: {
          name: 'app',
          test: /app\.s?css$/,
          chunks: 'all',
          enforce: true,
        },
        custom: {
          name: 'custom',
          test: /custom\.s?css$/,
          chunks: 'all',
          enforce: true,
        }
      },
    },
  },
  
  module: {
    rules: [
      {
        test: /\.(sa|sc|c)ss$/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
              options: {
                hmr: process.env.NODE_ENV === 'development',
              },
          },
          'css-loader', 
          'postcss-loader', 
          'sass-loader',
        ],
      },  

      {
        test: /.(ttf|otf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/,
        use: [{
          loader: 'file-loader',
          options: {
            emitFile: true,
            name: '[name].[ext]',
            outputPath: 'assets/fonts/',
            publicPath: '../fonts'
          }
        }]
      }
    ]
  },

  output: {
    path: outputDir,
    filename: 'assets/js/[name].js'
  },

  devServer: {
    compress: true,
    contentBase: outputDir,
    port: process.env.PORT || 8000,
    historyApiFallback: true
  }
};

I said i tried all of the solutions in this thread. That's true. But the more i look through them now the more i see that i could have found out faster if i hadn't been only rearranging the webpack config over and over again. Some of the solution do suggest using entry points. You know what, this is confusing. It is.

Close in favor #85

Just another idea, in case you know which directory you want to remove the useless JS file from and your webpack command is aliased as an npm run command then you can just issue a wildcard delete for anything that ends with a .js extension in that directory. Not a perfect solution, but satisfactory.

Example:

In package.json

{
  ...
  "scripts": {
    "build": "node_modules/.bin/webpack && rm -Rf ./dist/css/*.js"
  }
}

then

$ npm run build
commented

v5 is here and it has changed absolutely nothing. Seems it's being tracked at webpack/webpack#11671.

// webpack.config.js
{
  optimization: {
      splitChunks: {
        cacheGroups: {
          styles: {
            ...
            type: 'css/mini-extract',
            ...
          },
        },
      },
    },
}

I added the type: 'css/mini-extract', config. And then resolved the problem!

// webpack.config.js
{
  optimization: {
      splitChunks: {
        cacheGroups: {
          styles: {
            ...
            type: 'css/mini-extract',
            ...
          },
        },
      },
    },
}

I added the type: 'css/mini-extract', config. And then resolved the problem!

This doesn't work, at least not with WebPack 4. How does it work for you?

commented

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);
        },
      }
    ]
  }
}),

@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
    ],
};

Thank you very much for your help, I have tried several times to compile for separate js and css files and always had problems, but you helped a lot with your code. Good to you!!!!!