Skip to content

Support multiple instances of MiniCssExtractPlugin #45

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

Closed
james-s-turner opened this issue Mar 20, 2018 · 57 comments
Closed

Support multiple instances of MiniCssExtractPlugin #45

james-s-turner opened this issue Mar 20, 2018 · 57 comments

Comments

@james-s-turner
Copy link

The extract-webpack-text-plugin allows you to define multiple instances of the plugin. Does MiniCssExtractPlugin support the same feature? My end goal is to generate multiple bundles for SASS themes.

@michael-ciniawsky
Copy link
Member

Please describe your use case and provide your config, this might not be needed anymore with mini-css-extract-plugin

@james-s-turner
Copy link
Author

james-s-turner commented Mar 20, 2018

Hi @michael-ciniawsky - thanks for the prompt response. Hopefully this will clarify the need.
I want to create an app with multiple themes - defined as SASS variables.

/dark-theme/_theme-vars.scss
/light-theme/_theme-vars.scss

When the build runs, a CSS bundle file for each theme is emitted. Each component imports the SASS file containing the theme variables @import "theme-vars.scss". I configure the includePath of the sass-loader to point to the appropriate theme directory.
The full setup is shown in the repository

const __home = path.resolve(__dirname, '');

module.exports = {
    entry: path.resolve('src/index.js'),
    output: {
        filename: "[name].js",
        path: path.resolve("dist")
    },
    module: {
        rules: [
            {
                test: /\.scss$/,
                use: [
                    MiniCssExtractPlugin.loader,
                    "css-loader",
                    {
                        loader: "sass-loader",
                        options: {  includePaths: [path.resolve(__home, "src/light-theme"),]}

                    }
                ]
                // Want to do something like this to generate a second CSS theme bundle
                // use: [
                //     MiniCssExtractPlugin.loader,
                //     "css-loader",
                //     {
                //         loader: "sass-loader",
                //         options: {  includePaths: [path.resolve(__home, "src/dark-theme"),]}
                //
                //     }
                // ]
            }
        ]
    },
    plugins: [
        new MiniCssExtractPlugin({
            filename: "light-theme.css"
        })
        // new MiniCssExtractPlugin({
        //     filename: "dark-theme.css"
        // })
    ]
};

The above config runs happily emitting the css bundle /dist/light-theme.css. I can change the includePaths variable and generate /dist/dark-theme.css.

I want to run the build once - emitting both light and dark theme bundles.
So as shown in the putative commented out code, I want to somehow create "two instances" of the MiniCssExtractPlugin. Each one configured with the appropriate theme directory path.

@alexkrolick
Copy link

Another use case is this:

  • Vendored stylesheet imports (i.e., Bootstrap and library CSS from node_modules) should be bundled without CSS modules
  • App CSS should be bundled with modules in the loader config

In ETWP we'd do:

-const ExtractTextPlugin = require('extract-text-webpack-plugin')
+const ExtractTextPlugin = require('mini-css-extract-plugin')

-const GlobalExtractTextPlugin = new ExtractTextPlugin({
-  filename: production ? 'styles-[name]-[contenthash].css' : 'styles-[name].css',
-  ignoreOrder: false,
-  allChunks: true,
-})
-
-const ModuleExtractTextPlugin = new ExtractTextPlugin({
-  filename: production ? 'modules-[name]-[contenthash].css' : 'modules-[name].css',
-  ignoreOrder: false,
-  allChunks: true,
-})
       {
         test: /\.(css)$/,
         include: [/stylesheets/, /node_modules/],
-        use: extractCss
-          ? GlobalExtractTextPlugin.extract({
-            fallback: ['style-loader', 'css-loader'],
-            use: ['css-loader'],
-          })
-          : ['style-loader', 'css-loader'],
+        use: extractCss ? [ExtractTextPlugin.loader, 'css-loader'] : ['style-loader', 'css-loader'],
       },
      {
		 test: /\.css$/,
         exclude: [/stylesheets/, /node_modules/],
         use: extractCss
+          ? [
-          ? ModuleExtractTextPlugin.extract({
+            ExtractTextPlugin.loader,
-            fallback: [
-              'style-loader',
-              'css-loader?sourceMap&modules,localIdentName=[local]-[hash:base64]',
-            ],
+          ]
-            use: ['css-loader?sourceMap&modules,localIdentName=[local]-[hash:base64]'],
+            'css-loader?sourceMap&modules,localIdentName=[local]-[hash:base64]',
-          })
           : ['style-loader', 'css-loader?sourceMap&modules,localIdentName=[local]-[hash:base64]'],

@albertstill
Copy link

Here is an example repo of me trying to achieve theming with multiple instances of extract-text-webpack-plugin and two seperate PostCSS configs. It compiles but no files are written to disk, be great if this library supported the use case!

@garygreen
Copy link

Does this functionality not overlap with splitChunks? Your essentially wanting to split your different theme files? Seems to me you can just split your rules up so they generate each theme separately and use splitChunks to output them into the correct files?

@james-s-turner
Copy link
Author

@garygreen I can't see how that would work. In the end the SASS processor has to run twice (with different variables files each time) generating parallel sets of CSS. My understanding off chunks is that it's one set of output split into different bits - rather than two separate outputs. I'd happily be proved wrong tho ;-)

@garygreen
Copy link

garygreen commented Mar 27, 2018

@james-s-turner I'm thinking something along the lines of:

module: {
    rules: [
        {
            test: /\.scss$/,
            include: path.resolve(__home, "src/light-theme"),
            use: [
                MiniCssExtractPlugin.loader,
                "css-loader",
                "sass-loader"
            ]
        },
        {
            test: /\.scss$/,
            include: path.resolve(__home, "src/dark-theme"),
            use: [
                MiniCssExtractPlugin.loader,
                "css-loader",
                "sass-loader"
            ]
        }
    ]
},

With a splitChunks section etc.

Or is that just a slightly different way to write what your trying to achieve?

@sokra
Copy link
Member

sokra commented Mar 29, 2018

Interesting use case. The same could be supported for the mini-css-extract-plugin. In the same way as it's implemented in extract-text-webpack-plugin.

Send a PR.

@james-s-turner
Copy link
Author

james-s-turner commented Mar 30, 2018

@garygreen Unfortunately if you define two rules with the same test pattern - then only the first one is run

@ccorcos
Copy link

ccorcos commented Apr 12, 2018

@michael-ciniawsky the goal is just to be able to create more than one CSS file. Any suggestions?

@lukepolo
Copy link

We also have something like this where we have our sass styles getting extracted, then wanting to extract our vue styles to a different file.

@lukepolo
Copy link

@sokra is there any guidance on how you would like it done?

Should we :
Attach something to each loader in its options to check if the chunk should be extracted?

Or is there another way you wish this to be done?

@gigobyte
Copy link

gigobyte commented Apr 27, 2018

We use multiple instances of ETWP, one to handle css and one to handle scss so that the different formats end up in a different file.

const extractCSS = new ExtractTextPlugin('vendor.css');
const extractSASS = new ExtractTextPlugin('[name].css');

...
test: /\.scss$/,
use: extractSASS.extract({
...

test: /\.css$/,
use: extractCSS.extract({

@alexander-akait
Copy link
Member

Somebody can provide real use case for two instance?

@lukepolo
Copy link

@evilebottnawi this is a real case I believe :
#45 (comment)

I personally use the multiple extracts to separate the admin and public styles.

@alexander-akait
Copy link
Member

@lukepolo it is solved by code splitting, i.e splitChunks.cacheGroups, you need split you themes to own bundles and when mini-css-extract-plugin extract css automatically

@lukepolo
Copy link

Mind showing an example ? Or to some documentation, very little knowledge in that subject

@alexander-akait
Copy link
Member

Forget about this plugins as plugins, it is PoC build-in css extract in future webpack release with build-in css support. All extract to difference files should be done using splitChunks.cacheGroups, think about css same as about js, you need split you css to own bundle and plugins extract css self. No options in future webpack css support for multiple instance.

@lukepolo
Copy link

lukepolo commented Apr 27, 2018

Ok so basically what your saying is all we need todo is :

{
    optimization: {
      splitChunks: {
        cacheGroups: {
          commons: {
           test: m => {
                return /\.vue\?vue&type=style/.test(m._identifier);
            },
            name: "vue-extractions",
            chunks: "initial",
          },
        },
      },
    },
  },

@andosteinmetz
Copy link

Here's a (possibly unorthodox) use-case for multiple instances:

I have been using an additional instance of ExtractTextWebpackPlugin to output critical CSS into a Twig template partial to be inlined into the head of my document. This file lives in a directory outside of my normal ouptut.path to avoid mixing static files with includes. The relevant parts of my config look like so:

const extractCss = new ExtractTextPlugin('[name].css');
const extractCritical = new ExtractTextPlugin('../../src/FrontendBundle/Resources/views/common/critical-styles.css.twig');
{
   test: /.scss$/,
   exclude: path.resolve(srcPath, '../scss/critical.scss'),
   use: extractCss.extract(['css-loader',
    {
      loader: 'postcss-loader',
      options: { sourceMap: true }
    },
    'sass-loader'])
 },
 {
   test: /.scss$/,
   include: path.resolve(srcPath, '../scss/critical.scss'),
   use: extractCritical.extract(['css-loader',
    {
      loader: 'postcss-loader',
      options: { sourceMap: true }
    },
    'sass-loader'])
},

Will it be possible to continue with this approach using MiniExtractCSSPlugin?

@alexander-akait
Copy link
Member

@andosteinmetz use splitChunks.cacheGroups for split your critical styles from bundle and when mini-css-extract-plugin extract css.

Code splitting sometimes looks difficult, but it is part of your configuration which you should good understand. We have common and vendor bundles splitting by default, but for many project it is not enough. It is part where very difficult set universal zero configuration.

If you want have fast application, you should to understand how it works. With code splitting you can do all, really, what you can imagine.

Who want two/three/etc instance just don't know how to use code splitting. My recommendation is using webpack config from popular boilerplate. Because you can decrease perf you app.

@alexander-akait
Copy link
Member

@lukepolo yes, feel free to experimental with code splitting, if you can problems with this just ping me, also feel free to improve README to avoid question from other people 👍

@ccorcos
Copy link

ccorcos commented Apr 27, 2018

I would recommend just using "extract-text-webpack-plugin": "^4.0.0-beta.0",. It works for webpack 4.

@andosteinmetz
Copy link

Thanks @evilebottnawi. The application is already doing some code splitting, but mulitple instances of ETWP was a quick hack to output to another directory. I will look at the documentation for splitChunks.cacheGroups.

@alexander-akait
Copy link
Member

@ccorcos extract-text-webpack-plugin is not for extract css and can don't works with css in future, you need rewrite configuration when webpack do next release. It is bad practice

@alexander-akait
Copy link
Member

@andosteinmetz multiple instance just the hack, also it is decrease you build time, code splitting is very fast and stable.

@tim-maguire
Copy link

tim-maguire commented Jun 22, 2018

@evilebottnawi I wonder if this use case for a multi entry app can also be resolved by code splitting?
What we have in webpack 3:

const extractSass = new ExtractTextPlugin('build/css/[name].css');
const extractPageLevelSass = new ExtractTextPlugin(
  'Library.tx/build/css/[name].tx',
);
//…
      {
        test: /.*sass\/page_level\/.*\.scss$/,
        loader: extractPageLevelSass.extract({
          use: ['css-loader', parts.autoprefix(), parts.sassLoader()],
        }),
      },
      {
        test: /.*sass\/(?!page_level\/).*\.scss$/,
        loader: extractSass.extract({
          use: ['css-loader', parts.autoprefix(), parts.sassLoader()],
        }),
      },
//…
  plugins: [
    extractPageLevelSass,
    extractSass,
    //…
  ],
//…

Almost there in webpack 4 with mini-css-extract-plugin, but it just outputs all extracted stylesheets to both locations:

// parts
exports.extractCSS = ({
  include, test, exclude, use = [], filename
}) => {
  const plugin = new MiniCssExtractPlugin({
    filename: filename,
  });

  return {
    module: {
      rules: [
        {
          test: test,
          include,
          exclude,
          use: [MiniCssExtractPlugin.loader].concat(use),
        },
      ],
    },
    plugins: [plugin],
  };
};

// config

const extractPageLevelSass = parts.extractCSS({
  test: /.*sass\/page_level\/.*\.scss$/,
  filename: 'Library.tx/build/css/[name].tx',
  use: ['css-loader', parts.autoprefix(), parts.sassLoader()],
});

const extractSass = parts.extractCSS({
  test: /.*sass\/(?!page_level\/).*\.scss$/,
  filename: 'build/css/[name].css',
  use: ['css-loader', parts.autoprefix(), parts.sassLoader()],
});

module.exports = merge(config, extractPageLevelSass, extractSass);

screen shot 2018-06-22 at 20 18 25

@Igor-Vuk
Copy link

I am using multiple instances to separate my local style from my global styles. Until now I used it like this with extract-text-webpack-plugin. How would I achieve this with mini-css-extract?

const ExtractLocal = new ExtractTextPlugin({
  filename: 'stylesheet/stylesLocal.[contenthash].local.css',
  disable: false,
  allChunks: true,
})
const ExtractGlobal = new ExtractTextPlugin({
  filename: 'stylesheet/stylesGlobal.[contenthash].css',
  disable: false,
  allChunks: true,
})

module: {
    rules: [
       /* Local styles */
      {
        test: /\.local\.(css|scss)$/,
        use: ExtractLocal.extract({
          fallback: 'style-loader?sourceMap',
          use: [
             ......
          ],
        })
       }
      /* Global styles */
      {
        test: /^((?!\.local).)+\.(css)$/,
        use: ExtractGlobal.extract({
          fallback: 'style-loader?sourceMap',
          use: [
             .....
          ]
       })
      }  
]},
plugins: [
    ExtractLocal,
    ExtractGlobal,
    ....
]

@kronos93
Copy link

kronos93 commented Sep 9, 2018

I use multiple instances of ExtractTextWebpackPlugin to dynamically change the publichPath.
In some cases i need use an absolute publichPath like in a dompdf.css (for images it needs an absolute url)

Like:

const myApp= new ExtractTextPlugin('stylesheets/app.css');
const dompdf= new ExtractTextPlugin('stylesheets/dompdf.css');

module.exports = {
  module: {
    rules: [
      {
        test: /app\.scss$/,
        use: myApp.extract({ use: [...., 'sass-loader' ], publicPath: "/" })
      },
      {
        test: /dompdf\.scss$/i,
        use: dompdf.extract({ use: [...., 'sass-loader' ], publicPath: "https://app.domain.com/"})
      },
    ]
  },
  plugins: [
    myApp,
    dompdf
  ]
};

And if someone can help me, i'll be very grateful

I dont know if it is an issue.

I have the next entry config:

entry = {
      app: ['src/assets/js/app.js', 'src/assets/scss/app.scss'],
      dompdf: [ 'src/assets/scss/dompdf.scss'],
    };

And i have the default config of mini-css-extract-plugin and for some reason dompdf: ['src / assets / scss / dompdf.scss'], generate an individual js file that i do not need.

What can be a posible solution?

@dwatts3624
Copy link

I was researching this one and realized the answer is actually listed in the Webpack docs. I've implemented this and it works great!

https://webpack.js.org/plugins/mini-css-extract-plugin/#extracting-css-based-on-entry

Though I ended up doing something a bit more dynamic where I have a JSON config file...

{
  "scss" : [
    {
      "path" : "assets/scss/styles-red.scss",
      "name" : "styles-red",
      "primary" : true
    },
    {
      "path" : "assets/scss/styles-light-blue.scss",
      "name" : "styles-light-blue",
      "primary" : false
    }
  ]
}

Note: I have some environment-specific rules which are redacted here but that's the idea with primary in that I can set one of these to be default when I'm using the dev server.

Which I import:

const config = require('./env/dev/config');

Setup default entry points:

const entryPoints = {
  app: [ 'whatwg-fetch', appPath ]
}

Then loop through my config:

config.scss.forEach(entry => {
  entryPoints[entry.name] = path.join(rootPath, entry.path);
});

Setup the groups:

const cacheGroups = {}

config.scss.forEach(entryPoint => {
  cacheGroups[entryPoint.name] = {
    name: entryPoint.name,
    test: (m,c,entry = entryPoint.name) => m.constructor.name === 'CssModule' && recursiveIssuer(m) === entry,
    chunks: 'all',
    enforce: true
  }
});

And add them in!

const webpackConfig = {

  entry: entryPoints,

  // webpack stuff

  optimization: {
    splitChunks: {
      cacheGroups: cacheGroups
    }
  }

  // more webpack stuff

}

Hope that helps someone!

@bjankord
Copy link

I'm interested in this functionality to be able to generate a light theme and dark theme based on 1 entry.

@alexander-akait
Copy link
Member

@dwatts3624 feel free to send a PR to docs 👍

@oleksandrkyselov
Copy link

oleksandrkyselov commented Oct 27, 2018

I've migrated webpack 2 to 4, I needed to migrate from ExtractTextPlugin to MiniCssExtractPlugin, and now I'm facing the same issue and I'm trying to implement the suggested examples, I'm struggling since 4 hours and I've reached a point where the console doesn't throw errors but my ReactJS webpage does not load at all, blank.

Could someone bear a hand to me please?

WebPack 3 Configuration

const webpack = require('webpack');

const resolve = require('path').resolve;

const ExtractTextPlugin = require('extract-text-webpack-plugin');

const HTMLWebpackPlugin = require('html-webpack-plugin');

const CopyWebpackPlugin = require('copy-webpack-plugin');

const extractAppStyles = new ExtractTextPlugin('css/app.[contenthash].css');

const extractVendorStyles = new ExtractTextPlugin('css/vendor.[contenthash].css');

// This contains shared configuration for dev and prod builds
module.exports = {

	entry: {

		// All App source files will be compiled into main
		app: './src/index.js',

		// All vendor files will be compiled into vendor.
		// You should add new packages you install here. 
		vendor: [
			'babel-polyfill',
			'react',
			'react-dom',
			'react-router-dom',
			'semantic-ui-react',
			'dateformat',
			'axios',
			'react-waypoint',
			//'@babel/preset-react'
		]
	},
	devServer: {
		historyApiFallback: true,
		noInfo: true,
		contentBase: './dist',
		host: '0.0.0.0',
		hot: true,
		open: true,
		historyApiFallback: true,
		inline: true
	},
	module: {
		rules: [
			// Transpile all .js and .jsx files
			{
				test: /\.(js|jsx)?$/,
				exclude: /(node_modules)/,
				use: [
					{
						loader: 'babel-loader',
						options: {
							presets: [
								'@babel/react', 
								'@babel/env', 
							],
							plugins: [
								"@babel/plugin-proposal-class-properties", 
								[
									"@babel/plugin-proposal-decorators", { "legacy": true }
								],
								'@babel/plugin-proposal-object-rest-spread', 
								'@babel/plugin-syntax-dynamic-import', 
							],
						}
					},
				],
			},
			// Compile CSS files
			{ test: /\.css$/, loader: "style-loader!css-loader" },
			// Compile SCSS files
			{
				test: /\.scss$/,
				// This compiles styles specific to this app
				include: resolve(__dirname, './src/app/styles'),
				use: extractAppStyles.extract({
					fallback: 'style-loader',
					use: [
						{ loader: 'css-loader', options: { minimize: true, sourceMap: true } },
						{ loader: 'sass-loader', options: { sourceMap: true } },
					]
				}),
			},
			{
				test: /\.scss$/,
				// This compiles styles from Semantic-UI
				include: resolve(__dirname, './src/assets'),
				use: extractVendorStyles.extract({
					fallback: 'style-loader',
					use: [
						{ loader: 'css-loader', options: { minimize: true, sourceMap: true } },
						{ loader: 'sass-loader', options: { sourceMap: true } },
					]
				}),
			},

			// Copy static assets over with file-loader
			{
				test: /\.(ico)(\?v=[0-9]\.[0-9]\.[0-9])?$/, 
				loader: 'file-loader', options: {name: '[name].[ext]'},
			},
			{
				test: /\.(woff|woff2|eot|ttf|otf)(\?v=[0-9]\.[0-9]\.[0-9])?$/, 
				loader: 'file-loader', options: {name: 'fonts/[name].[ext]'},
			},
			{
				test: /\.(jpg|gif|png|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, 
				loader: 'file-loader', options: {name: 'images/[name].[ext]'},
			}
		]
	},
	resolve: {
		extensions: ['*', '.js', '.jsx'],
	},
	plugins: [
		// This pulls out webpack module IDs that changes every build to help with caching
		new webpack.HashedModuleIdsPlugin(),
		
		// This separates vendor-provided code into a separate chunk
		new webpack.optimize.CommonsChunkPlugin({
			name: 'vendor'
		}),
		
		// This pulls out webpack boilerplate code that changes every build to help with caching
		new webpack.optimize.CommonsChunkPlugin({
			name: 'runtime'
		}),

		// Extract styles into a separate css files
		extractAppStyles,
		extractVendorStyles,

		// Inject the build date as an environment variable 
		new webpack.DefinePlugin({
			'process.env':{
				'BUILD_DATE': JSON.stringify(new Date())
			}
		}),
		
		// Inject the required assets into the template index file
		new HTMLWebpackPlugin({
			filename: 'index.html',
			template: 'src/assets/index.html',
		}),

		// Copy public files into the dist folder
		new CopyWebpackPlugin([
			{ from: 'src/public' }
		]),
	],
	output: {
		path: __dirname + '/dist',
		filename: 'js/[name].[chunkhash].js',
		chunkFilename: 'js/[name].[chunkhash].js',
		publicPath: '/',
	},
};

WebPack 4 Configuration

const webpack = require('webpack');
const resolve = require('path').resolve;
//const ExtractTextPlugin = require('extract-text-webpack-plugin');
const HTMLWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const devMode = process.env.NODE_ENV !== 'production'

//const extractAppStyles = new ExtractTextPlugin('css/app.[contenthash].css');
//const extractVendorStyles = new ExtractTextPlugin('css/vendor.[contenthash].css');


// This contains shared configuration for dev and prod builds
module.exports = {
	entry: {
		// All App source files will be compiled into main
		app: './src/index.js',

		// All vendor files will be compiled into vendor.
		// You should add new packages you install here. 
		vendor: [
			'babel-polyfill',
			'react',
			'react-dom',
			'react-router-dom',
			'semantic-ui-react',
			'dateformat',
			'axios',
			'react-waypoint',
		]
	},
	devServer: {
		historyApiFallback: true,
		noInfo: true,
		contentBase: './dist',
		host: '0.0.0.0',
		hot: true,
		open: true,
		historyApiFallback: true,
		inline: true
	},
	module: {
		rules: [
			// Transpile all .js and .jsx files
			{
				test: /\.(js|jsx)?$/,
				exclude: /(node_modules)/,
				use: [
					{
						loader: 'babel-loader',
						options: {
							presets: [
								'@babel/react', 
								'@babel/env', 
							],
							plugins: [
								"@babel/plugin-proposal-class-properties", 
								[
									"@babel/plugin-proposal-decorators", { "legacy": true }
								],
								'@babel/plugin-proposal-object-rest-spread', 
								'@babel/plugin-syntax-dynamic-import', 
							],
						}
					},
				],
			},
			// Compile CSS files
			{ test: /\.css$/, use: [MiniCssExtractPlugin.loader, "css-loader"] },
			// Compile SCSS files
			{
				test: /\.scss$/,
				// This compiles styles specific to this app
				include: resolve(__dirname, './src/app/styles'),
				use: [
					devMode ? 'style-loader' : MiniCssExtractPlugin.loader,
					{ loader: 'css-loader', options: { minimize: true, sourceMap: true } },
					{ loader: 'sass-loader', options: { 
						sourceMap: true,
						includePaths: [resolve(__dirname, "css/app.css")]
					 } }
				],
			},
			{
				test: /\.scss$/,
				// This compiles styles from Semantic-UI
				include: resolve(__dirname, './src/assets'),
				use: [
					devMode ? 'style-loader' : MiniCssExtractPlugin.loader,
					{ loader: 'css-loader', options: { minimize: true, sourceMap: true } },
					{ loader: 'sass-loader', options: { 
						sourceMap: true,
						includePaths: [resolve(__dirname, "css/vendor.css")]
					 } }
				]
			},

			// Copy static assets over with file-loader
			{
				test: /\.(ico)(\?v=[0-9]\.[0-9]\.[0-9])?$/, 
				loader: 'file-loader', options: {name: '[name].[ext]'},
			},
			{
				test: /\.(woff|woff2|eot|ttf|otf)(\?v=[0-9]\.[0-9]\.[0-9])?$/, 
				loader: 'file-loader', options: {name: 'fonts/[name].[ext]'},
			},
			{
				test: /\.(jpg|gif|png|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, 
				loader: 'file-loader', options: {name: 'images/[name].[ext]'},
			}
		]
	},
	resolve: {
		extensions: ['*', '.js', '.jsx'],
	},
	plugins: [
		// This pulls out webpack module IDs that changes every build to help with caching
		new webpack.HashedModuleIdsPlugin(),
		
		// This separates vendor-provided code into a separate chunk
		// new webpack.optimize.CommonsChunkPlugin({
		// 	name: 'vendor'
		// }),
		
		// // This pulls out webpack boilerplate code that changes every build to help with caching
		// new webpack.optimize.CommonsChunkPlugin({
		// 	name: 'runtime'
		// }),

		// Extract styles into a separate css files
		//extractAppStyles,
		//extractVendorStyles,

		// Inject the build date as an environment variable 
		new webpack.DefinePlugin({
			'process.env':{
				'BUILD_DATE': JSON.stringify(new Date())
			}
		}),
		
		// Inject the required assets into the template index file
		new HTMLWebpackPlugin({
			filename: 'index.html',
			template: 'src/assets/index.html',
		}),

		// Copy public files into the dist folder
		new CopyWebpackPlugin([
			{ from: 'src/public' }
		]),
		new MiniCssExtractPlugin({
			// Options similar to the same options in webpackOptions.output
			// both options are optional
			filename: devMode ? '[name].css' : '[name].[hash].css',
			chunkFilename: devMode ? '[id].css' : '[id].[hash].css',
		}),
		new MiniCssExtractPlugin({
			filename: devMode ? 'app.css' : 'app.[hash].css',
        }),
        new MiniCssExtractPlugin({
            filename: devMode ? 'vendor.css' : 'vendor.[hash].css',
		})
	],
	optimization: {
		splitChunks: {
			chunks: 'all',
		},
		runtimeChunk: true,
	},
	output: {
		path: __dirname + '/dist',
		filename: 'js/[name].[chunkhash].js',
		chunkFilename: 'js/[name].[chunkhash].js',
		publicPath: '/',
	},
};

I think that I'm not seeing it from a correct point of view. I don't see the way how I can simulate WebPack 3 configuration with mini-css-extract-plugin

@jimblue
Copy link

jimblue commented Dec 5, 2018

Any update about this?

@ErikVeland
Copy link

ErikVeland commented Jan 7, 2019

I was researching this one and realized the answer is actually listed in the Webpack docs. I've implemented this and it works great!

Hope that helps someone!

This helped a lot. Only issue is while it generates the correct css, it ALSO generates an extraneous JS per chunk. How can I stop this?

const path = require('path'),
	CleanWebpackPlugin = require("clean-webpack-plugin"),
	MiniCssExtractPlugin = require("mini-css-extract-plugin"),
	CopyWebpackPlugin = require('copy-webpack-plugin'),
	src = path.resolve(__dirname, '../../html/media/mytributes/src'),
	dist = path.resolve(__dirname, '../../html/media/mytributes/dist');

function recursiveIssuer(m) {
  if (m.issuer) {
    return recursiveIssuer(m.issuer);
  } else if (m.name) {
    return m.name;
  } else {
    return false;
  }
}

module.exports = {
	mode: 'development',
	entry: {
		main: path.resolve(__dirname, src + '/js/main.js'),
		standalone: path.resolve(__dirname, src + '/styles.esdynamo/standalone.scss'),
		whitelabel: path.resolve(__dirname, src + '/styles.esdynamo/whitelabel.scss')
	},
	optimization: {
		splitChunks: {
			cacheGroups: {
				standaloneStyles: {
					name: 'standalone',
					test: (m,c,entry = 'standalone') => m.constructor.name === 'CssModule' && recursiveIssuer(m) === entry,
					chunks: 'all',
					enforce: true
				},
				whitelabelStyles: {
					name: 'whitelabel',
					test: (m,c,entry = 'whitelabel') => m.constructor.name === 'CssModule' && recursiveIssuer(m) === entry,
					chunks: 'all',
					enforce: true
				}
			}
		}
	},
	module: {
		rules: [
	        {
				test: /\.js$/,
				exclude: /(node_modules)/,
				loader: "babel-loader",
				options: {
					presets: ["@babel/preset-env"]
				}
			},
			{
				test: /\.scss$/,
				use: [
	                MiniCssExtractPlugin.loader, {
						loader: 'css-loader',
						options: {
							importLoaders: 1,
							url: false
						}
					},
					"postcss-loader", "sass-loader"
	            ]
			},
			{
				test: /\.(png|jpg|gif|svg)$/,
				exclude: [
	                path.resolve(__dirname, './node_modules'), ],
					use: {
					loader: 'file-loader',
					options: {
						name: '[path][name]-[hash].[ext]',
						outputPath: '../'
					},
				},
			},
		]
	},
	plugins: [
	new CleanWebpackPlugin([dist]),
	new MiniCssExtractPlugin({
		filename: "[name].css",
		chunkFilename: "[id].css"
	}),
	new CopyWebpackPlugin([{
		from: src + '/images',
		to: dist + '/images'
	}])
    ]
};

@pete-willard
Copy link

@ErikVeland You could try this: https://github.com/fqborges/webpack-fix-style-only-entries

@davidli16
Copy link

We have a use case for multiple instances of the plugin because we have two separate loader configurations, one for our legacy CSS that doesn't use CSS modules and one for our updated CSS that does use CSS modules. For this reason, we need to be able to run the plugin on each and extract them into the same output file.

@mmontes11
Copy link

Any update about this?
Having multiple instances of MiniCssExtractPlugin would be very useful and has many use cases, for example, having a separate .scss for our dark theme.

@alexander-akait
Copy link
Member

You should use splitChunks for this, no need multiple instance

@Connormiha
Copy link

Connormiha commented Jun 12, 2019

You should use splitChunks for this, no need multiple instance

@evilebottnawi
Can you show an example? I want to create many css bundles files(with different loader options) for each entry point(many themes). But JS bundle should be only one for each entry.
Old extract-webpack-text-plugin allows do it without creating excess js bundles.

umeboshi2 added a commit to umeboshi2/garkbit that referenced this issue Jul 3, 2019
@ArturBaybulatov
Copy link

I use extract-text-webpack-plugin to extract CSS and HTML at the same time in my multi-entry setup (separate bundles per "page"). Is there a way to achieve that with mini-css-extract-plugin?

const glob = require('glob');
const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');


const entry = glob.sync('pages/**/*.js', { cwd: 'src/' }).reduce(function(entry, pathName) {
    const pathNameWithoutExt = path.join(path.dirname(pathName), path.parse(pathName).name);
    entry[pathNameWithoutExt] = './' + pathName; // Leading dot is crucial
    return entry;
}, {});


const extractStyles = new ExtractTextPlugin('[name].css');
const extractHtml = new ExtractTextPlugin('[name].html');


module.exports = {
    context: path.resolve('src/'),

    entry,

    output: {
        path: path.resolve('public/'),
        filename: '[name].js',
    },

    module: {
        rules: [
            {
                test: /\.js$/,
                include: path.resolve('src/'),
                use: 'babel-loader',
            },

            {
                test: /\.less$/,
                include: path.resolve('src/'),
                use: extractStyles.extract({ use: ['raw-loader', 'postcss-loader', 'less-loader'] }),
            },

            {
                test: /\.html$/,
                include: path.resolve('src/modules/'),
                use: 'raw-loader',
            },

            {
                test: /\.html$/,
                include: path.resolve('src/pages/'),

                use: extractHtml.extract({
                    use: [
                        { loader: 'raw-loader' },
                        { loader: 'nunjucks-html-loader', options: { searchPaths: ['src/'] } },
                    ],
                }),
            },
        ],
    },

    plugins: [
        extractStyles,
        extractHtml,
    ],
};

@ScriptedAlchemy
Copy link
Contributor

Chunk splitting API really is the optimal solution here. I don't have it on hand but Google "the 100% correct way to code split" there's a medium article that comes up which shows some versitility of splitChunks. The webpack documentation site also has an updated section showing css splitting. Either under the code splitting, splitChunks, or mini css docs.

I've been digging through the next.js webpack builds. The latest release has an impressive build and their use of splitChunks has some CSS config if I remember correctly.

@davidli16
Copy link

davidli16 commented Jul 14, 2019

#45 (comment)

Is there a good solution for this use case I described above? We have two separate loader configurations for our CSS.. This is currently our sole blocker from being able to upgrade to Webpack 4.

{
    // CSS modules
    test: /\.scss$/,
    exclude: [/_[A-Za-z]+\.scss/],
    use: ExtractTextPlugin.extract({
        use: [
            {
                loader: 'css-loader',
                options: {
                    modules: true,
                    importLoaders: 1,
                    minimize: isProd,
                },
            },
            {
                loader: 'sass-loader',
                {
                    includePaths: [
                        path.join(options.root, 'src'),
                    ],
                },
            },
        ],
        fallback: 'style-loader',
    }),
},
{
    // Legacy CSS files (no CSS modules)
    test: /_[A-Za-z]+\.scss$|[A-Za-z]+\.css$/,
    use: ExtractTextPlugin.extract({
        use: [
            {
                loader: 'css-loader',
                options: {
                    importLoaders: 1,
                    minimize: isProd,
                },
            },
            {
                loader: 'sass-loader',
                options: {
                    includePaths: [
                        path.join(options.root, 'src'),
                    ],
                },
            },
        ],
        fallback: 'style-loader',
    }),
},

@alexander-akait
Copy link
Member

alexander-akait commented Jul 16, 2019

Closed, this thread looks like a spam, it is hard to track something here, no need support multiple instances, you can split your css using splitChunks (as written above multiple times). Anyway if you have problems with splitting please open new issue with reproducible test repo and please don't spam your problem into someone else's issue.

Note: if you use vue-cli, angular-cli, CFA, next.js or other boilerplate solution please open issue in their repo before open issue here. Some boilerplate solutions can use complex logic for splitting and you configuration potentially break/override their original splitting.

@bjankord
Copy link

I understand closing this issue as people were starting to add similar related issues but the original issue that @james-s-turner logged never seemed to be addressed/resolved.

The conversation turned into people wondering how to code split CSS.

@james-s-turner provided a repro with a test case: https://github.com/james-s-turner/webpack4-multi-theme/blob/master/webpack-config.js

I'm trying to accomplish the same thing as @james-s-turner was trying to do, but haven't had much luck reading through docs on splitChunks: { cacheGroups }

@alexander-akait
Copy link
Member

alexander-akait commented Jul 16, 2019

@bjankord please open new issue with reproducible test repo, i will write how to solve this and we update docs for this case, thanks

@bjankord
Copy link

bjankord commented Jul 16, 2019

Sounds good, I appreciate the feedback @evilebottnawi! 👍
Edit: I've logged #427 to scope the feature request specific to generating multiple theme files from one common shared CSS input.

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