postcss-loader: Doesn't work with webpack 2

Hello guys. Looks like it doesn’t works with the latest webpack 2 beta.

        loaders: [
            {
                test: /\.css$/,
                loader: ExtractTextPlugin.extract({ fallbackLoader: 'style-loader', loader: 'css-loader!postcss-loader' })
            },

Then I ran webpack-dev-server and it doesn’t finish.

Some guys have trouble too here https://github.com/webpack/webpack/issues/2812

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Comments: 67 (17 by maintainers)

Most upvoted comments

@panjiesw You’ve pretty much got it. Here’s an excerpt from my webpack.config.js. Hopefully anyone else looking for how to get webpack@2 to play nice with postcss-loader will see this because I had to spend several hours crawling github seeing as webpack@2 is in beta there’s no documentation. I have tested this, and at of the time I’m posting this, using the latest releases, it works, but may require some tweaking to fit into your setup.

const filePath = require('./paths');
const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
    devtool: 'source-map',
    resolve: {
        modules: [ 'node_modules', filePaths.SRC ],
        extensions: [ '.css', '.ts', '.tsx', '.js', '.jsx' ]
    },
    output: {
        path: filePaths.DIST,
        publicPath: '/',
        filename: '[name].js'
    },
    module: {
        loaders: [
            // .... other loaders
            {
                test: /\.css$/,
                include: [ filePaths.src ],
                loader: ExtractTextPlugin.extract({
                    fallbackLoader: 'style-loader',
                    loader: [
                        { loader: 'css-loader', query: { modules: true, sourceMaps: true } },
                        'postcss-loader'
                    ]
                })
            }
        ]
    },
    plugins: [ // https://github.com/webpack/webpack/issues/3018#issuecomment-248633498
        new webpack.LoaderOptionsPlugin({
            options: {
                context: filePaths.root,
                postcss: [ // <---- postcss configs go here under LoadOptionsPlugin({ options: { ??? } })
                    require('postcss-cssnext'),
                    require('lost')(),
                    require('postcss-reporter')()
                ]
                // ...other configs that used to directly on `modules.exports`
            }
        }),
        new ExtractTextPlugin({ filename: 'css/[name].css', disable: false, allChunks: true })
    ]
};

No docs, it’s webpack you know. Anyway I found this, take a look please at loaders section https://gist.github.com/sokra/27b24881210b56bbaff7

@StevenIseki that doesn’t work for me too. This is the only way I managed to make it work using Css Modules, ExtractTextPlugin and the Autoprefixer plugin.

postcss.config.js

module.exports = {
  plugins: {
    autoprefixer: {
      browsers: ['last 2 versions']
    },
  },
};

webpack.config.js

import webpack from 'webpack'
import HtmlWebpackPlugin from 'html-webpack-plugin'
import ExtractTextPlugin from 'extract-text-webpack-plugin'
import autoprefixer from 'autoprefixer'

...
const CSS_REGEX = /\.css$|\.scss$/

const config = options => {
  return {
    ...
    plugins:[
      new ExtractTextPlugin('bundle.css'),
      ...
    ],
    module: {
      rules: [
        {
          test: CSS_REGEX,
          loader: ExtractTextPlugin.extract({
            fallbackLoader: "style-loader",
            loader: [
              { loader: 'css-loader', query: { modules: true, importLoaders: 2, localIdentName: '[name]__[local]__[hash:base64:5]' } },
              { loader: 'postcss-loader' },
              { loader: 'sass-loader', options: {} }
            ]
          }),
        },
        ...
      ]
    }
  }
}
export default config

package.json

...
"webpack": "^2.2.0-rc.1",
"webpack-dev-server": "^2.2.0-rc.0"
"extract-text-webpack-plugin": "^2.0.0-beta.4",
"css-loader": "^0.26.1",
"postcss-loader": "^1.2.1",
"sass-loader": "^4.1.0",
"style-loader": "^0.13.1",
"css-loader": "^0.26.1",
...

@StevenIseki

