Mini-css-extract-plugin: Support multiple instances of MiniCssExtractPlugin

Created on 20 Mar 2018  ·  57Comments  ·  Source: webpack-contrib/mini-css-extract-plugin

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.

Most helpful comment

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

All 57 comments

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

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

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]'],

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!

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?

@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 ;-)

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

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.

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

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

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.

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

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({

Somebody can provide real use case for two instance?

@evilebottnawi this is a real case I believe :
https://github.com/webpack-contrib/mini-css-extract-plugin/issues/45#issuecomment-374625122

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

@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

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

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.

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",
          },
        },
      },
    },
  },

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?

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

@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 :+1:

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

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.

@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

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

I'm just trying to be practical.

I leave this issue open for questions. Please try to use splitChunks.cacheGroups for code splitting first and when mini-css-extract-plugin automatically extract your css. Feel free to question.

I can't find any documentation on how to use splitChunks. Can you please forward me to the docs?

https://github.com/webpack/webpack.js.org/blob/master/src/content/plugins/split-chunks-plugin.md

Don't bother looking at the official docs on ~https://webpack.js.org~ - the Webpack 4 docs is yet to land officially. Maybe they will land when Webpack 5 ships. 😆

@garygreen thanks, we works on docs, but we need more contributors :disappointed:

thanks, we works on docs, but we need more contributors

Honestly, I don't think Webpack 4 should of shipped if the docs weren't ready. There were blog posts going around selling Webpack 4 yet how are us mere mortals suppose to upgrade without any guidance / documentation? It's not ready, so it's only a partial release atm, IMO.

Personally, I'm skipping W4 and looking forward to W5. Hopefully everything will be ironed out by then. 😃

@garygreen yes, we got a lesson, next major release will be with migration guide and updated docs. webpack always will be free (we use crowding for rewards contributors). In spite of this, we still have a big drawback with contributors. Right now i am one on webpack-contrib org :smile:

@evilebottnawi Thanks for maintaining this beast!

Just to give an update :

We got it working perfectly , except #85 is now going to cause an issue.

Here is the repo for anyone else trying following :

https://github.com/thecrypticace/webpack-css-chunks-test/

@lukepolo Awesome, confirm https://github.com/webpack-contrib/mini-css-extract-plugin/issues/85 it is bug, need fix, feel free to PR

@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

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,
    ....
]

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?

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!

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

@dwatts3624 feel free to send a PR to docs :+1:

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

Any update about this?

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

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.

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.

You should use splitChunks for this, no need multiple instance

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.

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

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.

https://github.com/webpack-contrib/mini-css-extract-plugin/issues/45#issuecomment-486369069

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',
    }),
},

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.

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 }

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

Sounds good, I appreciate the feedback @evilebottnawi! 👍
Edit: I've logged https://github.com/webpack-contrib/mini-css-extract-plugin/issues/427 to scope the feature request specific to generating multiple theme files from one common shared CSS input.

Was this page helpful?
0 / 5 - 0 ratings