Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't output empty JS files #151

Closed
TomS- opened this issue May 17, 2018 · 45 comments
Closed

Don't output empty JS files #151

TomS- opened this issue May 17, 2018 · 45 comments

Comments

@TomS-
Copy link

TomS- commented May 17, 2018

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.

@alexander-akait
Copy link
Member

@TomS- can you create minimum reproducible test repo?

@TomS-
Copy link
Author

TomS- commented May 17, 2018

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

@TomS-
Copy link
Author

TomS- commented May 18, 2018

https://github.com/TomS-/minicss Sorry for the delay

@tiendq
Copy link
Contributor

tiendq commented May 21, 2018

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

@TomS-
Copy link
Author

TomS- commented May 21, 2018

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

@tiendq
Copy link
Contributor

tiendq commented May 22, 2018

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

@paulmasek
Copy link

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

@GiancarlosIO
Copy link

+1

@alexander-akait
Copy link
Member

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

@thewebsitedev
Copy link

thewebsitedev commented Jun 16, 2018

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.

@bregenspan
Copy link

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

@paynecodes
Copy link

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.

@alexander-akait
Copy link
Member

We search way to fix it inside plugin

@danechitoaie
Copy link

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.

@Igloczek
Copy link

Igloczek commented Jul 4, 2018

@Igloczek
Copy link

Igloczek commented Jul 4, 2018

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

@danechitoaie
Copy link

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.

@riccardomessineo
Copy link

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

@Igloczek
Copy link

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

@riccardomessineo
Copy link

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

@fqborges
Copy link

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

@michael-ciniawsky
Copy link
Member

michael-ciniawsky commented Aug 24, 2018

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 michael-ciniawsky changed the title Feature Request: Don't output JS file Don't output empty JS files Aug 24, 2018
@alexander-akait
Copy link
Member

alexander-akait commented Aug 24, 2018

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

@artemkochnev
Copy link

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

@themojilla
Copy link

@riccardomessineo
HtmlWebpackExcludeAssetsPlugin broke the app.

@riccardomessineo
Copy link

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

@tflori
Copy link

tflori commented Dec 25, 2018

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

@Jorenm
Copy link

Jorenm commented Jan 11, 2019

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

@wzc0x0
Copy link

wzc0x0 commented Jan 14, 2019

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

@leonexcc
Copy link

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)

@alexander-akait
Copy link
Member

alexander-akait commented Apr 11, 2019

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

@psdon
Copy link

psdon commented Nov 18, 2019

Another way to prevent this:

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

@ararename
Copy link

ararename commented Dec 4, 2019

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
  }
};

@alexander-akait
Copy link
Member

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

@ararename
Copy link

ararename commented Dec 4, 2019

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.

@ararename
Copy link

ararename commented Dec 4, 2019

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.

@alexander-akait
Copy link
Member

Close in favor #85

@grahamsutton
Copy link

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

@Bilge
Copy link

Bilge commented Dec 21, 2020

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

@zhuzhuyule
Copy link

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

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

@koga73
Copy link

koga73 commented Feb 9, 2021

// 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?

@alexb-uk
Copy link

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

@Ihor139
Copy link

Ihor139 commented Apr 24, 2024

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests