Skip to content
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

[v2] How to manually specify postcss plugins? #5778

Closed
steverandy opened this issue Jun 7, 2018 · 32 comments
Closed

[v2] How to manually specify postcss plugins? #5778

steverandy opened this issue Jun 7, 2018 · 32 comments
Labels
type: question or discussion Issue discussing or asking a question about Gatsby

Comments

@steverandy
Copy link

I read the new doc about migrating to v2 (https://v2--gatsbyjs.netlify.com/docs/migrating-from-v1-to-v2/#manually-specify-postcss-plugins)
I would like to try v2 alpha, but not sure how to configure postcss plugins.

Is there any more detailed example on how to manually configure postcss plugins?

Thanks.

@m-allanson
Copy link
Contributor

Good question - there isn't yet. This step is kind of a placeholder, the plan is to add a new plugin that will allow you to configure PostCSS for your project with a plain old PostCSS config file. Check out the issue discussing this: #3284

If you're comfortable configuring webpack, you could do this using Gatsby's onCreateWebpackConfig and setWebpackConfig hooks. See the v2 docs on adding a custom webpack config for a bit more info. There's also an old PR that implements some of this functionality - which could be a useful reference.

@szimek
Copy link
Contributor

szimek commented Jun 21, 2018

I've tried to add postcss-import and postcss-cssnext plugins, but have some strange error: #4428 (comment) :/

Additionally, after adding postcss-cssnext it complains that it already includes autoprefixer plugin, so it's included twice, because it's also added by default by Gatsby.

@jeroenransijn
Copy link

jeroenransijn commented Jun 21, 2018

Edit

I realize that you can use actions.setWebpackConfig instead of actions.replaceWebpackConfig as long as you make sure the test reads as: /\.css$/ exactly (I was using /\.css/).

Original

I am having some issues migrating from v1 to v2. I trying to reconfigure the .css test and add my own options:

actions.setWebpackConfig({
    module: {
      rules: [
        {
          test: /\.css$/,
          use: [
            loaders.style(),
            loaders.css({ importLoaders: 1 }),
            loaders.postcss()
          ],
        },

I think the problem I am running into is that the .css test is already defined by Gatsby, and this will simply add the loaders on top of the default stack. It seems like replaceWebpackConfig might be required, but I am not comfortable nuking the whole config and potentially merging the default config with the override myself. Seems odd that would be the way to do it.

Usage

My usage is simply the following in components/Layout.js:

import './../css/index.css'

And then in my index.css I import a bunch of files.

Since cssnext is being deprecated I am trying to use https://preset-env.cssdb.org/ instead.

Basically I am just migrating from v1 to v2 and this is blocking me sadly.

@jeroenransijn
Copy link

As soon as I add any type of loader I am getting errors. It doesn't matter if I only have one loader or a ton.


  actions.setWebpackConfig({
    module: {
      rules: [
        {
          test: /\.css$/,
          use: [
            loaders.style(),
            loaders.miniCssExtract(),
            loaders.css({ importLoaders: 1 }),
            loaders.postcss(),
          ],
        },
      ],
    },
  })

The error:

(5:1) Unknown word

  3 | // load the styles
  4 | var content = require("!!../../node_modules/style-loader/index.js??ref--10-1!../../node_modules/css-loader/index.js??ref--10-2!../../node_modules/postcss-loader/lib/index.js??postcss-3!../../node_modules/style-loader/index.js??ref--12-0!../../node_modules/css-loader/index.js??ref--12-1!./index.css");
> 5 | if(typeof content === 'string') content = [[module.id, content, '']];
    | ^
  6 | // Prepare cssTransformation
  7 | var transform;

@jeroenransijn
Copy link

jeroenransijn commented Jun 21, 2018

After trying to make this work for a while I am still getting the above error. In the default webpack config. oneOf: [rules.cssModules(), rules.css()] is used. It seems that webpack-merge is used within: https://github.com/gatsbyjs/gatsby/blob/1fb19f9ad16618acdac7eda33d295d8ceba7f393/packages/gatsby/src/redux/reducers/webpack.js.

When using setWebpackConfig and adding a test for /\.css$/ with loaders the merge doesn't replace the oneOf rule in the original default. I am definitely no master on Webpack so have no idea if this is bad or not, but it's def not working for me.

Start Config

[
  {
    "test": {},
    "exclude": {},
    "use": [
      {
        "options": {
          "cacheDirectory": true,
          "babelrc": false,
          "sourceType": "unambiguous",
          "presets": [
            [
              "/Users/jeroenransijn/dev/src/github.com/segmentio/evergreen/docs/node_modules/@babel/preset-env/lib/index.js",
              {
                "loose": true,
                "modules": false,
                "useBuiltIns": "usage",
                "targets": {
                  "browsers": [
                    ">0.25%",
                    "not dead"
                  ]
                }
              }
            ],
            [
              "/Users/jeroenransijn/dev/src/github.com/segmentio/evergreen/docs/node_modules/@babel/preset-react/lib/index.js",
              {
                "useBuiltIns": true,
                "pragma": "React.createElement",
                "development": true
              }
            ],
            [
              "/Users/jeroenransijn/dev/src/github.com/segmentio/evergreen/docs/node_modules/@babel/preset-flow/lib/index.js",
              {}
            ]
          ],
          "plugins": [
            "/Users/jeroenransijn/dev/src/github.com/segmentio/evergreen/docs/node_modules/babel-plugin-remove-graphql-queries/index.js",
            "/Users/jeroenransijn/dev/src/github.com/segmentio/evergreen/docs/node_modules/react-hot-loader/babel.js",
            [
              "/Users/jeroenransijn/dev/src/github.com/segmentio/evergreen/docs/node_modules/@babel/plugin-proposal-export-default-from/lib/index.js",
              {}
            ],
            [
              "/Users/jeroenransijn/dev/src/github.com/segmentio/evergreen/docs/node_modules/@babel/plugin-proposal-class-properties/lib/index.js",
              {
                "loose": true
              }
            ],
            [
              "/Users/jeroenransijn/dev/src/github.com/segmentio/evergreen/docs/node_modules/@babel/plugin-syntax-dynamic-import/lib/index.js",
              {}
            ],
            [
              "/Users/jeroenransijn/dev/src/github.com/segmentio/evergreen/docs/node_modules/@babel/plugin-transform-runtime/lib/index.js",
              {
                "helpers": true,
                "regenerator": true,
                "polyfill": false
              }
            ]
          ]
        },
        "loader": "/Users/jeroenransijn/dev/src/github.com/segmentio/evergreen/docs/node_modules/babel-loader/lib/index.js"
      }
    ]
  },
  {
    "test": {},
    "use": [
      {
        "options": {},
        "loader": "/Users/jeroenransijn/dev/src/github.com/segmentio/evergreen/docs/node_modules/json-loader/index.js"
      },
      {
        "options": {},
        "loader": "/Users/jeroenransijn/dev/src/github.com/segmentio/evergreen/docs/node_modules/yaml-loader/index.js"
      }
    ]
  },
  {
    "use": [
      {
        "loader": "/Users/jeroenransijn/dev/src/github.com/segmentio/evergreen/docs/node_modules/url-loader/dist/cjs.js",
        "options": {
          "limit": 10000,
          "name": "static/[name]-[hash].[ext]"
        }
      }
    ],
    "test": {}
  },
  {
    "use": [
      {
        "loader": "/Users/jeroenransijn/dev/src/github.com/segmentio/evergreen/docs/node_modules/url-loader/dist/cjs.js",
        "options": {
          "limit": 10000,
          "name": "static/[name]-[hash].[ext]"
        }
      }
    ],
    "test": {}
  },
  {
    "use": [
      {
        "loader": "/Users/jeroenransijn/dev/src/github.com/segmentio/evergreen/docs/node_modules/url-loader/dist/cjs.js",
        "options": {
          "name": "static/[name]-[hash].[ext]"
        }
      }
    ],
    "test": {}
  },
  {
    "oneOf": [
      {
        "test": {},
        "use": [
          {
            "options": {},
            "loader": "/Users/jeroenransijn/dev/src/github.com/segmentio/evergreen/docs/node_modules/style-loader/index.js"
          },
          {
            "loader": "/Users/jeroenransijn/dev/src/github.com/segmentio/evergreen/docs/node_modules/css-loader/index.js",
            "options": {
              "minimize": false,
              "sourceMap": true,
              "camelCase": "dashesOnly",
              "localIdentName": "[name]--[local]--[hash:base64:5]",
              "modules": true,
              "importLoaders": 1
            }
          },
          {
            "loader": "/Users/jeroenransijn/dev/src/github.com/segmentio/evergreen/docs/node_modules/postcss-loader/lib/index.js",
            "options": {
              "ident": "postcss-1",
              "sourceMap": true
            }
          }
        ]
      },
      {
        "test": {},
        "use": [
          {
            "options": {},
            "loader": "/Users/jeroenransijn/dev/src/github.com/segmentio/evergreen/docs/node_modules/style-loader/index.js"
          },
          {
            "loader": "/Users/jeroenransijn/dev/src/github.com/segmentio/evergreen/docs/node_modules/css-loader/index.js",
            "options": {
              "minimize": false,
              "sourceMap": true,
              "camelCase": "dashesOnly",
              "localIdentName": "[name]--[local]--[hash:base64:5]",
              "importLoaders": 1
            }
          },
          {
            "loader": "/Users/jeroenransijn/dev/src/github.com/segmentio/evergreen/docs/node_modules/postcss-loader/lib/index.js",
            "options": {
              "ident": "postcss-2",
              "sourceMap": true
            }
          }
        ]
      }
    ]
  }
]

End Config

[
  {
    "test": {},
    "exclude": {},
    "use": [
      {
        "options": {
          "cacheDirectory": true,
          "babelrc": false,
          "sourceType": "unambiguous",
          "presets": [
            [
              "/Users/jeroenransijn/dev/src/github.com/segmentio/evergreen/docs/node_modules/@babel/preset-env/lib/index.js",
              {
                "loose": true,
                "modules": false,
                "useBuiltIns": "usage",
                "targets": {
                  "browsers": [
                    ">0.25%",
                    "not dead"
                  ]
                }
              }
            ],
            [
              "/Users/jeroenransijn/dev/src/github.com/segmentio/evergreen/docs/node_modules/@babel/preset-react/lib/index.js",
              {
                "useBuiltIns": true,
                "pragma": "React.createElement",
                "development": true
              }
            ],
            [
              "/Users/jeroenransijn/dev/src/github.com/segmentio/evergreen/docs/node_modules/@babel/preset-flow/lib/index.js",
              {}
            ]
          ],
          "plugins": [
            "/Users/jeroenransijn/dev/src/github.com/segmentio/evergreen/docs/node_modules/babel-plugin-remove-graphql-queries/index.js",
            "/Users/jeroenransijn/dev/src/github.com/segmentio/evergreen/docs/node_modules/react-hot-loader/babel.js",
            [
              "/Users/jeroenransijn/dev/src/github.com/segmentio/evergreen/docs/node_modules/@babel/plugin-proposal-export-default-from/lib/index.js",
              {}
            ],
            [
              "/Users/jeroenransijn/dev/src/github.com/segmentio/evergreen/docs/node_modules/@babel/plugin-proposal-class-properties/lib/index.js",
              {
                "loose": true
              }
            ],
            [
              "/Users/jeroenransijn/dev/src/github.com/segmentio/evergreen/docs/node_modules/@babel/plugin-syntax-dynamic-import/lib/index.js",
              {}
            ],
            [
              "/Users/jeroenransijn/dev/src/github.com/segmentio/evergreen/docs/node_modules/@babel/plugin-transform-runtime/lib/index.js",
              {
                "helpers": true,
                "regenerator": true,
                "polyfill": false
              }
            ]
          ]
        },
        "loader": "/Users/jeroenransijn/dev/src/github.com/segmentio/evergreen/docs/node_modules/babel-loader/lib/index.js"
      }
    ]
  },
  {
    "test": {},
    "use": [
      {
        "options": {},
        "loader": "/Users/jeroenransijn/dev/src/github.com/segmentio/evergreen/docs/node_modules/json-loader/index.js"
      },
      {
        "options": {},
        "loader": "/Users/jeroenransijn/dev/src/github.com/segmentio/evergreen/docs/node_modules/yaml-loader/index.js"
      }
    ]
  },
  {
    "use": [
      {
        "loader": "/Users/jeroenransijn/dev/src/github.com/segmentio/evergreen/docs/node_modules/url-loader/dist/cjs.js",
        "options": {
          "limit": 10000,
          "name": "static/[name]-[hash].[ext]"
        }
      }
    ],
    "test": {}
  },
  {
    "use": [
      {
        "loader": "/Users/jeroenransijn/dev/src/github.com/segmentio/evergreen/docs/node_modules/url-loader/dist/cjs.js",
        "options": {
          "limit": 10000,
          "name": "static/[name]-[hash].[ext]"
        }
      }
    ],
    "test": {}
  },
  {
    "use": [
      {
        "loader": "/Users/jeroenransijn/dev/src/github.com/segmentio/evergreen/docs/node_modules/url-loader/dist/cjs.js",
        "options": {
          "name": "static/[name]-[hash].[ext]"
        }
      }
    ],
    "test": {}
  },
  {
    "oneOf": [
      {
        "test": {},
        "use": [
          {
            "options": {},
            "loader": "/Users/jeroenransijn/dev/src/github.com/segmentio/evergreen/docs/node_modules/style-loader/index.js"
          },
          {
            "loader": "/Users/jeroenransijn/dev/src/github.com/segmentio/evergreen/docs/node_modules/css-loader/index.js",
            "options": {
              "minimize": false,
              "sourceMap": true,
              "camelCase": "dashesOnly",
              "localIdentName": "[name]--[local]--[hash:base64:5]",
              "modules": true,
              "importLoaders": 1
            }
          },
          {
            "loader": "/Users/jeroenransijn/dev/src/github.com/segmentio/evergreen/docs/node_modules/postcss-loader/lib/index.js",
            "options": {
              "ident": "postcss-1",
              "sourceMap": true
            }
          }
        ]
      },
      {
        "test": {},
        "use": [
          {
            "options": {},
            "loader": "/Users/jeroenransijn/dev/src/github.com/segmentio/evergreen/docs/node_modules/style-loader/index.js"
          },
          {
            "loader": "/Users/jeroenransijn/dev/src/github.com/segmentio/evergreen/docs/node_modules/css-loader/index.js",
            "options": {
              "minimize": false,
              "sourceMap": true,
              "camelCase": "dashesOnly",
              "localIdentName": "[name]--[local]--[hash:base64:5]",
              "importLoaders": 1
            }
          },
          {
            "loader": "/Users/jeroenransijn/dev/src/github.com/segmentio/evergreen/docs/node_modules/postcss-loader/lib/index.js",
            "options": {
              "ident": "postcss-2",
              "sourceMap": true
            }
          }
        ]
      }
    ]
  },
  {
    "test": {},
    "use": [
      {
        "loader": "raw-loader"
      }
    ]
  },
  {
    "test": {},
    "use": [
      {
        "options": {},
        "loader": "/Users/jeroenransijn/dev/src/github.com/segmentio/evergreen/docs/node_modules/style-loader/index.js"
      },
      {
        "loader": "/Users/jeroenransijn/dev/src/github.com/segmentio/evergreen/docs/node_modules/css-loader/index.js",
        "options": {
          "minimize": false,
          "sourceMap": true,
          "camelCase": "dashesOnly",
          "localIdentName": "[name]--[local]--[hash:base64:5]",
          "importLoaders": 1
        }
      },
      {
        "loader": "/Users/jeroenransijn/dev/src/github.com/segmentio/evergreen/docs/node_modules/postcss-loader/lib/index.js",
        "options": {
          "ident": "postcss-3",
          "sourceMap": true
        }
      }
    ]
  }
]

@jeroenransijn
Copy link

This is finally working with me with postcss-cssnext:

const path = require('path')
const webpack = require('webpack') // eslint-disable-line import/no-extraneous-dependencies
const postcssCssnext = require('postcss-cssnext')
const poscssImport = require('postcss-import')


exports.onCreateWebpackConfig = ({ actions, loaders, getConfig }) => {
  actions.setWebpackConfig({
    module: {
      rules: [
        {
          test: /\.example/,
          use: [{ loader: 'raw-loader' }],
        },
        {
          test: /\.css$/,
          use: [
            loaders.miniCssExtract(),
            // // 0 => no loaders (default); 1 => postcss-loader; 2 => postcss-loader, sass-loader
            loaders.css({ importLoaders: 1 }),

            loaders.postcss({
              ident: 'postcss',
              plugins: () => [poscssImport(), postcssCssnext()],
            }),
          ],
        },
      ],
    }
  })

  const configAfterSettings = getConfig()

  // Losing my mind here.
  const finalRules = configAfterSettings.module.rules.filter(rule => {
    if (Object.prototype.hasOwnProperty.call(rule, 'oneOf')) {
      // Nuke this rule.
      return JSON.stringify(rule).indexOf('style-loader') === -1
    }
    return true
  })

  // So much for immutability.
  configAfterSettings.module.rules = finalRules
  actions.replaceWebpackConfig(configAfterSettings)
}

@KyleAMathews
Copy link
Contributor

@jeroenransijn nice! Next steps would be using https://www.npmjs.com/package/postcss-load-config to load your plugins from a .postcssrc file. This code could then be wrapped into gatsby-plugin-postcss. Any interest in working on this? :-)

@szimek
Copy link
Contributor

szimek commented Jun 22, 2018

@jeroenransijn Hey! Do you have any idea why doing this throws an error:

exports.onCreateWebpackConfig = ({ actions, rules }) => {
  actions.setWebpackConfig({
    module: {
      rules: [rules.postcss()],
    },
  });
};

Even though I'm not specifying any postcss plugins there, I'm still getting an error very similar to yours:

(1:1) Unknown word

> 1 | exports = module.exports = require("../../../../node_modules/gatsby/node_modules/css-loader/lib/css-base.js")(true);
    | ^
  2 | // imports
  3 | exports.i(require("-!../../../../node_modules/gatsby/node_modules/css-loader/index.js??ref--11-0!../../../../node_modules/postcss-loader/lib/index.js??postcss-3!./@freeletics/web-package-particle/src/physics/variables.css"), "");

 @ ./src/templates/IndexPage.module.css 4:14-337 18:2-22:4 18:336-22:3 19:20-343

I'll later try your solution, even though it looks a bit complex ;) Do you know if it replaces postcss config for all Gatsby stages? I'm not sure if it's really true, but I might need different postcss plugins in development and production builds (though maybe I won't if e.g. css-loader can minify css as well)

@jeroenransijn
Copy link

jeroenransijn commented Jun 22, 2018

@szimek that is exactly the issue I ran into. The main problem is that there is already a loader for CSS/CSS modules in the default Gatsby Webpack config. setWebpackConfig does not remove or replace the default config for that. You end up running two loaders on the same import. Which is the error you experiencing.

The following super ugly hack gets the config, nukes that rule, and then replaces the Webpack config:

const configAfterSettings = getConfig()

// Losing my mind here.
const finalRules = configAfterSettings.module.rules.filter(rule => {
  if (Object.prototype.hasOwnProperty.call(rule, 'oneOf')) {
    // Nuke this rule.
    return JSON.stringify(rule).indexOf('style-loader') === -1
  }
  return true
})

// So much for immutability.
configAfterSettings.module.rules = finalRules
actions.replaceWebpackConfig(configAfterSettings)

I talked to Kyle about some ideas around this. Imo, preferably we would have something inside of Gatsby to nuke this rule in a more graceful matter. The two following things come to mind:

actions.removeDefaultWebpackCSSLoader()

Or something more like the following to allow disabling other default loaders. Although I am not sure if there is a use case for this.

actions.setDefaultWebpackLoaders({
   css: false,
})

@KyleAMathews I don't mind giving the plugin a try, but I think we should have something in Gatbsy to do the above without having to rely on hacks. It might be I am missing something here that would be more graceful without changing Gatsby..

@jquense
Copy link
Contributor

jquense commented Jun 22, 2018

I don't think there should be problem generally with adding another css loader even without removing the existing one, It should just handle the files first leaving nothing for the later plugin to handle unless i'm missing something (believe i've done this is exact thing before without problem) I'm wondering if errors are do to not handling the various build stages correctly. It's not enough to add a new loader ,you need to add them at the right stages, like the existing one (and less, sass, style plugins as well).

@jeroenransijn
Copy link

@jquense I am not all that seasoned in Webpack so I might be missing something obvious. When I was trying this I was actually surprised I was able to solve this problem by removing the current loader. I was expecting Webpack to be able to understand what was going on.

Additionally, I think less, sass are generally less of an issue because they occupy a different extension vs. adding more loaders to the same extension.

@szimek
Copy link
Contributor

szimek commented Jun 26, 2018

Currently Gatsby has slightly different webpack rules for handling CSS files depending on the command (develop and build) and each stage (develop-html, develop, build-javascript etc.). Additionally, it handles .module.css files slightly differently than .css files.

I'm not sure if PostCSS config needs to change depending on the command or stage, but would it be enough if it was possible to completely replace PostCSS loader config only (e.g. via postcss.config.js file or an object), without touching the rest? E.g. postcss-preset-env seems to already include autoprefixer, so it would be better to allow to replace the PostCSS config completely, rather than only extend the existing default one, without allowing to overwrite the defaults.

On one hand it probably is a bad idea to provide specific options for each loader (this might get quickly out of hand, e.g. it would be great if I could change localIdentName in css-loader to generate shorter class names in production build to save a few KB, others might want other stuff), but on the other, the CSS setup is a bit complicated, so replacing it all like @jeroenransijn is doing is a bit hacky and risky.

Gatsby does provide loaders.postcss(options) method, but, if I understand it correctly, I can only use it to generate a new loader config, not to replace the existing one. Maybe there could some function that allows to replace a loader config and one could use switch (stage) {...} to provide correct config for each stage.

@mdreizin
Copy link
Contributor

@szimek @jeroenransijn I have created a simple plugin which does the followings:

  • removes default postcss-loader
  • adds a new one with the default options

Now we can use postcss.config.js without any issues.

Please check https://github.com/mdreizin/gatsby-plugin-postcss

@KyleAMathews
Copy link
Contributor

@mdreizin do you mind helping maintain your plugin here in this repo? I'd been planning on writing something similar to this soon :-) I was planning on using https://github.com/michael-ciniawsky/postcss-load-config to add support for additional ways of handling postcss config.

@KyleAMathews
Copy link
Contributor

If you're amenable to this, just PR the plugin here and add me as an owner on NPM (kylemathews)

@mdreizin
Copy link
Contributor

@KyleAMathews No problems, I will make a new PR within a hour.

@jeroenransijn
Copy link

jeroenransijn commented Jun 28, 2018

@mdreizin thanks so much! This is amazing!

@KyleAMathews as a note this is still removing the current rule https://github.com/mdreizin/gatsby-plugin-postcss/blob/master/src/gatsby-node.js#L9 :

  originalConfig.module.rules = originalConfig.module.rules.filter(rule => {
    if (Array.isArray(rule.oneOf)) {
      return JSON.stringify(rule).indexOf('postcss-loader') === -1;
    }

    return true;
  });

I am okay with this, but would like to highlight it might be nice to make it a feature of the framework to do this safely.

@mdreizin
Copy link
Contributor

@jeroenransijn I also agree that we need to have some methods (or an approach) which help to remove existing loaders.

@mdreizin
Copy link
Contributor

@KyleAMathews I have already granted NPM permissions and created a new PR. It is a start point for further discussions and improvements ;)

I thought about using postcss-load-config and keep gatsby-plugin-postcss as simple as possible, but decided to stick with postcss-loader, because it use postcss-load-config out of the box and solves a lot of existing issues.

@szimek
Copy link
Contributor

szimek commented Jun 29, 2018

@mdreizin Thank you!

@ooloth
Copy link
Contributor

ooloth commented Jul 3, 2018

Or something more like the following to allow disabling other default loaders. Although I am not sure if there is a use case for this.

actions.setDefaultWebpackLoaders({
  css: false,
})

@jeroenransijn I could definitely use this!

Until Gatsby's postcss customization is figured out (including different configs in development vs. production), I've been:

  1. Nuking the default CSS loader
  2. Using the postcss and purgecss CLI tools to generate one stylesheet for development (without purgeCSS) and another for production (with purgeCSS)
  3. Adding a new CSS loader that excludes all CSS partials and builds that aren't relevant at each stage and doesn't apply the Webpack postcss loader

With Webpack v1 in Gatsby v1, I was able to remove the default loader at each stage and redefine it like this:

exports.modifyWebpackConfig = ({ config, stage }) => {
  switch (stage) {
  case `develop`:
    // Remove default CSS loader
    config.removeLoader(`css`)

    // Remove postcss from Gatsby's dev process and ignore partials
    config.loader(`css`, {
      test: /\.css$/,
      exclude: [
        /src\/styles\/base/,
        /src\/styles\/builds\/after-purgecss/,
        /src\/styles\/components/,
        /src\/styles\/plugins/,
        /src\/styles\/reset/,
        /src\/styles\/supports/,
        /src\/styles\/utilities/
      ],
      loaders: [`style`, `css`]
    })

    break

  case `build-css`:
    // Remove default CSS loader
    config.removeLoader(`css`)

    // Remove postcss from Gatsby's build process and ignore partials
    config.loader(`css`, {
      test: /\.css$/,
      exclude: [
        /src\/styles\/base/,
        /src\/styles\/builds\/after-postcss/,
        /src\/styles\/components/,
        /src\/styles\/plugins/,
        /src\/styles\/reset/,
        /src\/styles\/supports/,
        /src\/styles\/utilities/
      ],
      loader: ExtractTextPlugin.extract([`css?minimize`])
    })

    break
  }

  return config
}

Unfortunately, this functionality does not seem to be available with Webpack v4 in Gatsby v2.

Any tips on how to duplicate this functionality in Gatsby v2 would be very helpful!

(A plugin that enables the same functionality without having to use the CLI tools or modify the Webpack config directly would obviously be fantastic.)

@jquense
Copy link
Contributor

jquense commented Jul 3, 2018

@ooloth none of that should be necessary in v2, Gatsby does not include postcss except to autoprefix, which shouldn't affect any style flow. v1 had a fairly prescriptive postcss loader by default, not that has been removed in v2. I'm not sure what all your excludes are for tho, can you talk about why you need them?

@ooloth
Copy link
Contributor

ooloth commented Jul 3, 2018

@jquense Thanks for the clarification about postcss in v2. In my case, I need to run the precss and tailwindcss postcss plugins before the autoprefixer plugin is run, so the v2 postcss setup may still interrupt some style flows. (I see above that a plugin is on its way to handle this, though.)

Regarding your question about all the excludes, they are there to facilitate my CSS workflow, which currently consists of one index.css (which imports a number of CSS partials) that is run through the postcss CLI tool in development (which outputs styles/builds/after-postcss/output.css) and the postcss and purgecss CLI tools in production (which outputs styles/builds/after-purgecss/output.css).

In my layout component, I import the relevant CSS file for the current environment:

// Use PostCSS stylesheet in development and PostCSS/PurgeCSS stylesheet in production:
switch (process.env.NODE_ENV) {
  case `development`:
    require(`../styles/builds/after-postcss/output.css`)
    break
  case `production`:
    require(`../styles/builds/after-purgecss/output.css`)
    break
}

Basically, I just want to run a specific sequence of postcss plugins in development and then the same postcss plugins + purgecss in production.

In v1, I found the development spreadsheet would unfortunately still be inlined in production unless I added the excludes. I opted to "exclude" irrelevant files (rather than explicitly testing just for relevant ones) to make sure Gatsby still picked up CSS produced by other packages (e.g. typefaces).

In v2, the webpack setup in my previous comment no longer works and once again the development version of my CSS is loading in production (slowing page load as it includes many unused styles).

I'm open to any advice here! A cleaner approach that just uses a single stylesheet (instead of separate dev and production stylesheets) and uses webpack to run postcss on it for development and postcss + purgecss for production would be lovely.

@jquense
Copy link
Contributor

jquense commented Jul 3, 2018

In my case, I need to run the precss and tailwindcss postcss plugins before the autoprefixer plugin is run, so the v2 postcss setup may still interrupt some style flows. (I see above that a plugin is on its way to handle this, though.)

You shouldn't need to update the existing loader then, adding a new loader on the end would be enough. You can either piggyback on the existing loader by adding just postcss loader (no css or style) or do the whole cahin and the files will be handled before the default one effectively ignore it.

Basically, I just want to run a specific sequence of postcss plugins in development and then the same postcss plugins + purgecss in production.

I think you are being too smart for webpack, try switching your switch to a simpler if then block like if (process.env.NODE_ENV === 'production')

@ooloth
Copy link
Contributor

ooloth commented Jul 3, 2018

I think you are being too smart for webpack, try switching your switch to a simpler if then block like if (process.env.NODE_ENV === 'production')

Can you clarify which switch you're referring to?

(I had one in my layout component and one in gatsby-node above.)

@ooloth
Copy link
Contributor

ooloth commented Jul 3, 2018

@jquense You were absolutely right!

Updating from switch to if in my layout component fixed the issue with the dev stylesheet loading in production:

// Use PostCSS stylesheet in development and PostCSS/PurgeCSS stylesheet in production:
if (process.env.NODE_ENV === `development`) {
  require(`../styles/builds/after-postcss/output.css`)
} else if (process.env.NODE_ENV === `production`) {
  require(`../styles/builds/after-purgecss/output.css`)
}

No idea why that worked, but I'm happy. :)

Any advice about how to apply purgeCSS only in production (either via webpack or postcss) without having to use the CLI tools and generate two different stylesheets?

@jquense
Copy link
Contributor

jquense commented Jul 4, 2018

Glad that is working. If you can point me to a minimal purgeCSS setup I might be able to recommend something. But I'm not really familiar with it

@ooloth
Copy link
Contributor

ooloth commented Jul 4, 2018

@jquense Thanks!

PurgeCSS is a tool that removes unused selectors from a stylesheet to reduce file size (useful when using utility-class libraries like Tachyons or TailwindCSS). It's like purifyCSS (which there is already a Gatsby plugin for), but much more effective.

How to achieve the minimal setup is the question, but here are examples of each option:

I currently use the CLI tool (see example) to take the "after-postcss" version of my stylesheet and generate the "after-purgecss" version for production before each gatsby build. Ideally, I'd like to move away from this approach.

PurgeCSS can also be used with Webpack (see example) or postcss (see example). Could you possibly recommend how to integrate one of these approaches with Gatsby (in production only)? I'm a bit confused about exactly how to add the webpack example to Gatsby's v2 webpack config or how to add a production-only postcss plugin to the upcoming gatsby-plugin-postcss...

(Thanks in advance!)

@jquense
Copy link
Contributor

jquense commented Jul 4, 2018

It looks like you need to include the plugin you can use the gatsby API and stage to only alter the config during the build (production) steps:

exports.onCreateWebpackConfig = ({ stage, actions }) => {
   if (stage.includes('develop')) return

   actions.setWebpackConfig({ 
      plugins: [new PurgecssPlugin(yourOptions)]
   })
}

@ooloth
Copy link
Contributor

ooloth commented Jul 4, 2018

@jquense Thanks again!

That got me started and I got PurgeCSS working in production like this:

const PurgeCssPlugin = require(`purgecss-webpack-plugin`)
const path = require(`path`)
const glob = require(`glob`)

const PATHS = {
  src: path.join(__dirname, `src`)
}

// Nonessential options removed for brevity
const purgeCssConfig = {
  paths: glob.sync(`${PATHS.src}/**/*.js`, { nodir: true }),
}

exports.onCreateWebpackConfig = ({ actions, stage }) => {
  if (stage.includes(`develop`)) return

  // Add PurgeCSS in production
  if (stage.includes(`build`)) {
    actions.setWebpackConfig({
      plugins: [new PurgeCssPlugin(purgeCssConfig)]
    })
  }
}

Now my layout component can just import one file (generated by the postcss CLI) instead of using the conditional loading logic above:

import '../styles/builds/after-postcss/output.css'

I tried add adding some of the postcss examples above to gatsby-node so I can drop the postcss CLI, but kept running into errors. Hopefully gatsby-plugin-postcss will solve that!

@ooloth
Copy link
Contributor

ooloth commented Jul 25, 2018

I just noticed that the production css produced by gatsby-node snippet in my last comment is not minified. (I also use gatsby-plugin-postcss now instead of postcss-cli.)

Any tips how to minify the CSS output in production?

@iljapanic
Copy link

As of the official release of v2 PostCSS seems to work like a charm.

I started off configuring various PostCSS plugins myself just to find out everything is done through postcss-preset-env these days. Both manually declaring PostCSS plugins and doing all the transformations through the postcss-preset-env works.

Here is the plugin config that is working well for me:

    {
      resolve: `gatsby-plugin-postcss`,
      options: {
        postCssPlugins: [
          require(`postcss-preset-env`)({
            stage: 1,
            browsers: '< 1%'
          })
        ],
      },
    },

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: question or discussion Issue discussing or asking a question about Gatsby
Projects
None yet
Development

No branches or pull requests

9 participants