...
module: {
    rules: [
      { 
        test: /\.css$/, 
        use: [
          'style-loader',
          { loader: 'css-loader', options: { modules: true, importLoaders: 1 } },
          { loader: 'postcss-loader', options: { plugins: () => [...plugins] } },
        ]
      },
      { 
        test: /\.js$/,
        use:  [
           { loader: 'babel-loader', options: { presets: ['es2015', 'react', 'stage-2'] } }
        ],
        exclude: /node_modules/, 
      }
    ]
  }
...

loaders -> rules, loader -> use, LoaderOptionsPlugin was a ‘polyfill’ required in webpack =< 2.1.0-beta.24and is unnessecary beginning from 2.1.0-beta.25. This is the final syntax for webpack@2

The loader configuration in webpack 2 itself has changed, which, i think, requires you to set the loader name without the query string.

So maybe you should change the config to this (untested):

ExtractTextPlugin.extract({
  fallbackLoader: "style-loader",
  loader: [
    { loader: 'css-loader', query: {sourceMap: true} },
    { loader: 'postcss-loader' }
  ]
})

I’m not so sure about the order, but you get the idea.

@francoisromain

    test: /\.css$/,
      use: ExtractTextPlugin.extract({
-       fallbackLoader: 'style-loader',
+       fallback: 'style-loader',
-        loader: [{
+        use: [{
-         loader: 'css-loader?sourceMap',
+         loader: 'css-loader',
+         options: { sourceMap: true, importLoaders: 1 }
        }, {
-        loader: 'postcss-loader?sourceMap',
+        loader: 'postcss-loader',
          options: {
+          sourceMap: true
            plugins: () => [
              postcssImport({ addDependencyTo: webpack }),
              postcssCssnext({
                browsers: ['last 2 versions', 'ie >= 9'],
                compress: true,
              }),
              postcssInlineSvg,
            ],
          },
        }],
      })

but not atm 😛 (v2-rc*), wait until v2@latest is out

@edmundo096 webpack v2.2.1 ships the ident fix, it’s finally working now

@StevenIseki , @michael-ciniawsky : Came up against this issue with the postcss-loader blowing up when I tried to pass it the autoprefixer plugin as well. Found a solution that worked for me at the bottom of this thread: -

{
  loader: 'postcss-loader',
  options: { ident: 'postcss', plugins: () => [ require('plugin') ] }
}

Hadn’t seen the use of ident in this thread yet so I figured I’d post this here in case anyone else finds themselves here after googling this problem.

Also, I didn’t want another smelly config file in my repo root… 😜

Webpack docs regarding Complex Options

I am using the following setup for postcss-loader with webpack 2 🔽 The important part is new webpack.LoaderOptionsPlugin({ options: { postcss: [ autoprefixer ] } }) in plugins and !postcss-loader in css-loader.

const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin');
const autoprefixer = require('autoprefixer')

module.exports = {
  entry: './index.js',
  devtool: 'inline-source-map',
  output: {
    filename: 'bundle.js',
    publicPath: ''
  },
  module: {
    loaders: [
      { 
        test: /\.css$/, 
        loader: 'style-loader!css-loader?modules&importLoaders=1!postcss-loader' 
      },
      { 
        test: /\.js$/, exclude: /node_modules/, 
        loader: 'babel-loader?presets[]=es2015&presets[]=react&presets[]=stage-2' 
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({ title: 'Example', template: './index.html' }),
    new webpack.LoaderOptionsPlugin({ options: { postcss: [ autoprefixer ] } })
  ],
}

So basically, what I conclude after reading and some researching, there are 4 ways (as for now) to load the PostCSS configuration for Webpack 2:

  1. Using a postcss.config.js file, which must be “anywhere down the file tree” where Webpack matches a file (e.g. if a match was on a \assets\stylesheets file, postcss.config.js must be anywhere inside that path). This is due how postcss-loader works. It uses postcss-load-config module, which uses cosmiconfig module (see more in davidtheclark/cosmiconfig). That is why there is almost no documentation about where one can place that file. Placing it on the root almost assures that postcss.config.js will just work, although this is not prefered by everybody.

As a side note, since this depends mainly on cosmiconfig we also have other 3 ways here:

  • package.json property (anywhere down the file tree). An example reached reading this is here
  • JSON or YAML “rc file” (anywhere down the file tree).
  • CLI --config argument.
  • As already commented, .config.js CommonJS module (anywhere down the file tree).
  1. OLD workaround for Webpack <= 2.2.0, using the ident on the options object (as @owenDods commented). Unnecessary in the in Webpack >= 2.2.1, see point 4
{
  loader: 'postcss-loader',
  options: {
    ident: 'postcss',     // <- This..
    plugins: () => [ require('plugin') ]
  }
}
  1. Using the webpack.LoaderOptionsPlugin. This makes postcss-loader load the options (and plugins) as if we were using Webpack 1. See more here https://webpack.js.org/plugins/loader-options-plugin/

The last 2 are handled respectively by this line of code: var options = params.plugins || loader.options.postcss; in the index.js#L48. All of this 3 proaches support an array of plugins.

Maybe documentating or mentioning this 3 ways in the Webpack 2 section on the README.md would be nice in my opinion @ai

Hope this helps someone else (avoiding possible headaches) in the future.


EDIT: Update as for Webpack >= v2.2.1

  1. The preferred way (without the ident workaround for webpack <= 2.2.0).
{
  loader: 'postcss-loader',
  options: {
    plugins: () => [ require('plugin') ]
  }
}

For future lost soul who is also stuck with webpack.

For those who want to use post-css, extract-text-plugin, resolve-url-loader, css-module and react-css-module with webpack 2 and source map enable Here is how

package.json

{
  "devDependencies": {
    "babel-cli": "^6.18.0",
    "babel-core": "^6.21.0",
    "babel-loader": "^6.2.10",
    "babel-plugin-transform-decorators-legacy": "^1.3.4",
    "babel-plugin-transform-remove-console": "^6.8.0",
    "babel-preset-es2015": "^6.18.0",
    "babel-preset-react": "^6.16.0",
    "babel-preset-stage-0": "*",
    "css-loader": "^0.26.1",
    "extract-text-webpack-plugin": "2.0.0-beta.4",
    "file-loader": "^0.9.0",
    "imports-loader": "^0.7.0",
    "node-sass": "^4.1.1",
    "postcss-cssnext": "^2.9.0",
    "postcss-import": "^9.0.0",
    "postcss-loader": "^1.2.1",
    "precss": "^1.4.0",
    "react-css-modules": "^4.1.0",
    "resolve-url-loader": "^1.6.1",
    "sass-loader": "^4.1.1",
    "style-loader": "^0.13.1",
    "url-loader": "^0.5.7",
    "webpack": "2.1.0-beta.25",
    "webpack-dev-server": "2.1.0-beta.10",
  }
}

webpack.config.babel.js



import ExtractTextPlugin from 'extract-text-webpack-plugin'
import path from 'path'

export default {
  devtool: 'source-map',
  entry: './app/index.js',
  output: {
    filename: 'bundle.js',
    path: path.join(__dirname, 'dist'),
    publicPath: 'http://localhost:1111/dist/'
  },
  devServer: {
    historyApiFallback: true,
    contentBase: path.join(__dirname, 'dist'),
    noInfo: true,
    port: 1111
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: 'babel-loader'
      },
      {
        test: /\.(jpe?g|gif|png|svg)$/i,
        use: 'file-loader?name=/image/[name].[ext]'
      },
      {
        test: /\.(css|scss)$/,
        loader: ExtractTextPlugin.extract({
          loader: [
            { loader: 'css-loader?sourceMap&modules&importLoaders=1&localIdentName=[path]___[local]___[hash:base64:5]'},
            { loader: 'resolve-url-loader?sourceMap'},
            { loader: 'sass-loader?sourceMap'},
            { loader: 'postcss-loader?sourceMap' },
          ]
        })
      }
    ]
    
  },
  plugins: [
    new ExtractTextPlugin({
      filename:'bundle.css',
      disable:false,
      allChunks: true
    }),

  ]
}

postcss.config.js

module.exports = {
	plugins: {
		'postcss-import': {},
		'postcss-cssnext': {
			browsers: ['last 2 versions', '> 5%'],
		},
	},
}

.babelrc

{
	"presets": [
		"es2015",
		"react",
		"stage-0"
	],
	"plugins": [
		[
			"react-css-modules",
			{
				"generateScopedName": "[path]___[local]___[hash:base64:5]"
			}
		]
	]
}

For those who don’t use url-resolve you can remove this

webpack.config.babel.js

{ loader: 'sass-loader'}, // remove this line
{ loader: 'resolve-url-loader'} // remove this line

For those who wonder why my webpack config file is webpack.config.babel.js not webpack.config.js

is because I use es6 syntax in webpack config, I had to name my config webpack.config.babel.js so webpack would understand the syntax.

@koutsenko

- require('autoprefixer')
+ require('autoprefixer')({ browsers: 'last 2 versions' })

@ai, yeah, didn’t liked the ident neither. More like a workaround and took me several Google searches to find it as a mere “workaround solution”, specially because it doccumented with intention for the Loader devs (I skipped it haha, https://webpack.js.org/guides/migrating/#loader-changes)

Have not digged more into it, but… There is any way that we could use options normally (without the ident)? Something like:

{
  loader: 'postcss-loader',
  options: {
    plugins: () => [ require('plugin') ]
  }
}

It’s something that could be fixed on PostCSS-loader? (or it’s something from Webpack 2 itself?) Or which of the ways do you recommend us using?

@owenDods 👍 but this is going away in webpack >= v2.3.x (next v2 minor), after talking with sokra about it #4109, the ident will be added by webpack and no config step required for complex options anymore. Also the reason this was not documented until yet 😛

Yep, and the config structure is the same:

ExtractTextPlugin.extract({ fallbackLoader: 'style-loader', loader: 'css-loader?sourceMap!postcss-loader'})

I also find that I have to give ident: 'something' to make @import work:

{
  entry: './src/css/test.css',
  output: {
    path: __dirname,
    filename: 'assets/dummy.css'
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ExtractTextPlugin.extract({
          use: [
            { loader: 'css-loader', options: { importLoaders: 1 } },
            {
              loader: 'postcss-loader',
              options: {
//                    ident: 'remove-this-and-it-fails',
                plugins: [ require('postcss-cssnext')() ]
              }
            }
          ]
        })
      }
    ]
  },
  plugins: [
    new ExtractTextPlugin("assets/bundle.css")
  ]
}

with test.css:

@import url('inner.css');

body {
  & div {
    color: red;
  }
}

leads to:

Module build failed: ModuleBuildError: Module build failed: TypeError: plugin is not a function

With ident: 'remove-this-and-it-fails' uncommented it works.

Here’s a complete example: https://github.com/AndreKR/issue-webpack-postcss-ident

Just spent some time fixing this issue in my react boilerplate.

Check this commit

I had to move my postcss plugins to a postcss.config.js. Also had to return an object in that file to get postcss-cssnext to work with browser settings.

Then had a weird issue with a production build if I use options instead of query for my .css files.

  "engines": {
    "node": "6.9.1",
    "npm": "3.10.8"
  },
"webpack": "2.1.0-beta.25",
"css-loader": "0.26.1",
"postcss": "5.2.6",
"postcss-browser-reporter": "0.5.0",
"postcss-cssnext": "2.9.0",
"postcss-focus": "1.0.0",
"postcss-load-config": "1.0.0",
"postcss-loader": "1.2.1",
"postcss-reporter": "3.0.0",
"postcss-simple-vars": "3.0.0",
"postcss-smart-import": "0.6.3",

@michael-ciniawsky What is [...plugins] ? I get the error PostCSS Config could not be loaded. Please check your PostCSS Config

Here is my config…

const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin');
const autoprefixer = require('autoprefixer')

module.exports = {
  entry: './index.js',
  devtool: 'inline-source-map',
  output: { filename: 'bundle.js', publicPath: '' },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          { loader: 'css-loader', options: { modules: true, importLoaders: 1 } },
          { loader: 'postcss-loader', options: { plugins: () => [...plugins] } },
        ]
      },
      {
        test: /\.js$/,
        use: [ { loader: 'babel-loader', options: { presets: ['es2015', 'react', 'stage-2'] } } ],
        exclude: /node_modules/,
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({ title: 'Example', template: './index.html' })
  ],
}

