loading...

Switching from SASS to PostCSS

michaeldscherr profile image Michael Scherr Updated on ・4 min read

Ever since PostCSS came out, I've exclusively used it for Autoprefixer. I didn't really dig into the power of PostCSS and how it could actually be a comparable solution to SASS.

This article will go into depth about my skepticisms, and how I was able to find solutions using PostCSS.

The Push

What originally considered me to switch was my interest in Tailwind CSS. I love the ideas of utility libraries, but found it difficult to sell to other developers. However, we've been looking for ways to optimize development time, so Tailwind felt like a good choice. It's built with PostCSS in mind, so I at least wanted to give it a try.

Previous Approach

My previous approach was a mix of SASS, BEM, ITCSS, & SMACSS. They solved the following issues:

  • SASS - variables, loops, imports, nested selectors, etc
  • BEM - reduce specificity and allow for easier additions / modifications to code
  • ITCSS - (same as BEM), but also allows for an inverted triangle hierarchy for specificity gets more and more specific.
  • SMACSS - used mostly for modifier classes, like .js-is-active, .has-posts, etc.

This worked really well for me. I saw the power of utility libraries, but figured I would let them evolve and try them out later.

The Switch

I won't bore you with the details, but I went through many iterations like:

  • PostCSS only, no plugins besides Tailwind
  • PostCSS + SASS
  • PostCSS only, with Tailwind and other complementary plugins

The core issue with the PostCSS + SASS approach was the inevitable duplication. For example, these situations can't work together:

/**
  * cannot set $font-size-px-base in PostCSS
  * since SASS gets run first, so would have
  * to duplicate code
*/
@function rem($pixels, $context: $font-size-px-base) {
  @if (unitless($pixels)) {
    $pixels: $pixels * 1px;
  }
  @if (unitless($context)) {
    $context: $context * 1px;
  }
  @return $pixels / $context * 1rem;
}

/**
  * can't use Tailwind variables
  * in the SASS function `scale` since
  * SASS runs before PostCSS
*/
.selector {
  font-size: scale(font-size, theme('fontSize.lg'), theme('fontSize.xl'));
}

/**
  * Interpolation was also a nightmare
  * I couldn't figure out how to make
  * something like this work
  * even with PostCSS Simple Vars
*/
.selector {
  $path: theme('path.theme');

  background-image: url($(path)/test.png);
}

Final Solution

The final solution involved PostCSS only, with Tailwind and other complementary plugins.

When I thought about it, I was really using SASS for:

  • imports
  • mixins
  • functions
  • variables
  • nested selectors - rarely, but nice to have, especially for pseudo selectors
  • loops - very rarely, but nice to have

PostCSS has solutions for all of these once I realized the power of their plugin system. Furthermore, I found out some plugins, like for functions and mixins, allow you to inject Javascript functions into PostCSS. I was sold.

Imports

PostCSS Imports gives the exact same functionality as SASS Imports, so it was an easy switch.

@import 'tailwindcss/base';

@import 'tailwindcss/components';
@import 'components/btn';

@import 'tailwindcss/utilities';

Functions

PostCSS Functions allows you to write functions IN JAVASCRIPT and inject them into PostCSS. IN JAVASCRIPT. I LOVE JAVASCRIPT.

// functions.js

let settings = require('./settings');

let rem = (pixels, context = settings.defaults.fontSizePxBase) => {
  pixels = parseFloat(pixels);

  let result = pixels / context;

  return `${result}rem`;
};

module.exports = {
  rem,
};

Mixins

PostCSS Mixins is the same deal as PostCSS Functions.

// mixins.js

let placeholder = (mixin, immediateSelector = true) => {
  let vendors = [
    '::-webkit-input-placeholder',
    ':-moz-placeholder',
    '::-moz-placeholder',
    ':-ms-input-placeholder',
  ];

  return vendors.reduce((prev, vendor) => {
    let selector = immediateSelector ? `&${vendor}` : vendor;

    prev[selector] = {
      '@mixin-content': {},
    };

    return prev;
  }, {});
};

module.exports = {
  placeholder,
};

Other Notable Plugins

Example PostCSS Config File

// postcss.config.js

let functions = require('./functions');
let mixins = require('./mixins');

module.exports = function(context) {
  /**
   * context comes from what you pass
   * into `gulp-postcss`, omitting detailing here
   * since `gulp` is an implementation detail
   */
  let { options } = context;

  let plugins = [
    require('postcss-import')({
      path: [options.paths.components, options.paths.styles, 'node_modules'],
    }),
    require('tailwindcss')('./tailwind.config.js'),
    require('postcss-functions')({
      functions,
    }),
    require('postcss-mixins')({
      mixins,
    }),
    require('postcss-nested'),
    require('postcss-simple-vars'),
    require('autoprefixer'),
  ];

  return {
    plugins,
  };
};

I hope this at least got you interested in PostCSS and what it's powerful plugin system can provide for your next project.

Posted on by:

michaeldscherr profile

Michael Scherr

@michaeldscherr

I swear by my code and my love of it that I will never code for the sake of another man, nor ask another man to code for mine.

Discussion

pic
Editor guide
 

Fascinating explanation.

I also make article about PostCSS.

epsi-rns.gitlab.io/frontend/2019/1...

PostCSS Loop Spacing Helper

 

Nice write-up! Check out postcss-preset-env, which does the same thing as a lot of your plugins, in just one: preset-env.cssdb.org/

 

Thanks! I'll check it out soon.