0

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}}
mrtiev
  • 43
  • 1
  • 2
  • 4

2 Answers2

1

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}}
zoobestik
  • 11
  • 2
  • I added an example for this case for you: https://github.com/zoobestik/stackoverflow-45870309 – zoobestik Aug 25 '17 at 13:59
  • That is what I had originally. I switched to the csso-loader to see if there would be any difference. Just in case I tried the CssoWebackPlugin again following your code but it was the same that i had earlier and it still produces duplicated results. Just to check, I ran the CLI version of csso on my styles.css (unminified) and the minification resulted in the same duplicated thing! When I manually moved the first media query statement to be just above the other 2 media queries and ran the cli tool again, THEN it properly deduped. Is that a feature? Doesn't feel right. – mrtiev Aug 25 '17 at 15:03
  • It's possible if you have other css rules with `font-size`. Is it exists restructuring will be not safe. :( Sad but you should provide some more information for **csso** for better optimization. It's named [scopes](https://github.com/css/csso#scopes), easiest way to do it use [localIdentName](https://github.com/webpack-contrib/css-loader) or `getLocalIdent` and [this solution](https://github.com/zoobestik/csso-webpack-plugin/issues/5#issuecomment-314832747). It will be provided in `CssoWebackPlugin` as preset in the future. But and without `scopes` csso + restructuring still best css minifer. – zoobestik Aug 25 '17 at 17:12
  • Thank you for your reply, zoobestik and the effort you put into solving my problem. In the end, I found that tackling the problem from a different angle would be easier. I eventually ended up solving the problem and used CSSO in conjunction with a different plugin to get the result I wanted. Thanks again for the replies! – mrtiev Aug 25 '17 at 18:25
0

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"}
mrtiev
  • 43
  • 1
  • 2
  • 4