Okay here we go

Changing query to options fails

{ loader: 'sass-loader', options: { sourceMap: true } }

removing resolve-url-loader fails

// { loader: 'resolve-url-loader' },

PostCSS options inside. also fails

{
    loader: 'postcss-loader',
    options: {
        plugins: () => {
            return [
                require('autoprefixer')({
                    browsers: [
                        "> 5%",
                        "last 2 versions"
                    ]
                })
            ]
        }
    }
},

without ETWP also fails

{
    test: /\.s?css$/,
    use: [
        { loader: 'style-loader' },
        { loader: 'css-loader', options: { minimize: true, importLoaders: 1 } },
        {
            loader: 'postcss-loader',
            options: {
                plugins: () => {
                    return [
                        require('autoprefixer')({
                            browsers: [
                                "> 5%",
                                "last 2 versions"
                            ]
                        })
                    ]
                }
            }
        },
        // { loader: 'resolve-url-loader' },
        { loader: 'sass-loader', options: { sourceMap: true } }
    ]
},

without ETWP + ident = Works

{
    test: /\.s?css$/,
    use: [
        { loader: 'style-loader' },
        { loader: 'css-loader', options: { minimize: true, importLoaders: 1 } },
        {
            loader: 'postcss-loader',
            options: {
                ident: 'postcss',
                plugins: () => {
                    return [
                        require('autoprefixer')({
                            browsers: [
                                "> 5%",
                                "last 2 versions"
                            ]
                        })
                    ]
                }
            }
        },
        // { loader: 'resolve-url-loader' },
        { loader: 'sass-loader', options: { sourceMap: true } }
    ]
},

