loading...
Cover image for Add PostCSS to Create-React-App

Add PostCSS to Create-React-App

puritanic profile image Darkø Tasevski Updated on ・5 min read

Without ejecting!

A small introduction to PostCSS

Many of you are spending your time working with CSS, so I guess you are familiar with preprocessors such as Less, Sass, and Stylus. These tools are a vital part of the web development ecosystem nowadays, and I cannot imagine writing styles for a website without using features like nesting, variables, or mixins as it can be cumbersome, awkward and sometimes less intuitive.

But, there is a couple of problems with traditional preprocessors:

  • They don't follow CSS standards. You might say that each of the preprocessors has become a standard of its own. Regrettably, they don’t aim at being compatible with the W3C standards, which means that they cannot you cant use their features as polyfills for early testing of the newer W3C standards.
  • They are not extendable. Whichever preprocessor you choose, you are limited to the set of features that it provides. If you need anything on top of that, you’ll need to add it as a separate step in your build process. If you feel like writing your extension, you’re on your own. Good luck with that.

This is where PostCSS comes in.

In a nutshell, PostCSS does the same thing as LESS/SASS/SCSS...
But enhance it with a few plugins, and it can do much more.
PostCSS is much like Babel for JavaScript.
It parses your CSS, using Javascript under the hood, turning it into the raw AST (abstract syntax tree) and then performs transformations to the CSS that today's browsers will understand and render.
And a bonus is that JavaScript can transform our styles much more quickly than other processors.

Enough about PostCSS and let's focus on the purpose of this post.
After some Googling, I've found out that for PostCss to work, you had to eject CRA to edit the underlying Webpack configuration, which deals with all the necessary loaders for different file types, which I've found as a bit of a drastic measure. In the end, after a bit of trial and error hacking together different solutions, I got it working. Here’s how!

You can find the final code here: https://github.com/Puritanic/CRA-feat-PostCSS.

Edit: It seems that my google foo isn't good as I thought it was. Here's an alternate method for extending CRA with PostCSS created by @jonathantneal

First, create a new React app:

create-react-app postcss-cra
cd postcss-cra

And install postcss-cli and a few basic plugins

yarn add --dev autoprefixer postcss-nested postcss-cli npm-run-all 

Then, at the root of the project, you need to create a file called postcss.config.js and add this code:

module.exports = {
    plugins: [
        require('postcss-nested'),
        require('autoprefixer'),
    ],
};

Almost there!
Now all that is left is to edit the scripts in package.json :


    "scripts": {
        "build:css": "postcss src/styles/main.css -o src/index.css",
        "watch:css": "postcss src/styles/main.css -o src/index.css -w",
        "start": "npm-run-all -p watch:css start-js",
        "start-js": "react-scripts start",
        "build-js": "react-scripts build",
        "build": "npm-run-all build:css build-js",
        "test": "react-scripts test --env=jsdom",
        "eject": "react-scripts eject"
        },

Create a styles folder inside src where our CSS will live:

mkdir src/styles
mv src/App.css src/styles/main.css  

And to test postcss, let's modify default CRA styles a bit:

/* src/styles/main.css */
.App {
  text-align: center;

    &-logo {
        animation: App-logo-spin infinite 20s linear;
        height: 80px;
    }

    &-header {
        background-color: #222;
        height: 150px;
        padding: 20px;
        color: white;
    }

    &-title {
        font-size: 1.5em;
    }

    &-intro {
        font-size: large; 
    }
}
@keyframes App-logo-spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

Don't forget to remove import './App.css';' from App.js, as we've moved that file to styles and renamed it to main.css.

Moment of truth! Now run: yarn run start.
In the browser, you should see almost the same styles that CRA by default has. Now let's see output index.css file in src/:

.App {
    text-align: center;
    display: flex;
    flex-direction: column;
}

.App-logo {
        -webkit-animation: App-logo-spin infinite 20s linear;
                animation: App-logo-spin infinite 20s linear;
        height: 80px;
    }

.App-header {
        background-color: #222;
        height: 150px;
        padding: 20px;
        color: white;
    }

.App-title {
        font-size: 1.5em;
    }

.App-intro {
        font-size: large;
    }

@-webkit-keyframes App-logo-spin {
    from {
        -webkit-transform: rotate(0deg);
                transform: rotate(0deg);
    }
    to {
        -webkit-transform: rotate(360deg);
                transform: rotate(360deg);
    }
}

@keyframes App-logo-spin {
    from {
        -webkit-transform: rotate(0deg);
                transform: rotate(0deg);
    }
    to {
        -webkit-transform: rotate(360deg);
                transform: rotate(360deg);
    }
}

