CSSO + ExtractTextPlugin in webpack not restructuring extracted page

396 Views Asked by At

I'm trying to create a scaffolding for my react project and learn webpack in the process. I've been trying to get my css modularized in their own component folders and get them extracted into a single styles.css file in the build folder.

The problem is that in each of those modularized .css files, I have media queries and sometimes they overlap in terms of specific rules for the same width breakpoint. This results in a bunch of duplicate @media all and (min-width: 400px)-esque statements in my extracted styles.css file.

So to remedy that I discovered this csso plugin that seemed to do the trick in their online tool. It merged (by restructuring) all those duplicate @media statements into one and put the different rules in there. The problem is that in my project, it will only do that if the duplicate @media statements come from the same file (which shouldn't happen). If they come from different files (which is the problem i'm trying to solve), they don't get merged.

Now, I'm not sure how those plugins work in the background but I imagine csso-webpack-plugin is parsing all the files and then adding the result to the bundle and THEN the extraction to styles.css takes place, and not parsing styles.css directly. Can anyone think of a solution for this problem?

My production config is as follows:

const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const webpack = require('webpack');
const HtmlWebPackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const CssoWebpackPlugin = require('csso-webpack-plugin').default;

module.exports = {
  entry: {
    app: './src/index.js'
  },
  module:{
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader'
      },
      {
        test: /\.css$/,
        use: ExtractTextPlugin.extract({
          fallback: 'style-loader',
          use: [
            'css-loader',
            'csso-loader'
          ]
        })
      }
    ]
  },
  plugins:[
    new CleanWebpackPlugin(['build']),
    new HtmlWebPackPlugin(),
    new ExtractTextPlugin('styles.css'),
    new webpack.optimize.UglifyJsPlugin({
      beautify: false,
      mangle: {
        screw_ie8: true,
        keep_fnames: true
      },
      compress: {
        screw_ie8: true
      },
      comments: false
    }),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      minChunks(module){
        var context = module.context;
        return context && (context.indexOf('node_modules') > 0);
      }
    })
  ],
  output: {
    filename: '[name].bundle.min.js',
    path: path.resolve(__dirname, 'build')
  }
} 

My index.css looks like this:

@media all and (max-width: 100px){
  .lorelei{
    font-size: 50px;
  }
}

My App.css:

@media all and (max-width: 100px){
  .loboto{
    font-size: 30px;
  }
}


@media all and (max-width: 100px){
  .jonas{
    font-size: 30px;
  }
}

And the resulting styles.css

@media all and (max-width:100px){.lorelei{font-size:50px}}@media all and (max-width:100px){.jonas,.loboto{font-size:30px}}
2

There are 2 best solutions below

4
On

CssoWebpackPlugin will process already extracted bundle and not required csso-loader at all.

For restructuring you should apply CssoWebpackPlugin inside plugins section into your webpack config, like — new CssoWebpackPlugin().

Section rules:

{
    test: /\.css$/,
    use: ExtractTextPlugin.extract({
    fallback: 'style-loader',
    use: 'css-loader'
}

Section plugins:

plugins: [
    /* ... */
    new ExtractTextPlugin('styles.css'),
    new CssoWebpackPlugin(), // <==== it's very important!
    /* ... */
]

And your complete config should looks like:

const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const webpack = require('webpack');
const HtmlWebPackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const CssoWebpackPlugin = require('csso-webpack-plugin').default;

module.exports = {
  entry: {
    app: './src/index.js'
  },
  module:{
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader'
      },
      {
        test: /\.css$/,
        use: ExtractTextPlugin.extract({
          fallback: 'style-loader',
          use: 'css-loader'
        })
      }
    ]
  },
  plugins:[
    new CleanWebpackPlugin(['build']),
    new HtmlWebPackPlugin(),
    new ExtractTextPlugin('styles.css'),
    new CssoWebpackPlugin(),
    new webpack.optimize.UglifyJsPlugin({
      beautify: false,
      mangle: {
        screw_ie8: true,
        keep_fnames: true
      },
      compress: {
        screw_ie8: true
      },
      comments: false
    }),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      minChunks(module){
        var context = module.context;
        return context && (context.indexOf('node_modules') > 0);
      }
    })
  ],
  output: {
    filename: '[name].bundle.min.js',
    path: path.resolve(__dirname, 'build')
  }
}

After that changes result will merged good:

@media all and (max-width:100px){.lorelei{font-size:50px}.jonas,.loboto{font-size:30px}}
0
On

Since the solution to my problem wasn't a safe solution to all cases, but it was something I needed anyway, I tried looking for a different solution and found one by using the group-media-queries-plugin first on the extracted styles.css file first and then using csso to minify the result. I found it as a CLI plugin so I ended up using both it and CSSO CLI via npm scripts.

My npm script ended up being rather verbose but it gets the work done:

  "scripts": { "build:prod": "webpack --env=prod --progress --profile --colors && group-css-media-queries ./build/styles.css ./build/styles.css && csso ./build/styles.css ./build/styles.min.css"}