You can have a look at this with the real files i am using here: https://github.com/milewski/portfolio

@gazpachu I got the same issue. After some research, I concluded how the loading of postcss.config.js worked.

I recommend you read my suggestions in the comment above https://github.com/postcss/postcss-loader/issues/92#issuecomment-276036682. Summed up, if you use postcss.config.js, it must be “anywhere down the file tree” where Webpack matches a file (in your case, of /src/app/components/bundle.scss file, postcss.config.js must be anywhere inside that path; using the same folder level where the webpack.config.js file is located, as you said, wouldn’t work).

Otherwise, you should pass the options by any of other ways used by cosmiconfig, or indicate its location by the query params ?config as you have used, which I believe is processed at https://github.com/postcss/postcss-loader/blob/master/index.js#L54

For the prefixes, Autoprefixer applies it depending the current browser use. See the README first lines of https://github.com/postcss/autoprefixer.

@gazpachu dependant on what browsers you have defined. Add some older browsers and it should prefix as required

Finally I had something working (with webpack 2.2.1. and extract text plugin @2.0.0-beta). I’ll put it here:

    test: /\.css$/,
      use: ExtractTextPlugin.extract({
        fallbackLoader: 'style-loader',
        loader: [{
          loader: 'css-loader?sourceMap',
        }, {
          loader: 'postcss-loader?sourceMap',
          options: {
            plugins: () => [
              postcssImport({ addDependencyTo: webpack }),
              postcssCssnext({
                browsers: ['last 2 versions', 'ie >= 9'],
                compress: true,
              }),
              postcssInlineSvg,
            ],
          },
        }],
      })

@edmundo096 It’s a webpack core issue, which can’t be fixed within loaders (the query is already malformed when the loader receives it), I addressed it to sokra and it will be fixed in webpack v2.3+, see the PR I linked above for more info, it basically has to do with how webpack stringifies the loader query. Your example is the desired syntax and will be working in webpack v2.3+

Ah I see the execution order is different - for me postcss runs first, css-loader second. Maybe that’s causing an issue on your end if css-loader is producing JS, and later post-css loader is expecting css input?