Voila! As you can see, our styles are automatically auto-vendor-prefixed for better support, and our nested code is transformed to code that browser can understand.

Now let's do something more advanced:

yarn add -D postcss-import postcss-preset-env

After that, let's add those new plugins to postcss.config.js:

module.exports = {
    plugins: [
        require('postcss-import'),
        require('postcss-preset-env')({
            stage: 1,
        }),
        require('postcss-nested'),
        require('autoprefixer'),
    ],
};

Now, create a test files inside styles folder:

touch src/styles/styles.css src/styles/styles1.css

Move everything from src/styles/main.css to styles.css and add this:

@import 'styles.css';
@import 'styles1.css';

Now, in src/styles/styles1.css add this weird mumbo-jumbo:

@custom-media --viewport-medium (width <= 50rem);
@custom-selector :--heading h1, h2, h3, h4, h5, h6;

:root {
    --fontSize: 1rem;
    --mainColor: #12345678;
    --secondaryColor: lab(32.5 38.5 -47.6 / 90%);
}

html {
    overflow: hidden auto;
}

@media (--viewport-medium) {
    body {
        color: var(--mainColor);
        font-family: system-ui;
        font-size: var(--fontSize);
        line-height: calc(var(--fontSize) * 1.5);
        overflow-wrap: break-word;
        padding-inline: calc(var(--fontSize) / 2 + 1px);
    }
}

:--heading {
    margin-block: 0;
}

.App-header:matches(header, .main) {
    background-image: image-set('./img/background.jpg' 1x, './img/background-2x.jpg' 2x);
    background-size: contain;
}

a {
    color: rebeccapurple;

    &:hover {
        color: color-mod(var(--secondaryColor) b(15%) a(75%));
    }
}

Now restart CRA server. Everything should work as intended, except for several (obligatory) cats showing in the header now :D

This is just a scratch on the surface, PostCSS have enormous power in its plugins, and have a great community around it. What's the best is that the most of PostCSS plugins are using stuff that will be used as native CSS syntax in future, so your code is future-proof. In the end, I'm enjoying using it, and that's what matters, and I hope that you will too. Thanks for reading!

Some resources:
Official PostCSS Github Repo and Page
PostCSS-preset-env playground
List of awesome PostCSS plugins

Discussion

pic
Editor guide
Collapse
jonathantneal profile image
Jonathan Neal

Take a look at Rewire. It lets you extend Create React App without ejecting. There is a plugin for PostCSS, too. Let me know what you think.

github.com/csstools/react-app-rewi...

Collapse
cl3m3cy profile image
iHmD

Good stuff!
thanks man!

Collapse
puritanic profile image
Darkø Tasevski Author

Looks interesting, going to take it for a spin in my next project. Thanks :D

Collapse
z2lai profile image
z2lai

Nice solution, thanks for writing this! I'd suggest adding some explanations of what the new scripts in package.json do as that looks like the most important part of getting PostCSS to work with React. Also, adding a heading or intro for the "Now let's do something more advanced" part would make the purpose of that section clearer for beginners like myself.

Collapse
avikaminetzky profile image
Avi Kaminetzky

Apparently CRA 2.0 has PostCSS included (as well as CSS modules). twitter.com/timer150/status/104527...

Collapse
puritanic profile image
Darkø Tasevski Author

Only Autoprefixer as far as I know, and that's what this tweet says. You can't add custom plugins to PostCSS in CRA currently, not sure about version 2.

Collapse
avikaminetzky profile image
Avi Kaminetzky

Yes.
I'm curious popular/useful custom plugins you've come across.

Collapse
karlkras profile image
Karl Krasnowsky

So would I configure this so the transpiler would find any file with the, e.g., extension *.pcss anywhere under my ./src location in my project?

Collapse
puritanic profile image
Darkø Tasevski Author

This postcss-cli config is watching only one file for changes, main.css in styles/ folder. All the other files are imported inside that file, so it's basically watching all imported files.

If you want to watch the whole dir, you can do it too, but you need to change script command a bit, there are a few examples how to do that in the main postcss-cli repository.

About extension, simple .css will work fine, as far as I know, there is no official extension for postcss files, and community recommends using just plain old .css.

Collapse
karlkras profile image
Karl Krasnowsky

Well I'm looking into incorporating a large, existing library that is using the pcss exension exclusively, I was hoping to use it as is without renaming the entire code base.

Collapse
basehq profile image
BaseHQ

Does this work with create-react-app v3?
it doesn't seem to process anything for me.

Collapse
puritanic profile image
Darkø Tasevski Author

Sorry for the late reply, I haven't tried this with the new CRA but it should work without the problems.